diggity 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,143 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ - master
8
+ tags:
9
+ - '*'
10
+ pull_request:
11
+ workflow_dispatch:
12
+
13
+ permissions:
14
+ contents: read
15
+
16
+ jobs:
17
+ linux:
18
+ runs-on: ${{ matrix.platform.runner }}
19
+ strategy:
20
+ matrix:
21
+ platform:
22
+ - runner: ubuntu-22.04
23
+ target: x86_64
24
+ - runner: ubuntu-22.04
25
+ target: x86
26
+ - runner: ubuntu-22.04
27
+ target: aarch64
28
+ - runner: ubuntu-22.04
29
+ target: armv7
30
+ steps:
31
+ - uses: actions/checkout@v4
32
+ - uses: actions/setup-python@v5
33
+ with:
34
+ python-version: 3.x
35
+ - name: Build wheels
36
+ uses: PyO3/maturin-action@v1
37
+ with:
38
+ target: ${{ matrix.platform.target }}
39
+ args: --release --out dist --find-interpreter
40
+ sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
41
+ manylinux: auto
42
+ - name: Upload wheels
43
+ uses: actions/upload-artifact@v4
44
+ with:
45
+ name: wheels-linux-${{ matrix.platform.target }}
46
+ path: dist
47
+
48
+ windows:
49
+ runs-on: ${{ matrix.platform.runner }}
50
+ strategy:
51
+ matrix:
52
+ platform:
53
+ - runner: windows-latest
54
+ target: x64
55
+ - runner: windows-latest
56
+ target: x86
57
+ steps:
58
+ - uses: actions/checkout@v4
59
+ - uses: actions/setup-python@v5
60
+ with:
61
+ python-version: 3.x
62
+ architecture: ${{ matrix.platform.target }}
63
+ - name: Build wheels
64
+ uses: PyO3/maturin-action@v1
65
+ with:
66
+ target: ${{ matrix.platform.target }}
67
+ args: --release --out dist --find-interpreter
68
+ sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
69
+ - name: Upload wheels
70
+ uses: actions/upload-artifact@v4
71
+ with:
72
+ name: wheels-windows-${{ matrix.platform.target }}
73
+ path: dist
74
+
75
+ pytest:
76
+ name: test ${{ matrix.python-version }}
77
+ strategy:
78
+ fail-fast: false
79
+ matrix:
80
+ python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
81
+ runs-on: ubuntu-22.04
82
+ permissions:
83
+ contents: read
84
+ steps:
85
+ - uses: actions/checkout@v4
86
+ - name: Install Rust stable
87
+ uses: dtolnay/rust-toolchain@stable
88
+ - name: Cache Rust
89
+ uses: Swatinem/rust-cache@v2
90
+ with:
91
+ key: test-${{ matrix.python-version }}-v3
92
+ - name: Install uv
93
+ uses: astral-sh/setup-uv@v5
94
+ with:
95
+ python-version: ${{ matrix.python-version }}
96
+ - name: Install deps
97
+ run: uv sync --group testing
98
+ - run: uv pip install -e .
99
+ env:
100
+ RUST_BACKTRACE: 1
101
+ - run: uv pip freeze
102
+ - run: uv run pytest
103
+
104
+ sdist:
105
+ runs-on: ubuntu-latest
106
+ permissions:
107
+ contents: read
108
+ steps:
109
+ - uses: actions/checkout@v4
110
+ - name: Build sdist
111
+ uses: PyO3/maturin-action@v1
112
+ with:
113
+ command: sdist
114
+ args: --out dist
115
+ - name: Upload sdist
116
+ uses: actions/upload-artifact@v4
117
+ with:
118
+ name: wheels-sdist
119
+ path: dist
120
+
121
+ release:
122
+ name: Release
123
+ runs-on: ubuntu-latest
124
+ if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }}
125
+ needs: [linux, windows, pytest, sdist]
126
+ permissions:
127
+ id-token: write
128
+ contents: write
129
+ attestations: write
130
+ steps:
131
+ - uses: actions/download-artifact@v4
132
+ - name: Generate artifact attestation
133
+ uses: actions/attest-build-provenance@v1
134
+ with:
135
+ subject-path: 'wheels-*/*'
136
+ - name: Publish to PyPI
137
+ if: ${{ startsWith(github.ref, 'refs/tags/') }}
138
+ uses: PyO3/maturin-action@v1
139
+ env:
140
+ MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
141
+ with:
142
+ command: upload
143
+ args: --non-interactive --skip-existing wheels-*/*
@@ -0,0 +1,78 @@
1
+ debug/
2
+ target/
3
+ /target
4
+
5
+ **/*.rs.bk
6
+
7
+ *.pdb
8
+
9
+ # Byte-compiled / optimized / DLL files
10
+ __pycache__/
11
+ .pytest_cache/
12
+ *.py[cod]
13
+
14
+ # C extensions
15
+ *.so
16
+
17
+ # Distribution / packaging
18
+ .Python
19
+ .venv/
20
+ env/
21
+ bin/
22
+ build/
23
+ develop-eggs/
24
+ dist/
25
+ eggs/
26
+ lib/
27
+ lib64/
28
+ parts/
29
+ sdist/
30
+ var/
31
+ include/
32
+ man/
33
+ venv/
34
+ *.egg-info/
35
+ .installed.cfg
36
+ *.egg
37
+
38
+ # Installer logs
39
+ pip-log.txt
40
+ pip-delete-this-directory.txt
41
+ pip-selfcheck.json
42
+
43
+ # Unit test / coverage reports
44
+ htmlcov/
45
+ .tox/
46
+ .coverage
47
+ .cache
48
+ nosetests.xml
49
+ coverage.xml
50
+
51
+ # Translations
52
+ *.mo
53
+
54
+ # Mr Developer
55
+ .mr.developer.cfg
56
+ .project
57
+ .pydevproject
58
+
59
+ # Rope
60
+ .ropeproject
61
+
62
+ # Django stuff:
63
+ *.log
64
+ *.pot
65
+
66
+ .DS_Store
67
+
68
+ # Sphinx documentation
69
+ docs/_build/
70
+
71
+ # PyCharm
72
+ .idea/
73
+
74
+ # VSCode
75
+ .vscode/
76
+
77
+ # Pyenv
78
+ .python-version
@@ -0,0 +1,171 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "autocfg"
7
+ version = "1.3.0"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
10
+
11
+ [[package]]
12
+ name = "cfg-if"
13
+ version = "1.0.0"
14
+ source = "registry+https://github.com/rust-lang/crates.io-index"
15
+ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
16
+
17
+ [[package]]
18
+ name = "diggity"
19
+ version = "0.1.0"
20
+ dependencies = [
21
+ "pyo3",
22
+ ]
23
+
24
+ [[package]]
25
+ name = "heck"
26
+ version = "0.5.0"
27
+ source = "registry+https://github.com/rust-lang/crates.io-index"
28
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
29
+
30
+ [[package]]
31
+ name = "indoc"
32
+ version = "2.0.5"
33
+ source = "registry+https://github.com/rust-lang/crates.io-index"
34
+ checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
35
+
36
+ [[package]]
37
+ name = "libc"
38
+ version = "0.2.158"
39
+ source = "registry+https://github.com/rust-lang/crates.io-index"
40
+ checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
41
+
42
+ [[package]]
43
+ name = "memoffset"
44
+ version = "0.9.1"
45
+ source = "registry+https://github.com/rust-lang/crates.io-index"
46
+ checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
47
+ dependencies = [
48
+ "autocfg",
49
+ ]
50
+
51
+ [[package]]
52
+ name = "once_cell"
53
+ version = "1.20.0"
54
+ source = "registry+https://github.com/rust-lang/crates.io-index"
55
+ checksum = "33ea5043e58958ee56f3e15a90aee535795cd7dfd319846288d93c5b57d85cbe"
56
+
57
+ [[package]]
58
+ name = "portable-atomic"
59
+ version = "1.7.0"
60
+ source = "registry+https://github.com/rust-lang/crates.io-index"
61
+ checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265"
62
+
63
+ [[package]]
64
+ name = "proc-macro2"
65
+ version = "1.0.86"
66
+ source = "registry+https://github.com/rust-lang/crates.io-index"
67
+ checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
68
+ dependencies = [
69
+ "unicode-ident",
70
+ ]
71
+
72
+ [[package]]
73
+ name = "pyo3"
74
+ version = "0.23.4"
75
+ source = "registry+https://github.com/rust-lang/crates.io-index"
76
+ checksum = "57fe09249128b3173d092de9523eaa75136bf7ba85e0d69eca241c7939c933cc"
77
+ dependencies = [
78
+ "cfg-if",
79
+ "indoc",
80
+ "libc",
81
+ "memoffset",
82
+ "once_cell",
83
+ "portable-atomic",
84
+ "pyo3-build-config",
85
+ "pyo3-ffi",
86
+ "pyo3-macros",
87
+ "unindent",
88
+ ]
89
+
90
+ [[package]]
91
+ name = "pyo3-build-config"
92
+ version = "0.23.4"
93
+ source = "registry+https://github.com/rust-lang/crates.io-index"
94
+ checksum = "1cd3927b5a78757a0d71aa9dff669f903b1eb64b54142a9bd9f757f8fde65fd7"
95
+ dependencies = [
96
+ "once_cell",
97
+ "target-lexicon",
98
+ ]
99
+
100
+ [[package]]
101
+ name = "pyo3-ffi"
102
+ version = "0.23.4"
103
+ source = "registry+https://github.com/rust-lang/crates.io-index"
104
+ checksum = "dab6bb2102bd8f991e7749f130a70d05dd557613e39ed2deeee8e9ca0c4d548d"
105
+ dependencies = [
106
+ "libc",
107
+ "pyo3-build-config",
108
+ ]
109
+
110
+ [[package]]
111
+ name = "pyo3-macros"
112
+ version = "0.23.4"
113
+ source = "registry+https://github.com/rust-lang/crates.io-index"
114
+ checksum = "91871864b353fd5ffcb3f91f2f703a22a9797c91b9ab497b1acac7b07ae509c7"
115
+ dependencies = [
116
+ "proc-macro2",
117
+ "pyo3-macros-backend",
118
+ "quote",
119
+ "syn",
120
+ ]
121
+
122
+ [[package]]
123
+ name = "pyo3-macros-backend"
124
+ version = "0.23.4"
125
+ source = "registry+https://github.com/rust-lang/crates.io-index"
126
+ checksum = "43abc3b80bc20f3facd86cd3c60beed58c3e2aa26213f3cda368de39c60a27e4"
127
+ dependencies = [
128
+ "heck",
129
+ "proc-macro2",
130
+ "pyo3-build-config",
131
+ "quote",
132
+ "syn",
133
+ ]
134
+
135
+ [[package]]
136
+ name = "quote"
137
+ version = "1.0.37"
138
+ source = "registry+https://github.com/rust-lang/crates.io-index"
139
+ checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
140
+ dependencies = [
141
+ "proc-macro2",
142
+ ]
143
+
144
+ [[package]]
145
+ name = "syn"
146
+ version = "2.0.77"
147
+ source = "registry+https://github.com/rust-lang/crates.io-index"
148
+ checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
149
+ dependencies = [
150
+ "proc-macro2",
151
+ "quote",
152
+ "unicode-ident",
153
+ ]
154
+
155
+ [[package]]
156
+ name = "target-lexicon"
157
+ version = "0.12.16"
158
+ source = "registry+https://github.com/rust-lang/crates.io-index"
159
+ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
160
+
161
+ [[package]]
162
+ name = "unicode-ident"
163
+ version = "1.0.13"
164
+ source = "registry+https://github.com/rust-lang/crates.io-index"
165
+ checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
166
+
167
+ [[package]]
168
+ name = "unindent"
169
+ version = "0.2.3"
170
+ source = "registry+https://github.com/rust-lang/crates.io-index"
171
+ checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce"
@@ -0,0 +1,13 @@
1
+ [package]
2
+ name = "diggity"
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 = "diggity"
9
+ crate-type = ["cdylib"]
10
+
11
+ [dependencies]
12
+ pyo3 = { version = "~0.23.4", features = ["extension-module"]}
13
+
diggity-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Andrey Torsunov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
diggity-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,118 @@
1
+ Metadata-Version: 2.4
2
+ Name: diggity
3
+ Version: 0.1.0
4
+ Classifier: Programming Language :: Rust
5
+ Classifier: Programming Language :: Python :: Implementation :: CPython
6
+ License-File: LICENSE
7
+ Summary: A utility library for working with nested objects and handling optional values. Inspired by Ruby `dig` function for safely accessing deeply nested structures
8
+ Author-email: Andrey Torsunov <andrey.torsunov@gmail.com>
9
+ Requires-Python: >=3.8
10
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
11
+ Project-URL: Homepage, https://github.com/gtors/diggity
12
+ Project-URL: Source, https://github.com/gtors/diggity
13
+
14
+ # Diggity
15
+
16
+ **Diggity** is a Python library that provides functionality similar to Ruby's `dig` method.
17
+ It allows you to traverse nested data structures to extract values using a specified path or return a default value when the traversal is unsuccessful.
18
+
19
+ Additionally, it includes `coalesce` and `coalesce_logical` functions for handling optional values and finding the first non-`None` or truthy value in a sequence.
20
+
21
+ ## Features
22
+ - **`dig_path`**: Extract value from nested data structures using dot-separated path.
23
+ - **`dig`**: Extract value from nested data structures using a sequence of keys, indices or attributes provided via `*args`.
24
+ - **`coalesce`**: Returns the first non-`None` value from a sequence of arguments.
25
+ - **`coalesce_logical`**: Returns the first truthy value from a sequence of arguments.
26
+
27
+ ## Installation
28
+
29
+ To install **Diggity**, simply run the following command:
30
+
31
+ ```bash
32
+ pip install diggity
33
+ ```
34
+
35
+ ## Usage
36
+
37
+ ### Extracting Nested Values
38
+
39
+ You can extract values from nested data structures in various ways. Below are some examples.
40
+
41
+ ```python
42
+ import diggity
43
+
44
+ data = {
45
+ "users": [
46
+ {
47
+ "name": "Alice",
48
+ "age": 30,
49
+ "preferences": {
50
+ "languages": ["Python", "Rust", "Go"]
51
+ }
52
+ },
53
+ ]
54
+ }
55
+
56
+ # Extracting a value using a dotted path
57
+ name = diggity.dig_path(data, "users.0.name") # Returns: "Alice"
58
+ # Or
59
+ name = diggity.dig(data, "users", 0, "name") # Also returns: "Alice"
60
+
61
+ # Extracting a non-existing value, returning None
62
+ hobby = diggity.dig_path(data, "users.0.hobby") # Returns: None
63
+ # Or
64
+ hobby = diggity.dig(data, "users", 0, "hobby") # Also returns: None
65
+
66
+ # Providing a default value for a non-existing path
67
+ hobby_with_default = diggity.dig(data, "users", 0, "hobby", default="No hobby specified") # Returns: "No hobby specified"
68
+
69
+ # Using a custom separator
70
+ favorite_language = diggity.dig_path(data, "users:0:preferences:languages:0", sep=":") # Returns: "Python"
71
+ ```
72
+
73
+ ### Handling Optional Values with `coalesce`
74
+
75
+ The `coalesce` function returns the first non-`None` value from a sequence of arguments.
76
+
77
+ ```python
78
+ import diggity
79
+
80
+ # Returns the first non-None value
81
+ result = diggity.coalesce(None, None, 42, None) # Returns: 42
82
+
83
+ # Returns None if all values are None
84
+ result = diggity.coalesce(None, None, None) # Returns: None
85
+
86
+ # Works with mixed types
87
+ result = diggity.coalesce(None, False, 0, "hello") # Returns: False
88
+ ```
89
+
90
+ ### Finding the First Truthy Value with `coalesce_logical`
91
+
92
+ The `coalesce_logical` function returns the first truthy value from a sequence of arguments.
93
+
94
+ ```python
95
+ import diggity
96
+
97
+ # Returns the first truthy value
98
+ result = diggity.coalesce_logical(None, False, 42, 0) # Returns: 42
99
+
100
+ # Returns None if all values are falsy
101
+ result = diggity.coalesce_logical(None, False, 0, "") # Returns: None
102
+
103
+ # Works with mixed types
104
+ result = diggity.coalesce_logical(None, False, "hello", 0) # Returns: "hello"
105
+ ```
106
+
107
+ ## License
108
+
109
+ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
110
+
111
+ ## Contributing
112
+
113
+ Contributions are welcome! Please feel free to submit a pull request or open an issue.
114
+
115
+ ## Acknowledgments
116
+
117
+ This project uses [PyO3](https://pyo3.rs/) to bridge Rust and Python. Special thanks to the contributors of the PyO3 library.
118
+
@@ -0,0 +1,104 @@
1
+ # Diggity
2
+
3
+ **Diggity** is a Python library that provides functionality similar to Ruby's `dig` method.
4
+ It allows you to traverse nested data structures to extract values using a specified path or return a default value when the traversal is unsuccessful.
5
+
6
+ Additionally, it includes `coalesce` and `coalesce_logical` functions for handling optional values and finding the first non-`None` or truthy value in a sequence.
7
+
8
+ ## Features
9
+ - **`dig_path`**: Extract value from nested data structures using dot-separated path.
10
+ - **`dig`**: Extract value from nested data structures using a sequence of keys, indices or attributes provided via `*args`.
11
+ - **`coalesce`**: Returns the first non-`None` value from a sequence of arguments.
12
+ - **`coalesce_logical`**: Returns the first truthy value from a sequence of arguments.
13
+
14
+ ## Installation
15
+
16
+ To install **Diggity**, simply run the following command:
17
+
18
+ ```bash
19
+ pip install diggity
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ ### Extracting Nested Values
25
+
26
+ You can extract values from nested data structures in various ways. Below are some examples.
27
+
28
+ ```python
29
+ import diggity
30
+
31
+ data = {
32
+ "users": [
33
+ {
34
+ "name": "Alice",
35
+ "age": 30,
36
+ "preferences": {
37
+ "languages": ["Python", "Rust", "Go"]
38
+ }
39
+ },
40
+ ]
41
+ }
42
+
43
+ # Extracting a value using a dotted path
44
+ name = diggity.dig_path(data, "users.0.name") # Returns: "Alice"
45
+ # Or
46
+ name = diggity.dig(data, "users", 0, "name") # Also returns: "Alice"
47
+
48
+ # Extracting a non-existing value, returning None
49
+ hobby = diggity.dig_path(data, "users.0.hobby") # Returns: None
50
+ # Or
51
+ hobby = diggity.dig(data, "users", 0, "hobby") # Also returns: None
52
+
53
+ # Providing a default value for a non-existing path
54
+ hobby_with_default = diggity.dig(data, "users", 0, "hobby", default="No hobby specified") # Returns: "No hobby specified"
55
+
56
+ # Using a custom separator
57
+ favorite_language = diggity.dig_path(data, "users:0:preferences:languages:0", sep=":") # Returns: "Python"
58
+ ```
59
+
60
+ ### Handling Optional Values with `coalesce`
61
+
62
+ The `coalesce` function returns the first non-`None` value from a sequence of arguments.
63
+
64
+ ```python
65
+ import diggity
66
+
67
+ # Returns the first non-None value
68
+ result = diggity.coalesce(None, None, 42, None) # Returns: 42
69
+
70
+ # Returns None if all values are None
71
+ result = diggity.coalesce(None, None, None) # Returns: None
72
+
73
+ # Works with mixed types
74
+ result = diggity.coalesce(None, False, 0, "hello") # Returns: False
75
+ ```
76
+
77
+ ### Finding the First Truthy Value with `coalesce_logical`
78
+
79
+ The `coalesce_logical` function returns the first truthy value from a sequence of arguments.
80
+
81
+ ```python
82
+ import diggity
83
+
84
+ # Returns the first truthy value
85
+ result = diggity.coalesce_logical(None, False, 42, 0) # Returns: 42
86
+
87
+ # Returns None if all values are falsy
88
+ result = diggity.coalesce_logical(None, False, 0, "") # Returns: None
89
+
90
+ # Works with mixed types
91
+ result = diggity.coalesce_logical(None, False, "hello", 0) # Returns: "hello"
92
+ ```
93
+
94
+ ## License
95
+
96
+ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
97
+
98
+ ## Contributing
99
+
100
+ Contributions are welcome! Please feel free to submit a pull request or open an issue.
101
+
102
+ ## Acknowledgments
103
+
104
+ This project uses [PyO3](https://pyo3.rs/) to bridge Rust and Python. Special thanks to the contributors of the PyO3 library.
@@ -0,0 +1,45 @@
1
+ [build-system]
2
+ requires = ["maturin>=1.7,<2.0"]
3
+ build-backend = "maturin"
4
+
5
+ [project]
6
+ name = "diggity"
7
+ description = "A utility library for working with nested objects and handling optional values. Inspired by Ruby `dig` function for safely accessing deeply nested structures"
8
+ license = { file = "LICENSE" }
9
+ readme = "README.md"
10
+ authors = [{ name = "Andrey Torsunov", email = "andrey.torsunov@gmail.com" }]
11
+ requires-python = ">=3.8"
12
+ classifiers = [
13
+ "Programming Language :: Rust",
14
+ "Programming Language :: Python :: Implementation :: CPython",
15
+ ]
16
+ dynamic = ["version"]
17
+
18
+ [project.urls]
19
+ Homepage = 'https://github.com/gtors/diggity'
20
+ Source = 'https://github.com/gtors/diggity'
21
+
22
+ [tool.maturin]
23
+ features = ["pyo3/extension-module"]
24
+
25
+ [dependency-groups]
26
+ dev = ["maturin"]
27
+ testing = [
28
+ { include-group = "dev" },
29
+ 'hypothesis',
30
+ 'pytest',
31
+ ]
32
+
33
+ [tool.pytest.ini_options]
34
+ testpaths = 'tests'
35
+ log_format = '%(name)s %(levelname)s: %(message)s'
36
+ filterwarnings = [
37
+ 'error',
38
+ # Python 3.9 and below allowed truncation of float to integers in some
39
+ # cases, by not making this an error we can test for this behaviour
40
+ 'ignore:(.+)Implicit conversion to integers using __int__ is deprecated',
41
+ ]
42
+ xfail_strict = true
43
+
44
+ [tool.uv]
45
+ package = false
@@ -0,0 +1,199 @@
1
+ /// A utility module for working with nested Python objects and handling optional values.
2
+ ///
3
+ /// This module provides functions to extract values from nested structures, handle `None` values,
4
+ /// and find the first truthy/not-None value in a sequence of arguments.
5
+ ///
6
+ /// # Functions
7
+ /// - `dig`: Extracts values from nested structures using a sequence of keys or attributes.
8
+ /// - `dig_path`: Extracts values from nested structures using a path string with a specified separator.
9
+ /// - `coalesce`: Returns the first non-`None` value from a sequence of arguments.
10
+ /// - `coalesce_logical`: Returns the first truthy value from a sequence of arguments.
11
+ ///
12
+ /// # Notes
13
+ /// - The `dig` function is inspired by Ruby's `Hash#dig` and `Array#dig` methods, providing
14
+ /// a safe way to access deeply nested values without raising errors for missing keys or indices
15
+ /// - The `dig` and `dig_path` functions are useful for traversing deeply nested dictionaries, sequences and objects.
16
+ /// - The `coalesce` and `coalesce_logical` functions are helpful for providing fallback values in cases
17
+ /// where `None` or falsy values are present.
18
+ use pyo3::{
19
+ exceptions::PyValueError,
20
+ prelude::*,
21
+ types::{PyAny, PyString, PyTuple},
22
+ };
23
+
24
+ use std::ops::ControlFlow;
25
+
26
+ #[pymodule]
27
+ fn diggity(m: &Bound<'_, PyModule>) -> PyResult<()> {
28
+ m.add_function(wrap_pyfunction!(dig, m)?)?;
29
+ m.add_function(wrap_pyfunction!(dig_path, m)?)?;
30
+ m.add_function(wrap_pyfunction!(coalesce, m)?)?;
31
+ m.add_function(wrap_pyfunction!(coalesce_logical, m)?)?;
32
+ Ok(())
33
+ }
34
+
35
+ /// Returns the first non-`None` value from the provided arguments.
36
+ ///
37
+ /// This function iterates through the given arguments and returns the first one that is not `None`.
38
+ /// If all arguments are `None`, it returns `None`.
39
+ ///
40
+ /// # Arguments
41
+ /// - `*args`: A variable number of arguments to check for the first non-`None` value.
42
+ ///
43
+ /// # Returns
44
+ /// - The first non-`None` value if found.
45
+ /// - `None` if all arguments are `None`.
46
+ ///
47
+ /// # Examples
48
+ /// ```python
49
+ /// assert coalesce(None, None, 42, None) == 42
50
+ /// assert coalesce(None, None, None) is None
51
+ /// ```
52
+ #[pyfunction]
53
+ #[pyo3(signature = (*args))]
54
+ fn coalesce(args: Bound<'_, PyTuple>) -> PyObject {
55
+ args.iter()
56
+ .find(|arg| !arg.is_none())
57
+ .map_or_else(|| args.py().None(), |arg| arg.unbind())
58
+ }
59
+
60
+ /// Returns the first truthy value from the provided arguments.
61
+ ///
62
+ /// This function iterates through the given arguments and returns the first one that evaluates to `True`
63
+ /// in a boolean context. If all arguments are falsy, it returns `None`.
64
+ ///
65
+ /// # Arguments
66
+ /// - `*args`: A variable number of arguments to check for the first truthy value.
67
+ ///
68
+ /// # Returns
69
+ /// - The first truthy value if found.
70
+ /// - `None` if all arguments are falsy.
71
+ ///
72
+ /// # Examples
73
+ /// ```python
74
+ /// assert coalesce_logical(None, False, 42, 0) == 42
75
+ /// assert coalesce_logical(None, False, 0, "") is None
76
+ /// ```
77
+ #[pyfunction]
78
+ #[pyo3(signature = (*args))]
79
+ fn coalesce_logical(args: Bound<'_, PyTuple>) -> PyObject {
80
+ args.iter()
81
+ .find(|arg| arg.is_truthy().unwrap_or(false))
82
+ .map_or_else(|| args.py().None(), |arg| arg.unbind())
83
+ }
84
+
85
+ /// Tries to extract the value from a nested structure
86
+ ///
87
+ /// This function traverses the given object using the keys or attributes provided in `args`.
88
+ /// If the path is not found, it returns `None` or a specified default value.
89
+ ///
90
+ /// # Arguments
91
+ /// - `obj`: The object containing nested structures from which to extract values.
92
+ /// - `*args`: A variable number of keys or attributes to traverse the nested structure.
93
+ /// - `default`: An optional default value to return if the path is not found.
94
+ ///
95
+ /// # Returns
96
+ /// - The value at the specified path if found.
97
+ /// - `None` or the provided default value if the path is not found.
98
+ ///
99
+ /// # Examples
100
+ /// ```python
101
+ /// data = {"a": {"b": {"c": 42}}}
102
+ /// assert dig(data, "a", "b", "c") == 42
103
+ /// assert dig(data, "a", "x", default=0) == 0
104
+ /// ```
105
+ #[pyfunction]
106
+ #[pyo3(signature = (obj, *args, r#default=None))]
107
+ fn dig(
108
+ py: Python,
109
+ obj: Bound<'_, PyAny>,
110
+ args: &Bound<'_, PyTuple>,
111
+ r#default: Option<&Bound<'_, PyAny>>,
112
+ ) -> PyResult<PyObject> {
113
+ let default_value = r#default;
114
+
115
+ if args.is_empty() {
116
+ return Ok(obj.unbind());
117
+ }
118
+
119
+ let value = args.iter().try_fold(obj, |acc, arg| {
120
+ if let Ok(key) = arg.downcast::<PyString>() {
121
+ acc.get_item(key).or_else(|_| acc.getattr(key)).map_or_else(
122
+ |_| ControlFlow::Break(default_value),
123
+ |v| ControlFlow::Continue(v),
124
+ )
125
+ } else {
126
+ acc.get_item(arg).map_or_else(
127
+ |_| ControlFlow::Break(default_value),
128
+ |v| ControlFlow::Continue(v),
129
+ )
130
+ }
131
+ });
132
+ extract_control_flow_value(value, py)
133
+ }
134
+
135
+ /// Tries to extract the value from a nested structure within an object using a specified path.
136
+ ///
137
+ /// This function traverses the given object using the keys or attributes specified in the `path` string,
138
+ /// split by the provided separator. If the path is not found, it returns `None` or a specified default value.
139
+ ///
140
+ /// # Arguments
141
+ /// - `obj`: The object containing nested structures from which to extract values.
142
+ /// - `path`: A string representing the path to the desired value, with keys or attributes separated by `sep`.
143
+ /// - `default`: An optional default value to return if the path is not found.
144
+ /// - `sep`: An optional string to specify the separator used in the path (default is ".").
145
+ ///
146
+ /// # Returns
147
+ /// - The value at the specified path if found.
148
+ /// - `None` or the provided default value if the path is not found.
149
+ ///
150
+ /// # Examples
151
+ /// ```python
152
+ /// data = {"a": {"b": [{"c": 42}]}}
153
+ /// assert dig_path(data, "a.b.0.c") == 42
154
+ /// assert dig_path(data, "a.x.y", default=0) == 0
155
+ /// assert dig_path(data, "a/b/0/c", sep="/") == 42
156
+ /// ``
157
+ #[pyfunction]
158
+ #[pyo3(signature = (obj, path, r#default=None, sep = "."))]
159
+ fn dig_path(
160
+ py: Python,
161
+ obj: Bound<'_, PyAny>,
162
+ path: &str,
163
+ r#default: Option<&Bound<'_, PyAny>>,
164
+ sep: &str,
165
+ ) -> PyResult<PyObject> {
166
+ let default_value = r#default;
167
+
168
+ if path.is_empty() {
169
+ return Ok(obj.unbind());
170
+ }
171
+
172
+ let value = path.split(sep).try_fold(obj, |acc, key| {
173
+ acc.get_item(key)
174
+ .or_else(|_| acc.getattr(key))
175
+ .or_else(|_| {
176
+ let index = key
177
+ .parse::<usize>()
178
+ .map_err(|_| PyValueError::new_err(py.None()))?;
179
+ acc.get_item(index)
180
+ })
181
+ .map_or_else(
182
+ |_| ControlFlow::Break(default_value),
183
+ |v| ControlFlow::Continue(v),
184
+ )
185
+ });
186
+
187
+ extract_control_flow_value(value, py)
188
+ }
189
+
190
+ #[inline]
191
+ fn extract_control_flow_value(
192
+ value: ControlFlow<Option<&Bound<'_, PyAny>>, Bound<'_, PyAny>>,
193
+ py: Python<'_>,
194
+ ) -> PyResult<PyObject> {
195
+ match value {
196
+ ControlFlow::Continue(v) => Ok(v.unbind()),
197
+ ControlFlow::Break(v) => Ok(v.into_pyobject(py)?.unbind()),
198
+ }
199
+ }
@@ -0,0 +1,101 @@
1
+ from diggity import dig, dig_path, coalesce, coalesce_logical
2
+
3
+
4
+ def test_dig():
5
+ # Test nested dictionary
6
+ data = {"a": {"b": [None, None, {"c": 42}]}}
7
+ assert dig(data, "a", "b", 2, "c") == 42
8
+
9
+ # Test missing key with default value
10
+ assert dig(data, "a", "x", default=0) == 0
11
+
12
+ # Test missing key without default value
13
+ assert dig(data, "a", "x") is None
14
+
15
+ # Test empty args
16
+ assert dig(data) == data
17
+
18
+
19
+ def test_dig_path():
20
+ # Test nested dictionary with default separator
21
+ data = [{"a": {"b": {"c": 42}}}]
22
+ assert dig_path(data, "0.a.b.c") == 42
23
+
24
+ # Test nested dictionary with custom separator
25
+ assert dig_path(data, "0/a/b/c", sep="/") == 42
26
+
27
+ # Test missing path with default value
28
+ assert dig_path(data, "0.a.x.y", default=0) == 0
29
+
30
+ # Test missing path without default value
31
+ assert dig_path(data, "0.a.x.y") is None
32
+
33
+ # Test empty path
34
+ assert dig_path(data, "") == data
35
+
36
+
37
+ def test_coalesce():
38
+ # Test with non-None values
39
+ assert coalesce(None, None, 42, None) == 42
40
+
41
+ # Test with all None values
42
+ assert coalesce(None, None, None) is None
43
+
44
+ # Test with mixed values
45
+ assert coalesce(None, False, 0, "hello") == False
46
+
47
+ # Test with no arguments
48
+ assert coalesce() is None
49
+
50
+
51
+ def test_coalesce_logical():
52
+ # Test with truthy values
53
+ assert coalesce_logical(None, False, 42, 0) == 42
54
+
55
+ # Test with all falsy values
56
+ assert coalesce_logical(None, False, 0, "") is None
57
+
58
+ # Test with mixed values
59
+ assert coalesce_logical(None, False, "hello", 0) == "hello"
60
+
61
+ # Test with no arguments
62
+ assert coalesce_logical() is None
63
+
64
+
65
+ # Edge Cases and Error Handling
66
+ def test_dig_edge_cases():
67
+ # Test non-dictionary object
68
+ class TestObject:
69
+ def __init__(self):
70
+ self.a = {"b": 42}
71
+
72
+ obj = TestObject()
73
+ assert dig(obj, "a", "b") == 42
74
+ assert dig({"a": 1}, 123) is None
75
+
76
+
77
+ def test_dig_path_edge_cases():
78
+ # Test invalid path separator
79
+ data = {"a": {"b": {"c": 42}}}
80
+ assert dig_path(data, "a-b-c", sep="-") == 42
81
+
82
+ # Test numeric keys in path
83
+ data = {"a": {0: {"c": 42}}}
84
+ assert dig_path(data, "a.0.c") == 42
85
+ assert dig_path(data, "a.x.y.z") is None
86
+
87
+
88
+ def test_coalesce_edge_cases():
89
+ # Test with empty tuple
90
+ assert coalesce() is None
91
+
92
+ # Test with non-None falsy values
93
+ assert coalesce(0, "", False, None) == 0
94
+
95
+
96
+ def test_coalesce_logical_edge_cases():
97
+ # Test with empty tuple
98
+ assert coalesce_logical() is None
99
+
100
+ # Test with non-truthy values
101
+ assert coalesce_logical(0, "", False, None) is None
diggity-0.1.0/uv.lock ADDED
@@ -0,0 +1,208 @@
1
+ version = 1
2
+ revision = 1
3
+ requires-python = ">=3.8"
4
+ resolution-markers = [
5
+ "python_full_version >= '3.9'",
6
+ "python_full_version < '3.9'",
7
+ ]
8
+
9
+ [[package]]
10
+ name = "attrs"
11
+ version = "25.1.0"
12
+ source = { registry = "https://pypi.org/simple" }
13
+ sdist = { url = "https://files.pythonhosted.org/packages/49/7c/fdf464bcc51d23881d110abd74b512a42b3d5d376a55a831b44c603ae17f/attrs-25.1.0.tar.gz", hash = "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e", size = 810562 }
14
+ wheels = [
15
+ { url = "https://files.pythonhosted.org/packages/fc/30/d4986a882011f9df997a55e6becd864812ccfcd821d64aac8570ee39f719/attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a", size = 63152 },
16
+ ]
17
+
18
+ [[package]]
19
+ name = "colorama"
20
+ version = "0.4.6"
21
+ source = { registry = "https://pypi.org/simple" }
22
+ sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
23
+ wheels = [
24
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
25
+ ]
26
+
27
+ [[package]]
28
+ name = "diggity"
29
+ source = { virtual = "." }
30
+
31
+ [package.dev-dependencies]
32
+ dev = [
33
+ { name = "maturin" },
34
+ ]
35
+ testing = [
36
+ { name = "hypothesis", version = "6.113.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
37
+ { name = "hypothesis", version = "6.127.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
38
+ { name = "maturin" },
39
+ { name = "pytest" },
40
+ ]
41
+
42
+ [package.metadata]
43
+
44
+ [package.metadata.requires-dev]
45
+ dev = [{ name = "maturin" }]
46
+ testing = [
47
+ { name = "hypothesis" },
48
+ { name = "maturin" },
49
+ { name = "pytest" },
50
+ ]
51
+
52
+ [[package]]
53
+ name = "exceptiongroup"
54
+ version = "1.2.2"
55
+ source = { registry = "https://pypi.org/simple" }
56
+ sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 }
57
+ wheels = [
58
+ { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 },
59
+ ]
60
+
61
+ [[package]]
62
+ name = "hypothesis"
63
+ version = "6.113.0"
64
+ source = { registry = "https://pypi.org/simple" }
65
+ resolution-markers = [
66
+ "python_full_version < '3.9'",
67
+ ]
68
+ dependencies = [
69
+ { name = "attrs", marker = "python_full_version < '3.9'" },
70
+ { name = "exceptiongroup", marker = "python_full_version < '3.9'" },
71
+ { name = "sortedcontainers", marker = "python_full_version < '3.9'" },
72
+ ]
73
+ sdist = { url = "https://files.pythonhosted.org/packages/28/32/6513cd7256f38c19a6c8a1d5ce9792bcd35c7f11651989994731f0e97672/hypothesis-6.113.0.tar.gz", hash = "sha256:5556ac66fdf72a4ccd5d237810f7cf6bdcd00534a4485015ef881af26e20f7c7", size = 408897 }
74
+ wheels = [
75
+ { url = "https://files.pythonhosted.org/packages/14/fa/4acb477b86a94571958bd337eae5baf334d21b8c98a04b594d0dad381ba8/hypothesis-6.113.0-py3-none-any.whl", hash = "sha256:d539180eb2bb71ed28a23dfe94e67c851f9b09f3ccc4125afad43f17e32e2bad", size = 469790 },
76
+ ]
77
+
78
+ [[package]]
79
+ name = "hypothesis"
80
+ version = "6.127.9"
81
+ source = { registry = "https://pypi.org/simple" }
82
+ resolution-markers = [
83
+ "python_full_version >= '3.9'",
84
+ ]
85
+ dependencies = [
86
+ { name = "attrs", marker = "python_full_version >= '3.9'" },
87
+ { name = "exceptiongroup", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" },
88
+ { name = "sortedcontainers", marker = "python_full_version >= '3.9'" },
89
+ ]
90
+ sdist = { url = "https://files.pythonhosted.org/packages/79/41/72e528551426dbb5156caf7946be53a0c685ee14b0628694ede55dd4a882/hypothesis-6.127.9.tar.gz", hash = "sha256:e8b065319b53ba62cd194912e6322e435daad862ca34e2da8997529e37edecda", size = 420334 }
91
+ wheels = [
92
+ { url = "https://files.pythonhosted.org/packages/c6/00/d204a616fd127ee7830f649cca860b814331f591995e12aaa3931da10c45/hypothesis-6.127.9-py3-none-any.whl", hash = "sha256:51d54c033cfed4ebdc45c9d45ae890d822546151d8d58f52cbed3f049cf448cf", size = 484014 },
93
+ ]
94
+
95
+ [[package]]
96
+ name = "iniconfig"
97
+ version = "2.0.0"
98
+ source = { registry = "https://pypi.org/simple" }
99
+ sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 }
100
+ wheels = [
101
+ { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
102
+ ]
103
+
104
+ [[package]]
105
+ name = "maturin"
106
+ version = "1.8.2"
107
+ source = { registry = "https://pypi.org/simple" }
108
+ dependencies = [
109
+ { name = "tomli", marker = "python_full_version < '3.11'" },
110
+ ]
111
+ sdist = { url = "https://files.pythonhosted.org/packages/e0/8f/6978427ce3f72b189012e1731d1d2d27b3151caa741666c905320e0a3662/maturin-1.8.2.tar.gz", hash = "sha256:e31abc70f6f93285d6e63d2f4459c079c94c259dd757370482d2d4ceb9ec1fa0", size = 199276 }
112
+ wheels = [
113
+ { url = "https://files.pythonhosted.org/packages/67/7a/8fbcaf8f29e583567b21512aa56012fbe5f3e4293ae18a768f4106d584d5/maturin-1.8.2-py3-none-linux_armv6l.whl", hash = "sha256:174cb81c573c4a74be96b4e4469ac84e543cff75850fe2728a8eebb5f4d7b613", size = 7676631 },
114
+ { url = "https://files.pythonhosted.org/packages/c8/88/e17f71a34d4c99558e33c2c3de2c53c4ec01e3fa1c931ba0a8cdc805ebc5/maturin-1.8.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:63ff7f612da90a26838a9c03aa8a80bab8b4e26f63e3df6ddb0e818394eb0aeb", size = 15126750 },
115
+ { url = "https://files.pythonhosted.org/packages/cb/fa/aab9005b0edaeb04a47cc47b07fa4afa25484d2f72217b276e2a446b795f/maturin-1.8.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c91504b4f05b07d0a9fb47c2a2a39c074328b6bc8f252190240e431f5f7ea8d7", size = 7885398 },
116
+ { url = "https://files.pythonhosted.org/packages/24/59/0f12db41e683d82a48f92ac5499c89faa416036b3c3a7379b71aa1ce0ccb/maturin-1.8.2-py3-none-manylinux_2_12_i686.manylinux2010_i686.musllinux_1_1_i686.whl", hash = "sha256:05e3a2aa9611afa5e1205dfa1434607f9d8e223d613a8a7c85540a159af688c0", size = 7754886 },
117
+ { url = "https://files.pythonhosted.org/packages/03/94/b9cb42cb5706389692b24f4691645e0b980708e46c9f008e89f4bb92a497/maturin-1.8.2-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:b408093e49d6d4ab98066eefd0fac64b01eb7af639e9b3151660c5fa96ce147c", size = 8226047 },
118
+ { url = "https://files.pythonhosted.org/packages/1e/38/63c8198a626407b1cefa37670f9d995616249f541ed9616252895bb2710b/maturin-1.8.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:638c66616f9b10060197c48d9e1eedf444d975699d9cd829138e69014554cda7", size = 7485993 },
119
+ { url = "https://files.pythonhosted.org/packages/d3/f3/8d7308902ab190a71c80bda92f3b72d446067fdf40a4c29d5de8e379f598/maturin-1.8.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:c2001b5c57e0dbf5992be56b93ffa897d4bcd0d6ca3de448e381b621225d4d87", size = 7570380 },
120
+ { url = "https://files.pythonhosted.org/packages/06/49/5458df84167506023b934b71488e75aa4a2f9af005f5659d9915adedca55/maturin-1.8.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:e015a5534aefb568b96a9cc7bc58995b1d90b5e2a44455d79e4f073a88cb0c83", size = 9811532 },
121
+ { url = "https://files.pythonhosted.org/packages/d9/52/deb373d1a046287e6f77146204524adbf70184c5510ed95aab882570c69d/maturin-1.8.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e624f73cb7fbfd8042e8c5cc5c11f58bede23a7931ea3ea9839812f5bd362fc", size = 10910211 },
122
+ { url = "https://files.pythonhosted.org/packages/da/b8/f0475031de5f5328c8b2bbb9b50503a6b0a58b3c5cbe50a656c418ca7435/maturin-1.8.2-py3-none-win32.whl", hash = "sha256:4a62268975f98885a04ae9f0df875b304e4f8c1f0d989e8a7ab18e42793126ee", size = 6980868 },
123
+ { url = "https://files.pythonhosted.org/packages/a2/f3/a67264d4ae3bf61a73abf616eba59543e0c8d182a77230703380f1858494/maturin-1.8.2-py3-none-win_amd64.whl", hash = "sha256:b6b29811013056f46a1e0b7f26907ae080028be65102d4fb23fbdf86847fffbd", size = 7886565 },
124
+ { url = "https://files.pythonhosted.org/packages/5e/df/3641646696277249407c923795825176403c208a6553e0fd21b6764038b5/maturin-1.8.2-py3-none-win_arm64.whl", hash = "sha256:4232c2380faf61862d27269c6acf14e1d542c4ba64086a3f5c356d6e5e4823e7", size = 6656754 },
125
+ ]
126
+
127
+ [[package]]
128
+ name = "packaging"
129
+ version = "24.2"
130
+ source = { registry = "https://pypi.org/simple" }
131
+ sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 }
132
+ wheels = [
133
+ { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 },
134
+ ]
135
+
136
+ [[package]]
137
+ name = "pluggy"
138
+ version = "1.5.0"
139
+ source = { registry = "https://pypi.org/simple" }
140
+ sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 }
141
+ wheels = [
142
+ { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 },
143
+ ]
144
+
145
+ [[package]]
146
+ name = "pytest"
147
+ version = "8.3.5"
148
+ source = { registry = "https://pypi.org/simple" }
149
+ dependencies = [
150
+ { name = "colorama", marker = "sys_platform == 'win32'" },
151
+ { name = "exceptiongroup", marker = "python_full_version < '3.11'" },
152
+ { name = "iniconfig" },
153
+ { name = "packaging" },
154
+ { name = "pluggy" },
155
+ { name = "tomli", marker = "python_full_version < '3.11'" },
156
+ ]
157
+ sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 }
158
+ wheels = [
159
+ { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 },
160
+ ]
161
+
162
+ [[package]]
163
+ name = "sortedcontainers"
164
+ version = "2.4.0"
165
+ source = { registry = "https://pypi.org/simple" }
166
+ sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594 }
167
+ wheels = [
168
+ { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575 },
169
+ ]
170
+
171
+ [[package]]
172
+ name = "tomli"
173
+ version = "2.2.1"
174
+ source = { registry = "https://pypi.org/simple" }
175
+ sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 }
176
+ wheels = [
177
+ { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 },
178
+ { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 },
179
+ { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 },
180
+ { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 },
181
+ { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 },
182
+ { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 },
183
+ { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 },
184
+ { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 },
185
+ { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 },
186
+ { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 },
187
+ { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 },
188
+ { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 },
189
+ { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 },
190
+ { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 },
191
+ { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 },
192
+ { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 },
193
+ { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 },
194
+ { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 },
195
+ { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 },
196
+ { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 },
197
+ { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 },
198
+ { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 },
199
+ { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 },
200
+ { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 },
201
+ { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 },
202
+ { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 },
203
+ { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 },
204
+ { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 },
205
+ { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 },
206
+ { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 },
207
+ { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 },
208
+ ]