oplnk-python-utils 1.0.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.
- oplnk_python_utils-1.0.0/LICENSE +21 -0
- oplnk_python_utils-1.0.0/MANIFEST.in +6 -0
- oplnk_python_utils-1.0.0/PKG-INFO +540 -0
- oplnk_python_utils-1.0.0/README.md +497 -0
- oplnk_python_utils-1.0.0/oplnk_python_utils/__init__.py +18 -0
- oplnk_python_utils-1.0.0/oplnk_python_utils/cache/__init__.py +21 -0
- oplnk_python_utils-1.0.0/oplnk_python_utils/cache/cache_utils.py +88 -0
- oplnk_python_utils-1.0.0/oplnk_python_utils/core/__init__.py +13 -0
- oplnk_python_utils-1.0.0/oplnk_python_utils/core/data.py +58 -0
- oplnk_python_utils-1.0.0/oplnk_python_utils/core/parsing.py +114 -0
- oplnk_python_utils-1.0.0/oplnk_python_utils/core/serialization.py +32 -0
- oplnk_python_utils-1.0.0/oplnk_python_utils/query/__init__.py +9 -0
- oplnk_python_utils-1.0.0/oplnk_python_utils/query/query_builder.py +817 -0
- oplnk_python_utils-1.0.0/oplnk_python_utils.egg-info/PKG-INFO +540 -0
- oplnk_python_utils-1.0.0/oplnk_python_utils.egg-info/SOURCES.txt +19 -0
- oplnk_python_utils-1.0.0/oplnk_python_utils.egg-info/dependency_links.txt +1 -0
- oplnk_python_utils-1.0.0/oplnk_python_utils.egg-info/not-zip-safe +1 -0
- oplnk_python_utils-1.0.0/oplnk_python_utils.egg-info/requires.txt +10 -0
- oplnk_python_utils-1.0.0/oplnk_python_utils.egg-info/top_level.txt +1 -0
- oplnk_python_utils-1.0.0/setup.cfg +4 -0
- oplnk_python_utils-1.0.0/setup.py +43 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 OpenLink SpA
|
|
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,540 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: oplnk-python-utils
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Reusable utilities for caching, data manipulation, parsing, and MongoDB query building
|
|
5
|
+
Home-page: https://github.com/openlinkspa/oplnk-python-utils
|
|
6
|
+
Author: OpenLink SpA
|
|
7
|
+
Author-email: contacto@openlink.cl
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Requires-Python: >=3.8
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: pymongo>=3.0.0
|
|
23
|
+
Requires-Dist: redis>=3.0.0
|
|
24
|
+
Requires-Dist: fastapi>=0.65.0
|
|
25
|
+
Requires-Dist: bson>=0.5.0
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest>=6.0; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest-cov>=2.0; extra == "dev"
|
|
29
|
+
Requires-Dist: black>=21.0; extra == "dev"
|
|
30
|
+
Requires-Dist: flake8>=3.8; extra == "dev"
|
|
31
|
+
Dynamic: author
|
|
32
|
+
Dynamic: author-email
|
|
33
|
+
Dynamic: classifier
|
|
34
|
+
Dynamic: description
|
|
35
|
+
Dynamic: description-content-type
|
|
36
|
+
Dynamic: home-page
|
|
37
|
+
Dynamic: license
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
Dynamic: provides-extra
|
|
40
|
+
Dynamic: requires-dist
|
|
41
|
+
Dynamic: requires-python
|
|
42
|
+
Dynamic: summary
|
|
43
|
+
|
|
44
|
+
# OpenLink Python Utils
|
|
45
|
+
|
|
46
|
+
A comprehensive Python package providing reusable utilities for caching, data manipulation, parsing, and MongoDB query building. Developed by OpenLink SpA for use across multiple projects.
|
|
47
|
+
|
|
48
|
+
## Overview
|
|
49
|
+
|
|
50
|
+
This package abstracts common functionality for:
|
|
51
|
+
|
|
52
|
+
- **Caching**: Redis-based caching utilities with serialization support
|
|
53
|
+
- **Query Building**: MongoDB aggregation pipeline builders with advanced filtering, sorting, pagination, and population features
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
|
|
57
|
+
Install from PyPI:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install oplnk-python-utils
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Or install in development mode:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install -e /path/to/oplnk-python-utils
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Add to your `requirements.txt`:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
oplnk-python-utils>=1.0.0
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Core Module
|
|
76
|
+
|
|
77
|
+
The core module contains general-purpose utilities organized by functionality. These are the foundational utilities used by other modules and can be used independently.
|
|
78
|
+
|
|
79
|
+
### Data Utilities (`core.data`)
|
|
80
|
+
|
|
81
|
+
Functions for data structure manipulation and normalization.
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from oplnk_python_utils.core.data import normalize, merge_dicts
|
|
85
|
+
|
|
86
|
+
# Normalize data for consistent processing (useful for caching)
|
|
87
|
+
data = {"z": 1, "a": {"c": 3, "b": 2}}
|
|
88
|
+
normalized = normalize(data)
|
|
89
|
+
# Result: {"a": {"b": 2, "c": 3}, "z": 1} - keys sorted recursively
|
|
90
|
+
|
|
91
|
+
# Merge multiple dictionaries with nested structure support
|
|
92
|
+
result = merge_dicts(
|
|
93
|
+
{"filters": {"name": "John"}},
|
|
94
|
+
{"filters": {"age": 25}},
|
|
95
|
+
{"sort": ["name:asc"]}
|
|
96
|
+
)
|
|
97
|
+
# Result: {"filters": {"name": ["John"], "age": 25}, "sort": ["name:asc"]}
|
|
98
|
+
|
|
99
|
+
# Advanced merging with arrays and conflicts
|
|
100
|
+
dict1 = {"tags": ["python"], "meta": {"version": 1}}
|
|
101
|
+
dict2 = {"tags": ["fastapi"], "meta": {"author": "team"}}
|
|
102
|
+
merged = merge_dicts(dict1, dict2)
|
|
103
|
+
# Result: {"tags": ["python", "fastapi"], "meta": {"version": 1, "author": "team"}}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
#### Data Functions
|
|
107
|
+
|
|
108
|
+
- **`normalize(value)`**: Recursively normalize data structures by sorting dictionary keys
|
|
109
|
+
- **`merge_dicts(*dicts)`**: Intelligently merge multiple dictionaries with nested structure support
|
|
110
|
+
|
|
111
|
+
### Parsing Utilities (`core.parsing`)
|
|
112
|
+
|
|
113
|
+
Functions for parsing query parameters and type conversion.
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from oplnk_python_utils.core.parsing import (
|
|
117
|
+
parse_params_to_dict,
|
|
118
|
+
infer_type,
|
|
119
|
+
parse_key_string_to_list,
|
|
120
|
+
parse_list_to_dict
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Parse nested query parameters (main function)
|
|
124
|
+
params = {
|
|
125
|
+
"filters[name][$containsi]": "john",
|
|
126
|
+
"filters[age][$gte]": "18",
|
|
127
|
+
"pagination[page]": "1",
|
|
128
|
+
"sort[0]": "name:asc",
|
|
129
|
+
"populate[0]": "user"
|
|
130
|
+
}
|
|
131
|
+
parsed = parse_params_to_dict(params)
|
|
132
|
+
# Result: {
|
|
133
|
+
# "filters": {"name": {"$containsi": "john"}, "age": {"$gte": 18}},
|
|
134
|
+
# "pagination": {"page": 1},
|
|
135
|
+
# "sort": ["name:asc"],
|
|
136
|
+
# "populate": ["user"]
|
|
137
|
+
# }
|
|
138
|
+
|
|
139
|
+
# Smart type inference with various formats
|
|
140
|
+
value = infer_type("123") # Returns: 123 (int)
|
|
141
|
+
value = infer_type("12.5") # Returns: 12.5 (float)
|
|
142
|
+
value = infer_type("true") # Returns: True (bool)
|
|
143
|
+
value = infer_type("false") # Returns: False (bool)
|
|
144
|
+
value = infer_type("hello") # Returns: "hello" (str)
|
|
145
|
+
value = infer_type(["1", "2"]) # Returns: [1, 2] (list with converted types)
|
|
146
|
+
|
|
147
|
+
# Lower-level parsing functions
|
|
148
|
+
keys = parse_key_string_to_list("filters[name][$eq]")
|
|
149
|
+
# Result: ["filters", "name", "$eq"]
|
|
150
|
+
|
|
151
|
+
nested_dict = parse_list_to_dict(["filters", "name", "$eq"], "john")
|
|
152
|
+
# Result: {"filters": {"name": {"$eq": "john"}}}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### Parsing Functions
|
|
156
|
+
|
|
157
|
+
- **`parse_params_to_dict(params)`**: Parse FastAPI QueryParams or dict into structured nested dictionary
|
|
158
|
+
- **`infer_type(value)`**: Smart type inference from string values (supports int, float, bool, datetime, lists)
|
|
159
|
+
- **`parse_key_string_to_list(s)`**: Parse bracketed notation strings like `"filters[name][$eq]"`
|
|
160
|
+
- **`parse_list_to_dict(lst, value)`**: Convert key list and value into nested dictionary structure
|
|
161
|
+
|
|
162
|
+
### Serialization Utilities (`core.serialization`)
|
|
163
|
+
|
|
164
|
+
Functions for converting data types to serializable formats.
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from oplnk_python_utils.core.serialization import datetime_serializer
|
|
168
|
+
from datetime import datetime
|
|
169
|
+
|
|
170
|
+
# Serialize datetime objects for JSON/cache storage
|
|
171
|
+
data = {
|
|
172
|
+
"created_at": datetime.now(),
|
|
173
|
+
"users": [
|
|
174
|
+
{"last_login": datetime.now(), "name": "John"},
|
|
175
|
+
{"last_login": datetime.now(), "name": "Jane"}
|
|
176
|
+
],
|
|
177
|
+
"metadata": {
|
|
178
|
+
"processed_at": datetime.now()
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
serialized = datetime_serializer(data)
|
|
183
|
+
# All datetime objects converted to ISO strings recursively
|
|
184
|
+
# Non-datetime values remain unchanged
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
#### Serialization Functions
|
|
188
|
+
|
|
189
|
+
- **`datetime_serializer(data)`**: Recursively convert datetime objects to ISO format strings in any data structure
|
|
190
|
+
|
|
191
|
+
## Cache Module
|
|
192
|
+
|
|
193
|
+
Advanced caching utilities with Redis integration and intelligent cache key management.
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
from oplnk_python_utils.cache import generate_cache_id, get_or_set_cache, invalidate_cache_keys
|
|
197
|
+
|
|
198
|
+
# Generate consistent cache keys from complex data
|
|
199
|
+
cache_key = generate_cache_id({
|
|
200
|
+
"filters": {"name": "John", "status": "active"},
|
|
201
|
+
"sort": ["created_at:desc"]
|
|
202
|
+
})
|
|
203
|
+
# Automatically normalizes data internally and creates MD5 hash
|
|
204
|
+
|
|
205
|
+
# Get from cache or execute function and cache result
|
|
206
|
+
async def expensive_operation(query):
|
|
207
|
+
# Some expensive database operation
|
|
208
|
+
return await db.collection.find(query).to_list()
|
|
209
|
+
|
|
210
|
+
result = await get_or_set_cache(
|
|
211
|
+
cache_key="user_query_abc123",
|
|
212
|
+
fetch_function=expensive_operation,
|
|
213
|
+
query={"status": "active"},
|
|
214
|
+
expiration=3600 # 1 hour
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
# Cache with automatic serialization of datetime objects
|
|
218
|
+
from datetime import datetime
|
|
219
|
+
async def get_user_data():
|
|
220
|
+
return {
|
|
221
|
+
"user": "john",
|
|
222
|
+
"last_login": datetime.now(),
|
|
223
|
+
"preferences": {"theme": "dark"}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
cached_data = await get_or_set_cache(
|
|
227
|
+
cache_key="user_john_data",
|
|
228
|
+
fetch_function=get_user_data,
|
|
229
|
+
expiration=1800
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
# Invalidate multiple cache keys by pattern
|
|
233
|
+
invalidated_count = await invalidate_cache_keys("user_*")
|
|
234
|
+
print(f"Invalidated {invalidated_count} cache entries")
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Cache Functions
|
|
238
|
+
|
|
239
|
+
- **`generate_cache_id(data, prefix="")`**: Generate MD5 hash from normalized data for consistent cache keys
|
|
240
|
+
- **`get_or_set_cache(cache_key, fetch_function, expiration=3600, **kwargs)`\*\*: Get cached result or execute function and cache with automatic datetime serialization
|
|
241
|
+
- **`invalidate_cache_keys(pattern)`**: Delete cache keys matching pattern (supports wildcards)
|
|
242
|
+
|
|
243
|
+
## Query Module
|
|
244
|
+
|
|
245
|
+
The query module provides a MongoDB aggregation pipeline builder with advanced features.
|
|
246
|
+
|
|
247
|
+
### Usage
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
from oplnk_python_utils.query import QueryBuilder
|
|
251
|
+
from pymongo import MongoClient
|
|
252
|
+
|
|
253
|
+
# Initialize MongoDB
|
|
254
|
+
db = MongoClient()["mydb"]
|
|
255
|
+
collection = db["users"]
|
|
256
|
+
|
|
257
|
+
# Create query builder
|
|
258
|
+
query_builder = QueryBuilder(
|
|
259
|
+
collection_prefix="myapp",
|
|
260
|
+
relations=[
|
|
261
|
+
["User", "user_id", "myapp", "users"],
|
|
262
|
+
["Role", "role_ids", "myapp", "roles"]
|
|
263
|
+
]
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
# Build query
|
|
267
|
+
query = {
|
|
268
|
+
"filters": {
|
|
269
|
+
"name": {"$containsi": "john"},
|
|
270
|
+
"age": {"$gte": 18},
|
|
271
|
+
"$or": [
|
|
272
|
+
{"status": {"$eq": "active"}},
|
|
273
|
+
{"last_login": {"$gte": "2023-01-01"}}
|
|
274
|
+
]
|
|
275
|
+
},
|
|
276
|
+
"sort": ["name:asc", "age:desc"],
|
|
277
|
+
"pagination": {"page": 1, "pageSize": 10},
|
|
278
|
+
"populate": ["role"],
|
|
279
|
+
"fields": ["name", "email", "age"]
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
# Method 1: Using parsed query dictionary
|
|
283
|
+
results = query_builder.get_query_response(
|
|
284
|
+
db_collection=collection,
|
|
285
|
+
query=query,
|
|
286
|
+
cache_client=redis_client,
|
|
287
|
+
cache_key_prefix="users",
|
|
288
|
+
use_cache=True
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
# Method 2: Using raw query parameters (like from FastAPI request)
|
|
292
|
+
raw_params = {
|
|
293
|
+
"filters[name][$containsi]": "john",
|
|
294
|
+
"filters[age][$gte]": "18",
|
|
295
|
+
"sort[0]": "name:asc",
|
|
296
|
+
"pagination[page]": "1",
|
|
297
|
+
"pagination[pageSize]": "10"
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
# Option A: Parse manually then use get_query_response
|
|
301
|
+
from oplnk_python_utils.core.parsing import parse_params_to_dict
|
|
302
|
+
parsed_query = parse_params_to_dict(raw_params)
|
|
303
|
+
results = query_builder.get_query_response(
|
|
304
|
+
db_collection=collection,
|
|
305
|
+
query=parsed_query,
|
|
306
|
+
cache_client=redis_client,
|
|
307
|
+
cache_key_prefix="users",
|
|
308
|
+
use_cache=True
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# Option B: Use read_query with automatic parsing (recommended)
|
|
312
|
+
results = query_builder.read_query(
|
|
313
|
+
db_collection=collection,
|
|
314
|
+
params=raw_params, # Will be parsed automatically using core.parsing
|
|
315
|
+
cache_client=redis_client,
|
|
316
|
+
cache_key_prefix="users",
|
|
317
|
+
use_cache=True
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
print(results)
|
|
321
|
+
# {
|
|
322
|
+
# "data": [...],
|
|
323
|
+
# "meta": {"totalCount": 150}
|
|
324
|
+
# }
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### QueryBuilder Methods
|
|
328
|
+
|
|
329
|
+
#### Pipeline Builders
|
|
330
|
+
|
|
331
|
+
- **`build_filter_pipeline(filters)`**: Build MongoDB filter pipeline
|
|
332
|
+
- **`build_sort_pipeline(sort_params)`**: Build sort pipeline
|
|
333
|
+
- **`build_fields_pipeline(fields_params)`**: Build field projection pipeline
|
|
334
|
+
- **`build_distinct_pipeline(distinct_params)`**: Build distinct pipeline
|
|
335
|
+
- **`build_pagination_pipeline(pagination_params)`**: Build pagination pipeline
|
|
336
|
+
- **`build_populate_pipeline(populate_params)`**: Build population pipeline
|
|
337
|
+
|
|
338
|
+
#### Aggregation Builders
|
|
339
|
+
|
|
340
|
+
- **`make_aggregation_query(query, base_pipeline=None)`**: Build complete aggregation pipeline
|
|
341
|
+
- **`make_count_aggregation_query(query, base_pipeline=None)`**: Build count aggregation pipeline
|
|
342
|
+
|
|
343
|
+
#### Query Execution
|
|
344
|
+
|
|
345
|
+
- **`get_query_response(db_collection, query, ...)`**: Execute query with optional caching
|
|
346
|
+
- **`read_query(db_collection, params=None, query=None, ...)`**: Complete query method with parameter parsing
|
|
347
|
+
|
|
348
|
+
#### Core Module Integration
|
|
349
|
+
|
|
350
|
+
The QueryBuilder class leverages utilities from the core module for enhanced functionality:
|
|
351
|
+
|
|
352
|
+
- **Parameter parsing**: Uses `core.parsing.parse_params_to_dict()` for automatic query parameter parsing
|
|
353
|
+
- **Data normalization**: Uses `core.data.normalize()` for consistent cache key generation
|
|
354
|
+
- **Datetime serialization**: Uses `core.serialization.datetime_serializer()` for cache-friendly data
|
|
355
|
+
|
|
356
|
+
See the [Core Module](#core-module) section for detailed documentation of these utilities.
|
|
357
|
+
|
|
358
|
+
### Supported Filter Operators
|
|
359
|
+
|
|
360
|
+
#### Text Search
|
|
361
|
+
|
|
362
|
+
- `$contains`, `$containsi` (case-insensitive)
|
|
363
|
+
- `$notContains`, `$notContainsi`
|
|
364
|
+
- `$startsWith`, `$startsWithi`
|
|
365
|
+
- `$endsWith`, `$endsWithi`
|
|
366
|
+
- `$eqi` (case-insensitive equality), `$nei`
|
|
367
|
+
|
|
368
|
+
#### Comparison
|
|
369
|
+
|
|
370
|
+
- `$eq`, `$ne`, `$lt`, `$lte`, `$gt`, `$gte`
|
|
371
|
+
- `$in`, `$notIn`
|
|
372
|
+
- `$between` (requires array with 2 elements)
|
|
373
|
+
|
|
374
|
+
#### Logical
|
|
375
|
+
|
|
376
|
+
- `$and`, `$or`
|
|
377
|
+
|
|
378
|
+
#### Existence
|
|
379
|
+
|
|
380
|
+
- `$exists`, `$notExists`
|
|
381
|
+
- `$null`, `$notNull`
|
|
382
|
+
|
|
383
|
+
#### Arrays
|
|
384
|
+
|
|
385
|
+
- `$listContains`, `$listNotContains`
|
|
386
|
+
|
|
387
|
+
### Query Structure
|
|
388
|
+
|
|
389
|
+
```python
|
|
390
|
+
query = {
|
|
391
|
+
"filters": {
|
|
392
|
+
# Filter conditions
|
|
393
|
+
"field": {"$operator": "value"},
|
|
394
|
+
"$or": [
|
|
395
|
+
{"field1": {"$eq": "value1"}},
|
|
396
|
+
{"field2": {"$gt": 10}}
|
|
397
|
+
]
|
|
398
|
+
},
|
|
399
|
+
"sort": ["field1:asc", "field2:desc"],
|
|
400
|
+
"pagination": {
|
|
401
|
+
"page": 1,
|
|
402
|
+
"pageSize": 20
|
|
403
|
+
},
|
|
404
|
+
"populate": ["relation1", "relation2"], # or {"relation1": True}
|
|
405
|
+
"fields": ["field1", "field2", "field3"],
|
|
406
|
+
"distinct": ["field1", "field2"]
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
## Integration with Existing CRUD
|
|
411
|
+
|
|
412
|
+
### Before (Original CRUD + Endpoints)
|
|
413
|
+
|
|
414
|
+
```python
|
|
415
|
+
# CRUD Class - 30+ lines of complex query logic
|
|
416
|
+
class CRUD:
|
|
417
|
+
def __init__(self, collection, relations=[]):
|
|
418
|
+
# ... original implementation
|
|
419
|
+
|
|
420
|
+
def read_query(self, query, pipeline=None, pipeline_count=None, use_cache=False):
|
|
421
|
+
# ... 30+ lines of complex caching and query logic
|
|
422
|
+
|
|
423
|
+
# Endpoint - Manual parameter parsing
|
|
424
|
+
from src.utils import parse_params_to_dict
|
|
425
|
+
|
|
426
|
+
@router.get("/query")
|
|
427
|
+
async def get_data(request: Request):
|
|
428
|
+
params = request.query_params
|
|
429
|
+
query = parse_params_to_dict(params) # Manual parsing
|
|
430
|
+
return crud.read_query(query, pipeline=base_pipeline, use_cache=True)
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### After (Using Utils Package)
|
|
434
|
+
|
|
435
|
+
```python
|
|
436
|
+
# CRUD Class - Now super clean!
|
|
437
|
+
from oplnk_python_utils.query import QueryBuilder
|
|
438
|
+
|
|
439
|
+
class CRUD(QueryBuilder):
|
|
440
|
+
def __init__(self, collection, relations=[]):
|
|
441
|
+
self.collection = collection
|
|
442
|
+
self.db = db[self.collection]
|
|
443
|
+
super().__init__(collection_prefix=PREFIX, relations=relations)
|
|
444
|
+
|
|
445
|
+
def read_query(self, params=None, query=None, pipeline=None, pipeline_count=None, use_cache=False):
|
|
446
|
+
# Just 6 lines! All complexity abstracted away
|
|
447
|
+
return super().read_query(
|
|
448
|
+
db_collection=self.db, params=params, query=query,
|
|
449
|
+
pipeline=pipeline, pipeline_count=pipeline_count,
|
|
450
|
+
use_cache=use_cache, cache_client=redis,
|
|
451
|
+
cache_key_prefix=f"app:{self.collection}"
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
# Endpoint - Even simpler with explicit imports!
|
|
455
|
+
from oplnk_python_utils.core.parsing import parse_params_to_dict
|
|
456
|
+
|
|
457
|
+
@router.get("/query")
|
|
458
|
+
async def get_data(request: Request):
|
|
459
|
+
# Option 1: Manual parsing (explicit control)
|
|
460
|
+
query = parse_params_to_dict(request.query_params)
|
|
461
|
+
return crud.read_query(query=query, pipeline=base_pipeline, use_cache=True)
|
|
462
|
+
|
|
463
|
+
# Option 2: Automatic parsing (convenience)
|
|
464
|
+
return crud.read_query(
|
|
465
|
+
params=request.query_params, # Parsed automatically
|
|
466
|
+
pipeline=base_pipeline,
|
|
467
|
+
use_cache=True
|
|
468
|
+
)
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
## Advanced Features
|
|
472
|
+
|
|
473
|
+
### Custom Relation Definitions
|
|
474
|
+
|
|
475
|
+
```python
|
|
476
|
+
relations = [
|
|
477
|
+
# Basic relation: [Type, field_name, prefix, collection_name]
|
|
478
|
+
["User", "user_id", "myapp", "users"],
|
|
479
|
+
|
|
480
|
+
# Array relation
|
|
481
|
+
["Role", "role_ids", "myapp", "roles"],
|
|
482
|
+
|
|
483
|
+
# Custom alias: [Type, field_name, prefix, collection_name, alias]
|
|
484
|
+
["Department", "dept_id", "myapp", "departments", "department"],
|
|
485
|
+
|
|
486
|
+
# Special Users relation (built-in handling)
|
|
487
|
+
["Users", "person", "", ""]
|
|
488
|
+
]
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### Custom Pipelines
|
|
492
|
+
|
|
493
|
+
```python
|
|
494
|
+
# Custom base pipeline
|
|
495
|
+
base_pipeline = [
|
|
496
|
+
{"$match": {"deleted": False}},
|
|
497
|
+
{"$lookup": {...}}
|
|
498
|
+
]
|
|
499
|
+
|
|
500
|
+
results = query_builder.get_query_response(
|
|
501
|
+
db_collection=collection,
|
|
502
|
+
query=query,
|
|
503
|
+
pipeline=base_pipeline, # Custom data pipeline
|
|
504
|
+
pipeline_count=custom_count_pipeline # Custom count pipeline
|
|
505
|
+
)
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### Smart Text Search
|
|
509
|
+
|
|
510
|
+
The query builder automatically handles mixed-type searching:
|
|
511
|
+
|
|
512
|
+
```python
|
|
513
|
+
# Search for "10" will match:
|
|
514
|
+
# - String fields containing "10"
|
|
515
|
+
# - Numeric fields equal to 10
|
|
516
|
+
# - Numeric fields in range 10.0-10.999
|
|
517
|
+
# - Numbers starting with 10 (100, 1000, etc.)
|
|
518
|
+
filters = {
|
|
519
|
+
"amount": {"$containsi": "10"}
|
|
520
|
+
}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
## Dependencies
|
|
524
|
+
|
|
525
|
+
- `pymongo`: MongoDB driver
|
|
526
|
+
- `redis`: Redis client
|
|
527
|
+
- `fastapi`: For HTTP exceptions
|
|
528
|
+
- `bson`: For ObjectId handling
|
|
529
|
+
|
|
530
|
+
## License
|
|
531
|
+
|
|
532
|
+
This package is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
|
533
|
+
|
|
534
|
+
## Contributing
|
|
535
|
+
|
|
536
|
+
This package is developed and maintained by OpenLink SpA. For questions or support, please contact [contacto@openlink.cl](mailto:contacto@openlink.cl).
|
|
537
|
+
|
|
538
|
+
## Repository
|
|
539
|
+
|
|
540
|
+
Source code is available at: [https://github.com/openlinkspa/oplnk-python-utils](https://github.com/openlinkspa/oplnk-python-utils)
|