collection-query 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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ivan R
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.
@@ -0,0 +1,287 @@
1
+ Metadata-Version: 2.4
2
+ Name: collection-query
3
+ Version: 0.1.0
4
+ Summary: Query through your collections like through a database
5
+ Keywords: query,filter,collection,list,django-orm,lookup
6
+ Author: Ivan R
7
+ Author-email: Ivan R <vanyaryanichev@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Requires-Python: >=3.8
22
+ Project-URL: Homepage, https://github.com/vanya1301/collection_query
23
+ Project-URL: Repository, https://github.com/vanya1301/collection_query
24
+ Project-URL: Issues, https://github.com/vanya1301/collection_query/issues
25
+ Description-Content-Type: text/markdown
26
+
27
+ # Collection Query
28
+
29
+ A Python library that provides Django-style query syntax for filtering and navigating through large JSON payloads serialized into Python collection objects (lists, dictionaries, etc.).
30
+
31
+ ## Problem Solved
32
+
33
+ When working with large JSON payloads or complex nested data structures in Python, extracting specific data often requires writing verbose loops, list comprehensions, or deeply nested dictionary access patterns. This becomes unwieldy and error-prone as the data complexity grows.
34
+
35
+ **Traditional approach:**
36
+ ```python
37
+ # Find all users with first_name "Blane" or "Samara" with ID < 5
38
+ results = []
39
+ for item in data:
40
+ if item.get("first_name") in ["Blane", "Samara"] and item.get("id", 0) < 5:
41
+ results.append(item)
42
+ ```
43
+
44
+ **With Collection Query:**
45
+ ```python
46
+ from collection_query import ListQuery
47
+
48
+ results = ListQuery(data).filter(
49
+ first_name__in=["Blane", "Samara"],
50
+ id__lt=5
51
+ )
52
+ ```
53
+
54
+ ## Features
55
+
56
+ - **Django-style query syntax**: Use familiar field lookups like `field__in`, `field__gt`, `field__contains`
57
+ - **Method chaining**: Chain multiple filter/exclude operations for complex queries
58
+ - **Nested field access**: Navigate nested dictionaries using double underscore notation
59
+ - **Extensible**: Easy to add custom field lookup operations
60
+ - **Zero dependencies**: Pure Python, no external runtime dependencies
61
+
62
+ ## Available Field Lookups
63
+
64
+ | Lookup | Description | Example |
65
+ |--------|-------------|---------|
66
+ | `in` | Value is in a list | `first_name__in=["Blane", "Samara"]` |
67
+ | `not` | Value is not equal to | `car_make__not="Land Rover"` |
68
+ | `in_range` | Value is in a range | `id__in_range=range(1, 5)` |
69
+ | `lt` | Less than | `id__lt=5` |
70
+ | `lte` | Less than or equal to | `id__lte=5` |
71
+ | `gt` | Greater than | `id__gt=3` |
72
+ | `gte` | Greater than or equal to | `id__gte=3` |
73
+ | `startswith` | String starts with | `email__startswith="sblackley"` |
74
+ | `endswith` | String ends with | `email__endswith="@imgur.com"` |
75
+ | `contains` | String contains substring | `ip_address__contains=".183."` |
76
+
77
+ ## Installation
78
+
79
+ ### Requirements
80
+
81
+ - Python 3.8 or higher
82
+
83
+ ### Install from PyPI
84
+
85
+ ```bash
86
+ pip install collection-query
87
+ ```
88
+
89
+ Or with [uv](https://github.com/astral-sh/uv):
90
+
91
+ ```bash
92
+ uv add collection-query
93
+ ```
94
+
95
+ Then import and use it:
96
+
97
+ ```python
98
+ from collection_query import ListQuery
99
+ ```
100
+
101
+ ### For Development
102
+
103
+ This project uses [uv](https://github.com/astral-sh/uv) for dependency management.
104
+
105
+ 1. Clone the repository:
106
+ ```bash
107
+ git clone https://github.com/vanya1301/collection_query.git
108
+ cd collection_query
109
+ ```
110
+
111
+ 2. Install dependencies with uv:
112
+ ```bash
113
+ uv sync
114
+ ```
115
+
116
+ 3. Run tests to verify installation:
117
+ ```bash
118
+ python -m unittest discover -v
119
+ ```
120
+
121
+ **Tested Python versions:** 3.8, 3.9, 3.10, 3.11, 3.12, 3.13
122
+
123
+ ## Usage Examples
124
+
125
+ ### Basic Filtering
126
+
127
+ **Traditional approach:**
128
+ ```python
129
+ # Find all users named "Donalt"
130
+ results = [item for item in data if item.get("first_name") == "Donalt"]
131
+ ```
132
+
133
+ **With Collection Query:**
134
+ ```python
135
+ from collection_query import ListQuery
136
+
137
+ results = ListQuery(data).filter(first_name="Donalt")
138
+ ```
139
+
140
+ ### Multiple Conditions (AND logic)
141
+
142
+ **Traditional approach:**
143
+ ```python
144
+ # Find users named "Donalt" who drive a Toyota
145
+ results = [
146
+ item for item in data
147
+ if item.get("first_name") == "Donalt" and item.get("car_make") == "Toyota"
148
+ ]
149
+ ```
150
+
151
+ **With Collection Query:**
152
+ ```python
153
+ results = ListQuery(data).filter(
154
+ first_name="Donalt",
155
+ car_make="Toyota"
156
+ )
157
+ ```
158
+
159
+ ### Using Field Lookups
160
+
161
+ **Traditional approach:**
162
+ ```python
163
+ # Find users with ID less than 5
164
+ results = [item for item in data if item.get("id", 0) < 5]
165
+
166
+ # Find users with ID in specific range
167
+ results = [item for item in data if 1 <= item.get("id", 0) < 5]
168
+
169
+ # Find users whose email contains specific text
170
+ results = [
171
+ item for item in data
172
+ if ".183." in item.get("ip_address", "")
173
+ ]
174
+ ```
175
+
176
+ **With Collection Query:**
177
+ ```python
178
+ results = ListQuery(data).filter(id__lt=5)
179
+ results = ListQuery(data).filter(id__in_range=range(1, 5))
180
+ results = ListQuery(data).filter(ip_address__contains=".183.")
181
+ ```
182
+
183
+ ### Method Chaining
184
+
185
+ **Traditional approach:**
186
+ ```python
187
+ # Find users with ID >= 3, exclude those named "Donalt", then exclude Toyota drivers
188
+ temp1 = [item for item in data if item.get("id", 0) >= 3]
189
+ temp2 = [item for item in temp1 if item.get("first_name") != "Donalt"]
190
+ results = [item for item in temp2 if item.get("car_make") != "Toyota"]
191
+ ```
192
+
193
+ **With Collection Query:**
194
+ ```python
195
+ results = (ListQuery(data)
196
+ .filter(id__gte=3)
197
+ .exclude(first_name="Donalt")
198
+ .exclude(car_make="Toyota")
199
+ )
200
+ ```
201
+
202
+ ### Nested Field Access
203
+
204
+ **Traditional approach:**
205
+ ```python
206
+ # Find items where nested car.country is "Japan"
207
+ results = [
208
+ item for item in nested_dict_data
209
+ if item.get("car", {}).get("country") == "Japan"
210
+ ]
211
+
212
+ # Find items where car.country is in ["Japan", "Germany"]
213
+ results = [
214
+ item for item in nested_dict_data
215
+ if item.get("car", {}).get("country") in ["Japan", "Germany"]
216
+ ]
217
+ ```
218
+
219
+ **With Collection Query:**
220
+ ```python
221
+ results = ListQuery(nested_dict_data).filter(car__country="Japan")
222
+ results = ListQuery(nested_dict_data).filter(car__country__in=["Japan", "Germany"])
223
+ ```
224
+
225
+ ### Combining Filter and Exclude
226
+
227
+ **Traditional approach:**
228
+ ```python
229
+ # Keep items matching condition, but exclude others
230
+ filtered = [item for item in data if item.get("id", 0) >= 3]
231
+ results = [item for item in filtered if item.get("first_name") != "Donalt"]
232
+ ```
233
+
234
+ **With Collection Query:**
235
+ ```python
236
+ results = ListQuery(data).filter(id__gte=3).exclude(first_name="Donalt")
237
+ ```
238
+
239
+ ### Real-World Example: API Response Processing
240
+
241
+ ```python
242
+ from collection_query import ListQuery
243
+
244
+ # Assume this is a large API response
245
+ api_response = {
246
+ "users": [
247
+ {"id": 1, "name": "Alice", "department": {"name": "Engineering", "budget": 100000}},
248
+ {"id": 2, "name": "Bob", "department": {"name": "Sales", "budget": 50000}},
249
+ {"id": 3, "name": "Charlie", "department": {"name": "Engineering", "budget": 120000}},
250
+ # ... hundreds more users
251
+ ]
252
+ }
253
+
254
+ # Find all Engineering users with budget > 100000
255
+ engineers = (ListQuery(api_response["users"])
256
+ .filter(department__name="Engineering")
257
+ .filter(department__budget__gt=100000))
258
+
259
+ for engineer in engineers:
260
+ print(f"{engineer['name']} - Budget: ${engineer['department']['budget']}")
261
+ ```
262
+
263
+ ## Testing
264
+
265
+ Run all tests:
266
+ ```bash
267
+ python -m unittest discover -v
268
+ ```
269
+
270
+ Run specific test file:
271
+ ```bash
272
+ python -m unittest tests.test_field_lookups -v
273
+ ```
274
+
275
+ Run specific test class:
276
+ ```bash
277
+ python -m unittest tests.test_field_lookups.FieldsLookupTestCase -v
278
+ ```
279
+
280
+ Run specific test method:
281
+ ```bash
282
+ python -m unittest tests.test_field_lookups.FieldsLookupTestCase.test_field_lookup_in -v
283
+ ```
284
+
285
+ ## License
286
+
287
+ MIT License - feel free to use in your projects.
@@ -0,0 +1,261 @@
1
+ # Collection Query
2
+
3
+ A Python library that provides Django-style query syntax for filtering and navigating through large JSON payloads serialized into Python collection objects (lists, dictionaries, etc.).
4
+
5
+ ## Problem Solved
6
+
7
+ When working with large JSON payloads or complex nested data structures in Python, extracting specific data often requires writing verbose loops, list comprehensions, or deeply nested dictionary access patterns. This becomes unwieldy and error-prone as the data complexity grows.
8
+
9
+ **Traditional approach:**
10
+ ```python
11
+ # Find all users with first_name "Blane" or "Samara" with ID < 5
12
+ results = []
13
+ for item in data:
14
+ if item.get("first_name") in ["Blane", "Samara"] and item.get("id", 0) < 5:
15
+ results.append(item)
16
+ ```
17
+
18
+ **With Collection Query:**
19
+ ```python
20
+ from collection_query import ListQuery
21
+
22
+ results = ListQuery(data).filter(
23
+ first_name__in=["Blane", "Samara"],
24
+ id__lt=5
25
+ )
26
+ ```
27
+
28
+ ## Features
29
+
30
+ - **Django-style query syntax**: Use familiar field lookups like `field__in`, `field__gt`, `field__contains`
31
+ - **Method chaining**: Chain multiple filter/exclude operations for complex queries
32
+ - **Nested field access**: Navigate nested dictionaries using double underscore notation
33
+ - **Extensible**: Easy to add custom field lookup operations
34
+ - **Zero dependencies**: Pure Python, no external runtime dependencies
35
+
36
+ ## Available Field Lookups
37
+
38
+ | Lookup | Description | Example |
39
+ |--------|-------------|---------|
40
+ | `in` | Value is in a list | `first_name__in=["Blane", "Samara"]` |
41
+ | `not` | Value is not equal to | `car_make__not="Land Rover"` |
42
+ | `in_range` | Value is in a range | `id__in_range=range(1, 5)` |
43
+ | `lt` | Less than | `id__lt=5` |
44
+ | `lte` | Less than or equal to | `id__lte=5` |
45
+ | `gt` | Greater than | `id__gt=3` |
46
+ | `gte` | Greater than or equal to | `id__gte=3` |
47
+ | `startswith` | String starts with | `email__startswith="sblackley"` |
48
+ | `endswith` | String ends with | `email__endswith="@imgur.com"` |
49
+ | `contains` | String contains substring | `ip_address__contains=".183."` |
50
+
51
+ ## Installation
52
+
53
+ ### Requirements
54
+
55
+ - Python 3.8 or higher
56
+
57
+ ### Install from PyPI
58
+
59
+ ```bash
60
+ pip install collection-query
61
+ ```
62
+
63
+ Or with [uv](https://github.com/astral-sh/uv):
64
+
65
+ ```bash
66
+ uv add collection-query
67
+ ```
68
+
69
+ Then import and use it:
70
+
71
+ ```python
72
+ from collection_query import ListQuery
73
+ ```
74
+
75
+ ### For Development
76
+
77
+ This project uses [uv](https://github.com/astral-sh/uv) for dependency management.
78
+
79
+ 1. Clone the repository:
80
+ ```bash
81
+ git clone https://github.com/vanya1301/collection_query.git
82
+ cd collection_query
83
+ ```
84
+
85
+ 2. Install dependencies with uv:
86
+ ```bash
87
+ uv sync
88
+ ```
89
+
90
+ 3. Run tests to verify installation:
91
+ ```bash
92
+ python -m unittest discover -v
93
+ ```
94
+
95
+ **Tested Python versions:** 3.8, 3.9, 3.10, 3.11, 3.12, 3.13
96
+
97
+ ## Usage Examples
98
+
99
+ ### Basic Filtering
100
+
101
+ **Traditional approach:**
102
+ ```python
103
+ # Find all users named "Donalt"
104
+ results = [item for item in data if item.get("first_name") == "Donalt"]
105
+ ```
106
+
107
+ **With Collection Query:**
108
+ ```python
109
+ from collection_query import ListQuery
110
+
111
+ results = ListQuery(data).filter(first_name="Donalt")
112
+ ```
113
+
114
+ ### Multiple Conditions (AND logic)
115
+
116
+ **Traditional approach:**
117
+ ```python
118
+ # Find users named "Donalt" who drive a Toyota
119
+ results = [
120
+ item for item in data
121
+ if item.get("first_name") == "Donalt" and item.get("car_make") == "Toyota"
122
+ ]
123
+ ```
124
+
125
+ **With Collection Query:**
126
+ ```python
127
+ results = ListQuery(data).filter(
128
+ first_name="Donalt",
129
+ car_make="Toyota"
130
+ )
131
+ ```
132
+
133
+ ### Using Field Lookups
134
+
135
+ **Traditional approach:**
136
+ ```python
137
+ # Find users with ID less than 5
138
+ results = [item for item in data if item.get("id", 0) < 5]
139
+
140
+ # Find users with ID in specific range
141
+ results = [item for item in data if 1 <= item.get("id", 0) < 5]
142
+
143
+ # Find users whose email contains specific text
144
+ results = [
145
+ item for item in data
146
+ if ".183." in item.get("ip_address", "")
147
+ ]
148
+ ```
149
+
150
+ **With Collection Query:**
151
+ ```python
152
+ results = ListQuery(data).filter(id__lt=5)
153
+ results = ListQuery(data).filter(id__in_range=range(1, 5))
154
+ results = ListQuery(data).filter(ip_address__contains=".183.")
155
+ ```
156
+
157
+ ### Method Chaining
158
+
159
+ **Traditional approach:**
160
+ ```python
161
+ # Find users with ID >= 3, exclude those named "Donalt", then exclude Toyota drivers
162
+ temp1 = [item for item in data if item.get("id", 0) >= 3]
163
+ temp2 = [item for item in temp1 if item.get("first_name") != "Donalt"]
164
+ results = [item for item in temp2 if item.get("car_make") != "Toyota"]
165
+ ```
166
+
167
+ **With Collection Query:**
168
+ ```python
169
+ results = (ListQuery(data)
170
+ .filter(id__gte=3)
171
+ .exclude(first_name="Donalt")
172
+ .exclude(car_make="Toyota")
173
+ )
174
+ ```
175
+
176
+ ### Nested Field Access
177
+
178
+ **Traditional approach:**
179
+ ```python
180
+ # Find items where nested car.country is "Japan"
181
+ results = [
182
+ item for item in nested_dict_data
183
+ if item.get("car", {}).get("country") == "Japan"
184
+ ]
185
+
186
+ # Find items where car.country is in ["Japan", "Germany"]
187
+ results = [
188
+ item for item in nested_dict_data
189
+ if item.get("car", {}).get("country") in ["Japan", "Germany"]
190
+ ]
191
+ ```
192
+
193
+ **With Collection Query:**
194
+ ```python
195
+ results = ListQuery(nested_dict_data).filter(car__country="Japan")
196
+ results = ListQuery(nested_dict_data).filter(car__country__in=["Japan", "Germany"])
197
+ ```
198
+
199
+ ### Combining Filter and Exclude
200
+
201
+ **Traditional approach:**
202
+ ```python
203
+ # Keep items matching condition, but exclude others
204
+ filtered = [item for item in data if item.get("id", 0) >= 3]
205
+ results = [item for item in filtered if item.get("first_name") != "Donalt"]
206
+ ```
207
+
208
+ **With Collection Query:**
209
+ ```python
210
+ results = ListQuery(data).filter(id__gte=3).exclude(first_name="Donalt")
211
+ ```
212
+
213
+ ### Real-World Example: API Response Processing
214
+
215
+ ```python
216
+ from collection_query import ListQuery
217
+
218
+ # Assume this is a large API response
219
+ api_response = {
220
+ "users": [
221
+ {"id": 1, "name": "Alice", "department": {"name": "Engineering", "budget": 100000}},
222
+ {"id": 2, "name": "Bob", "department": {"name": "Sales", "budget": 50000}},
223
+ {"id": 3, "name": "Charlie", "department": {"name": "Engineering", "budget": 120000}},
224
+ # ... hundreds more users
225
+ ]
226
+ }
227
+
228
+ # Find all Engineering users with budget > 100000
229
+ engineers = (ListQuery(api_response["users"])
230
+ .filter(department__name="Engineering")
231
+ .filter(department__budget__gt=100000))
232
+
233
+ for engineer in engineers:
234
+ print(f"{engineer['name']} - Budget: ${engineer['department']['budget']}")
235
+ ```
236
+
237
+ ## Testing
238
+
239
+ Run all tests:
240
+ ```bash
241
+ python -m unittest discover -v
242
+ ```
243
+
244
+ Run specific test file:
245
+ ```bash
246
+ python -m unittest tests.test_field_lookups -v
247
+ ```
248
+
249
+ Run specific test class:
250
+ ```bash
251
+ python -m unittest tests.test_field_lookups.FieldsLookupTestCase -v
252
+ ```
253
+
254
+ Run specific test method:
255
+ ```bash
256
+ python -m unittest tests.test_field_lookups.FieldsLookupTestCase.test_field_lookup_in -v
257
+ ```
258
+
259
+ ## License
260
+
261
+ MIT License - feel free to use in your projects.
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = ["uv_build>=0.9.0,<0.12.0"]
3
+ build-backend = "uv_build"
4
+
5
+ [project]
6
+ name = "collection-query"
7
+ version = "0.1.0"
8
+ description = "Query through your collections like through a database"
9
+ authors = [{ name = "Ivan R", email = "vanyaryanichev@gmail.com" }]
10
+ requires-python = ">=3.8"
11
+ readme = "README.md"
12
+ license = "MIT"
13
+ license-files = ["LICENSE"]
14
+ keywords = ["query", "filter", "collection", "list", "django-orm", "lookup"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Developers",
18
+ "Operating System :: OS Independent",
19
+ "Topic :: Software Development :: Libraries :: Python Modules",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.8",
22
+ "Programming Language :: Python :: 3.9",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Programming Language :: Python :: 3.13",
27
+ ]
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/vanya1301/collection_query"
31
+ Repository = "https://github.com/vanya1301/collection_query"
32
+ Issues = "https://github.com/vanya1301/collection_query/issues"
@@ -0,0 +1,6 @@
1
+ from __future__ import annotations
2
+
3
+ from collection_query.field_lookups import FieldLookups
4
+ from collection_query.list_query import ListQuery
5
+
6
+ __all__ = ["ListQuery", "FieldLookups"]
@@ -0,0 +1,43 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ class FieldLookups:
5
+ @staticmethod
6
+ def _in(item, value: list) -> bool:
7
+ return item in value
8
+
9
+ @staticmethod
10
+ def _not(item, value) -> bool:
11
+ return item != value
12
+
13
+ @staticmethod
14
+ def _in_range(item, value: range) -> bool:
15
+ return item in value
16
+
17
+ @staticmethod
18
+ def _lt(item, value) -> bool:
19
+ return item < value
20
+
21
+ @staticmethod
22
+ def _lte(item, value) -> bool:
23
+ return item <= value
24
+
25
+ @staticmethod
26
+ def _gt(item, value) -> bool:
27
+ return item > value
28
+
29
+ @staticmethod
30
+ def _gte(item, value) -> bool:
31
+ return item >= value
32
+
33
+ @staticmethod
34
+ def _startswith(item: str, value: str) -> bool:
35
+ return item.startswith(value)
36
+
37
+ @staticmethod
38
+ def _endswith(item: str, value: str) -> bool:
39
+ return item.endswith(value)
40
+
41
+ @staticmethod
42
+ def _contains(item: str, value: str) -> bool:
43
+ return value in item
@@ -0,0 +1,39 @@
1
+ from __future__ import annotations
2
+
3
+ from collection_query.field_lookups import FieldLookups
4
+
5
+
6
+ class ListQuery(list):
7
+ def filter(self, **kwargs) -> ListQuery:
8
+ return self._delete_condition_matching_items(**kwargs)
9
+
10
+ def exclude(self, **kwargs) -> ListQuery:
11
+ return self._delete_condition_matching_items(True, **kwargs)
12
+
13
+ def _delete_condition_matching_items(self, exclude=False, **kwargs) -> ListQuery:
14
+ if not kwargs: # no conditions: nothing to match, leave collection untouched
15
+ return self
16
+ for i in range(len(self) - 1, -1, -1): # going in reverse cause we deleting items
17
+ matches = all(self._evaluate_condition(self[i], k, v) for k, v in kwargs.items())
18
+ should_delete = matches if exclude else not matches
19
+ if should_delete:
20
+ del self[i]
21
+ return self
22
+
23
+ def _evaluate_condition(self, item, condition, value) -> bool:
24
+ segments = condition.split("__")
25
+
26
+ # The final segment may be a field lookup (e.g. ``__in``, ``__gt``); the
27
+ # preceding segments are always nested keys to traverse.
28
+ lookup = None
29
+ if len(segments) > 1 and callable(func := getattr(FieldLookups, f"_{segments[-1]}", None)):
30
+ lookup = func
31
+ segments = segments[:-1]
32
+
33
+ for key in segments:
34
+ if isinstance(item, dict) and key in item:
35
+ item = item[key]
36
+ else:
37
+ return False # missing field/branch never matches
38
+
39
+ return lookup(item, value) if lookup else item == value