├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── pyproject.toml ├── python └── dict_not_none │ └── __init__.py ├── src └── lib.rs └── tests └── test_dict_not_none.py /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | .pytest_cache/ 6 | *.py[cod] 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | .venv/ 14 | env/ 15 | bin/ 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | include/ 26 | man/ 27 | venv/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | pip-selfcheck.json 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | 45 | # Translations 46 | *.mo 47 | 48 | # Mr Developer 49 | .mr.developer.cfg 50 | .project 51 | .pydevproject 52 | 53 | # Rope 54 | .ropeproject 55 | 56 | # Django stuff: 57 | *.log 58 | *.pot 59 | 60 | .DS_Store 61 | 62 | # Sphinx documentation 63 | docs/_build/ 64 | 65 | # PyCharm 66 | .idea/ 67 | 68 | # VSCode 69 | .vscode/ 70 | 71 | # Pyenv 72 | .python-version -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.1.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 10 | 11 | [[package]] 12 | name = "bitflags" 13 | version = "1.3.2" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 16 | 17 | [[package]] 18 | name = "cfg-if" 19 | version = "1.0.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 22 | 23 | [[package]] 24 | name = "dict-not-none" 25 | version = "0.1.0" 26 | dependencies = [ 27 | "pyo3", 28 | ] 29 | 30 | [[package]] 31 | name = "indoc" 32 | version = "1.0.9" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" 35 | 36 | [[package]] 37 | name = "libc" 38 | version = "0.2.147" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" 41 | 42 | [[package]] 43 | name = "lock_api" 44 | version = "0.4.10" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" 47 | dependencies = [ 48 | "autocfg", 49 | "scopeguard", 50 | ] 51 | 52 | [[package]] 53 | name = "memoffset" 54 | version = "0.9.0" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" 57 | dependencies = [ 58 | "autocfg", 59 | ] 60 | 61 | [[package]] 62 | name = "once_cell" 63 | version = "1.18.0" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 66 | 67 | [[package]] 68 | name = "parking_lot" 69 | version = "0.12.1" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 72 | dependencies = [ 73 | "lock_api", 74 | "parking_lot_core", 75 | ] 76 | 77 | [[package]] 78 | name = "parking_lot_core" 79 | version = "0.9.8" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" 82 | dependencies = [ 83 | "cfg-if", 84 | "libc", 85 | "redox_syscall", 86 | "smallvec", 87 | "windows-targets", 88 | ] 89 | 90 | [[package]] 91 | name = "proc-macro2" 92 | version = "1.0.66" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" 95 | dependencies = [ 96 | "unicode-ident", 97 | ] 98 | 99 | [[package]] 100 | name = "pyo3" 101 | version = "0.19.2" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "e681a6cfdc4adcc93b4d3cf993749a4552018ee0a9b65fc0ccfad74352c72a38" 104 | dependencies = [ 105 | "cfg-if", 106 | "indoc", 107 | "libc", 108 | "memoffset", 109 | "parking_lot", 110 | "pyo3-build-config", 111 | "pyo3-ffi", 112 | "pyo3-macros", 113 | "unindent", 114 | ] 115 | 116 | [[package]] 117 | name = "pyo3-build-config" 118 | version = "0.19.2" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "076c73d0bc438f7a4ef6fdd0c3bb4732149136abd952b110ac93e4edb13a6ba5" 121 | dependencies = [ 122 | "once_cell", 123 | "target-lexicon", 124 | ] 125 | 126 | [[package]] 127 | name = "pyo3-ffi" 128 | version = "0.19.2" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "e53cee42e77ebe256066ba8aa77eff722b3bb91f3419177cf4cd0f304d3284d9" 131 | dependencies = [ 132 | "libc", 133 | "pyo3-build-config", 134 | ] 135 | 136 | [[package]] 137 | name = "pyo3-macros" 138 | version = "0.19.2" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "dfeb4c99597e136528c6dd7d5e3de5434d1ceaf487436a3f03b2d56b6fc9efd1" 141 | dependencies = [ 142 | "proc-macro2", 143 | "pyo3-macros-backend", 144 | "quote", 145 | "syn", 146 | ] 147 | 148 | [[package]] 149 | name = "pyo3-macros-backend" 150 | version = "0.19.2" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "947dc12175c254889edc0c02e399476c2f652b4b9ebd123aa655c224de259536" 153 | dependencies = [ 154 | "proc-macro2", 155 | "quote", 156 | "syn", 157 | ] 158 | 159 | [[package]] 160 | name = "quote" 161 | version = "1.0.33" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 164 | dependencies = [ 165 | "proc-macro2", 166 | ] 167 | 168 | [[package]] 169 | name = "redox_syscall" 170 | version = "0.3.5" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" 173 | dependencies = [ 174 | "bitflags", 175 | ] 176 | 177 | [[package]] 178 | name = "scopeguard" 179 | version = "1.2.0" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 182 | 183 | [[package]] 184 | name = "smallvec" 185 | version = "1.11.0" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" 188 | 189 | [[package]] 190 | name = "syn" 191 | version = "1.0.109" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 194 | dependencies = [ 195 | "proc-macro2", 196 | "quote", 197 | "unicode-ident", 198 | ] 199 | 200 | [[package]] 201 | name = "target-lexicon" 202 | version = "0.12.11" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" 205 | 206 | [[package]] 207 | name = "unicode-ident" 208 | version = "1.0.11" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" 211 | 212 | [[package]] 213 | name = "unindent" 214 | version = "0.1.11" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" 217 | 218 | [[package]] 219 | name = "windows-targets" 220 | version = "0.48.5" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 223 | dependencies = [ 224 | "windows_aarch64_gnullvm", 225 | "windows_aarch64_msvc", 226 | "windows_i686_gnu", 227 | "windows_i686_msvc", 228 | "windows_x86_64_gnu", 229 | "windows_x86_64_gnullvm", 230 | "windows_x86_64_msvc", 231 | ] 232 | 233 | [[package]] 234 | name = "windows_aarch64_gnullvm" 235 | version = "0.48.5" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 238 | 239 | [[package]] 240 | name = "windows_aarch64_msvc" 241 | version = "0.48.5" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 244 | 245 | [[package]] 246 | name = "windows_i686_gnu" 247 | version = "0.48.5" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 250 | 251 | [[package]] 252 | name = "windows_i686_msvc" 253 | version = "0.48.5" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 256 | 257 | [[package]] 258 | name = "windows_x86_64_gnu" 259 | version = "0.48.5" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 262 | 263 | [[package]] 264 | name = "windows_x86_64_gnullvm" 265 | version = "0.48.5" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 268 | 269 | [[package]] 270 | name = "windows_x86_64_msvc" 271 | version = "0.48.5" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 274 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dict-not-none" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | [lib] 8 | name = "_dict_not_none" 9 | crate-type = ["cdylib"] 10 | 11 | [dependencies] 12 | 13 | # for "present version" of dict_not_none_pyo3 14 | pyo3 = "0.19" 15 | 16 | # uncomment to use "future version" of dict_not_none_pyo3 17 | # pyo3 = { git = "https://github.com/davidhewitt/pyo3", branch = "list-methods-trait" } 18 | 19 | [profile.release] 20 | lto = "fat" 21 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["maturin>=1.2,<2.0"] 3 | build-backend = "maturin" 4 | 5 | [project] 6 | name = "dict-not-none" 7 | requires-python = ">=3.7" 8 | classifiers = [ 9 | "Programming Language :: Rust", 10 | "Programming Language :: Python :: Implementation :: CPython", 11 | "Programming Language :: Python :: Implementation :: PyPy", 12 | ] 13 | 14 | 15 | [tool.maturin] 16 | features = ["pyo3/extension-module"] 17 | python-source = "python" 18 | module-name = "dict_not_none._dict_not_none" 19 | -------------------------------------------------------------------------------- /python/dict_not_none/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from ._dict_not_none import * 4 | 5 | 6 | def dict_not_none(**kwargs: Any) -> Any: 7 | return {k: v for k, v in kwargs.items() if v is not None} 8 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use pyo3::{ 2 | ffi::{self, PyTuple_GET_ITEM, PyTuple_GET_SIZE}, 3 | prelude::*, 4 | types::{PyCFunction, PyDict}, 5 | }; 6 | 7 | // PRESENT FORM 8 | 9 | #[pyfunction(signature = (**kwargs))] 10 | fn dict_not_none_pyo3<'py>(py: Python<'py>, kwargs: Option<&'py PyDict>) -> &'py PyDict { 11 | let out = PyDict::new(py); 12 | if let Some(kwargs) = kwargs { 13 | for (key, value) in kwargs.iter() { 14 | if !value.is_none() { 15 | out.set_item(key, value).unwrap(); 16 | } 17 | } 18 | } 19 | out 20 | } 21 | 22 | // FUTURE FORM 23 | 24 | // #[pyfunction(signature = (**kwargs))] 25 | // fn dict_not_none_pyo3<'py>(py: Python<'py>, kwargs: Option>) -> Py2<'py, PyDict> { 26 | // let out = PyDict::new(py); 27 | // if let Some(kwargs) = kwargs { 28 | // for (key, value) in kwargs.iter() { 29 | // if !value.is_none() { 30 | // out.set_item(key, value).unwrap(); 31 | // } 32 | // } 33 | // } 34 | // out 35 | // } 36 | 37 | unsafe extern "C" fn dict_not_none_baremetal( 38 | _self: *mut ffi::PyObject, 39 | args: *const *mut ffi::PyObject, 40 | nargs: ffi::Py_ssize_t, 41 | kwnames: *mut ffi::PyObject, 42 | ) -> *mut ffi::PyObject { 43 | let kwargs = ffi::PyDict_New(); 44 | let kwargs_count = PyTuple_GET_SIZE(kwnames); 45 | if nargs != 0 { 46 | ffi::PyErr_SetString( 47 | ffi::PyExc_ValueError, 48 | "expected positional arguments only\0".as_ptr().cast(), 49 | ); 50 | return std::ptr::null_mut(); 51 | } 52 | // args is expected to be an array of pointers to PyObject, 53 | // where the first `nargs` are positional and the trailing are keyword arguments 54 | // which correspond to `kwnames` in turn. 55 | let mut current = args; 56 | for i in 0..kwargs_count { 57 | if ffi::Py_IsNone(*current) == 0 { 58 | ffi::PyDict_SetItem(kwargs, PyTuple_GET_ITEM(kwnames, i), *current); 59 | } 60 | current = current.add(1); 61 | } 62 | kwargs 63 | } 64 | 65 | static DICT_NOT_NONE: pyo3::PyMethodDef = pyo3::PyMethodDef::fastcall_cfunction_with_keywords( 66 | "dict_not_none_baremetal", 67 | pyo3::methods::PyCFunctionFastWithKeywords(dict_not_none_baremetal), 68 | "", 69 | ); 70 | 71 | #[pymodule] 72 | fn _dict_not_none(_py: Python, m: &PyModule) -> PyResult<()> { 73 | m.add_function(wrap_pyfunction!(dict_not_none_pyo3, m)?)?; 74 | 75 | m.add_function(PyCFunction::internal_new(&DICT_NOT_NONE, m.into())?)?; 76 | 77 | Ok(()) 78 | } 79 | -------------------------------------------------------------------------------- /tests/test_dict_not_none.py: -------------------------------------------------------------------------------- 1 | from dict_not_none import dict_not_none, dict_not_none_pyo3, dict_not_none_baremetal 2 | 3 | 4 | def test_dict_not_none_py(benchmark): 5 | assert benchmark(dict_not_none, a=1, b=None, c=3, d=None, e=5, f=None) == { 6 | "a": 1, 7 | "c": 3, 8 | "e": 5, 9 | } 10 | 11 | 12 | def test_dict_not_none_pyo3(benchmark): 13 | assert benchmark(dict_not_none_pyo3, a=1, b=None, c=3, d=None, e=5, f=None) == { 14 | "a": 1, 15 | "c": 3, 16 | "e": 5, 17 | } 18 | 19 | 20 | def test_dict_not_none_baremetal(benchmark): 21 | assert benchmark( 22 | dict_not_none_baremetal, a=1, b=None, c=3, d=None, e=5, f=None 23 | ) == { 24 | "a": 1, 25 | "c": 3, 26 | "e": 5, 27 | } 28 | --------------------------------------------------------------------------------