querymodelling 0.0.16__tar.gz → 0.0.17__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.
Files changed (44) hide show
  1. {querymodelling-0.0.16 → querymodelling-0.0.17}/.github/workflows/run-tests.yml +1 -1
  2. {querymodelling-0.0.16 → querymodelling-0.0.17}/HISTORY.md +7 -0
  3. {querymodelling-0.0.16 → querymodelling-0.0.17}/PKG-INFO +82 -1
  4. querymodelling-0.0.17/README.md +91 -0
  5. {querymodelling-0.0.16 → querymodelling-0.0.17}/querymodelling/_version.py +3 -3
  6. querymodelling-0.0.17/querymodelling/sql_filter.py +808 -0
  7. {querymodelling-0.0.16 → querymodelling-0.0.17}/querymodelling.egg-info/PKG-INFO +82 -1
  8. {querymodelling-0.0.16 → querymodelling-0.0.17}/querymodelling.egg-info/SOURCES.txt +3 -1
  9. {querymodelling-0.0.16 → querymodelling-0.0.17}/querymodelling.egg-info/scm_file_list.json +3 -1
  10. querymodelling-0.0.17/querymodelling.egg-info/scm_version.json +8 -0
  11. querymodelling-0.0.17/tests/sql_filter_test.py +358 -0
  12. querymodelling-0.0.16/README.md +0 -10
  13. querymodelling-0.0.16/querymodelling.egg-info/scm_version.json +0 -8
  14. {querymodelling-0.0.16 → querymodelling-0.0.17}/.github/workflows/run-publish.yml +0 -0
  15. {querymodelling-0.0.16 → querymodelling-0.0.17}/.gitignore +0 -0
  16. {querymodelling-0.0.16 → querymodelling-0.0.17}/.readthedocs.yaml +0 -0
  17. {querymodelling-0.0.16 → querymodelling-0.0.17}/.vscode/extensions.json +0 -0
  18. {querymodelling-0.0.16 → querymodelling-0.0.17}/.vscode/settings.json +0 -0
  19. {querymodelling-0.0.16 → querymodelling-0.0.17}/AUTHORS.rst +0 -0
  20. {querymodelling-0.0.16 → querymodelling-0.0.17}/LICENSE +0 -0
  21. {querymodelling-0.0.16 → querymodelling-0.0.17}/docs/Makefile +0 -0
  22. {querymodelling-0.0.16 → querymodelling-0.0.17}/docs/conf.py +0 -0
  23. {querymodelling-0.0.16 → querymodelling-0.0.17}/docs/index.rst +0 -0
  24. {querymodelling-0.0.16 → querymodelling-0.0.17}/docs/make.bat +0 -0
  25. {querymodelling-0.0.16 → querymodelling-0.0.17}/docs/requirements.txt +0 -0
  26. {querymodelling-0.0.16 → querymodelling-0.0.17}/docs/user/install.rst +0 -0
  27. {querymodelling-0.0.16 → querymodelling-0.0.17}/pyproject.toml +0 -0
  28. {querymodelling-0.0.16 → querymodelling-0.0.17}/querymodelling/__helper__.py +0 -0
  29. {querymodelling-0.0.16 → querymodelling-0.0.17}/querymodelling/__init__.py +0 -0
  30. {querymodelling-0.0.16 → querymodelling-0.0.17}/querymodelling/__version__.py +0 -0
  31. {querymodelling-0.0.16 → querymodelling-0.0.17}/querymodelling/base.py +0 -0
  32. {querymodelling-0.0.16 → querymodelling-0.0.17}/querymodelling/fields.py +0 -0
  33. {querymodelling-0.0.16 → querymodelling-0.0.17}/querymodelling/model.py +0 -0
  34. {querymodelling-0.0.16 → querymodelling-0.0.17}/querymodelling/pydantic.py +0 -0
  35. {querymodelling-0.0.16 → querymodelling-0.0.17}/querymodelling/redis_om.py +0 -0
  36. {querymodelling-0.0.16 → querymodelling-0.0.17}/querymodelling/sql.py +0 -0
  37. {querymodelling-0.0.16 → querymodelling-0.0.17}/querymodelling.egg-info/dependency_links.txt +0 -0
  38. {querymodelling-0.0.16 → querymodelling-0.0.17}/querymodelling.egg-info/requires.txt +0 -0
  39. {querymodelling-0.0.16 → querymodelling-0.0.17}/querymodelling.egg-info/top_level.txt +0 -0
  40. {querymodelling-0.0.16 → querymodelling-0.0.17}/setup.cfg +0 -0
  41. {querymodelling-0.0.16 → querymodelling-0.0.17}/setup.py +0 -0
  42. {querymodelling-0.0.16 → querymodelling-0.0.17}/tests/__init__.py +0 -0
  43. {querymodelling-0.0.16 → querymodelling-0.0.17}/tests/__main__.py +0 -0
  44. {querymodelling-0.0.16 → querymodelling-0.0.17}/tests/basic_test.py +0 -0
@@ -36,7 +36,7 @@ jobs:
36
36
  cache: 'pip'
37
37
  - name: Install dependencies
38
38
  run: |
39
- pip install unittest-xml-reporting
39
+ pip install unittest-xml-reporting pydantic sqlmodel
40
40
  - name: Run tests
41
41
  run: |
42
42
  python tests
@@ -1,6 +1,13 @@
1
1
  Release History
2
2
  ===============
3
3
 
4
+ Unreleased
5
+ ----------
6
+
7
+ - sql_filter: add typed, nested and generator based SQL filter models
8
+ (``SqlFilterModel``) with ``and`` / ``or`` / ``not``, typed operators,
9
+ SQL functions and JSON schema support
10
+
4
11
  0.0.12 (2025-10-27)
5
12
 
6
13
  - sql: add support for filtering boolean fields
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: querymodelling
3
- Version: 0.0.16
3
+ Version: 0.0.17
4
4
  Summary: build consistent api query models for fastapi
5
5
  Author: George Haddad
6
6
  Author-email: George Haddad <georgeh87@live.de>
@@ -224,3 +224,84 @@ At least you will need to have the pydantic package installed. Of course it make
224
224
 
225
225
  ## Example
226
226
 
227
+ ### Typed SQL filter models
228
+
229
+ `SqlFilterModel` generates a nested, fully typed filter model (incl. JSON
230
+ schema) from a model and compiles it into a SQLAlchemy where clause.
231
+
232
+ ```python
233
+ from sqlmodel import SQLModel, Field, select
234
+ from querymodelling.sql_filter import SqlFilterModel
235
+
236
+ class ExampleModel(SQLModel, table=True):
237
+ id: int | None = Field(default=None, primary_key=True)
238
+ name: str
239
+ description: str
240
+
241
+ ExampleFilter = SqlFilterModel(ExampleModel)
242
+
243
+ query = ExampleFilter.model_validate({
244
+ "and": {
245
+ "name": {"contains": "wurst", "startswith": "bump"},
246
+ "description": {"lower": {"contains": "test"}},
247
+ },
248
+ "or": [{"id": {"in": [1, 2]}}, {"name": {"length": {"gt": 3}}}],
249
+ })
250
+ statement = query.apply_sql(select(ExampleModel))
251
+ # or: select(ExampleModel).where(query.to_sql_expression())
252
+ ```
253
+
254
+ * Fields of the filter object are combined with `AND`.
255
+ * `and` / `or` accept an object (its conditions are combined) or a list of
256
+ objects (each object is combined with `AND`, the list with the operator).
257
+ `not` negates an object. Everything can be nested.
258
+ * Operators per type (default): `eq`, `ne`, `is_null`, `in`, `not_in`
259
+ (scalars), `gt`, `gte`, `lt`, `lte`, `between` (numbers, dates, times),
260
+ `contains`, `icontains`, `startswith`, `istartswith`, `endswith`,
261
+ `iendswith`, `like`, `not_like`, `ilike` (strings).
262
+ * SQL functions per type (default): `lower`, `upper`, `trim`, `length`
263
+ (strings), `abs` (numbers), `date`, `year`, `month`, `day`, `hour`,
264
+ `minute` (dates / times). Functions return a filter of their result type,
265
+ e.g. `{"name": {"length": {"gt": 3}}}`.
266
+
267
+ Everything is built from generators and can be extended or restricted:
268
+
269
+ ```python
270
+ from querymodelling.sql_filter import (
271
+ SqlOperator, SqlFunction, default_sql_filter_mapping, for_types, without
272
+ )
273
+
274
+ def parity(annotation):
275
+ yield SqlOperator(
276
+ "even", lambda column, value: (column % 2 == 0) == value,
277
+ value_type=lambda annotation: bool)
278
+
279
+ mapping = default_sql_filter_mapping(name_prefix="Custom")
280
+ mapping.operator_generators = [
281
+ without(generator, "like", "ilike")
282
+ for generator in mapping.operator_generators
283
+ ]
284
+ mapping.add_operators(for_types(parity, int, exclude=(bool,)))
285
+
286
+ ExampleFilter = SqlFilterModel(ExampleModel, mapping=mapping)
287
+ ```
288
+
289
+ * operator generators `(annotation) -> Iterable[SqlOperator]`
290
+ * function generators `(annotation) -> Iterable[SqlFunction]`
291
+ * field generators `(context, model, name, field_info, annotation) ->
292
+ Iterable[SqlFilterField]` (`field_generators=...`, e.g. virtual fields
293
+ based on SQL expressions)
294
+ * logical generators `(model) -> Iterable[SqlLogicalOperator]`
295
+ (`logical_generators=...`)
296
+
297
+ The filter can be used within existing query models as well:
298
+
299
+ ```python
300
+ from querymodelling.pydantic import QueryField
301
+ from querymodelling.sql_filter import sql_filter_expression
302
+
303
+ class ExampleQuery(PageQuery, BaseModel):
304
+ filter: ExampleFilter | None = QueryField(
305
+ sql_filter_expression, default=None)
306
+ ```
307
+
@@ -0,0 +1,91 @@
1
+ # querymodelling
2
+ build consistent api query models for fastapi
3
+
4
+ ## Requirements
5
+ At least you will need to have the pydantic package installed. Of course it make sense to have some kind of service supporting query models like fast api. Currently sql models are implemented - but feel free to create new ones.
6
+
7
+ ## Installation
8
+
9
+ ## Example
10
+
11
+ ### Typed SQL filter models
12
+
13
+ `SqlFilterModel` generates a nested, fully typed filter model (incl. JSON
14
+ schema) from a model and compiles it into a SQLAlchemy where clause.
15
+
16
+ ```python
17
+ from sqlmodel import SQLModel, Field, select
18
+ from querymodelling.sql_filter import SqlFilterModel
19
+
20
+ class ExampleModel(SQLModel, table=True):
21
+ id: int | None = Field(default=None, primary_key=True)
22
+ name: str
23
+ description: str
24
+
25
+ ExampleFilter = SqlFilterModel(ExampleModel)
26
+
27
+ query = ExampleFilter.model_validate({
28
+ "and": {
29
+ "name": {"contains": "wurst", "startswith": "bump"},
30
+ "description": {"lower": {"contains": "test"}},
31
+ },
32
+ "or": [{"id": {"in": [1, 2]}}, {"name": {"length": {"gt": 3}}}],
33
+ })
34
+ statement = query.apply_sql(select(ExampleModel))
35
+ # or: select(ExampleModel).where(query.to_sql_expression())
36
+ ```
37
+
38
+ * Fields of the filter object are combined with `AND`.
39
+ * `and` / `or` accept an object (its conditions are combined) or a list of
40
+ objects (each object is combined with `AND`, the list with the operator).
41
+ `not` negates an object. Everything can be nested.
42
+ * Operators per type (default): `eq`, `ne`, `is_null`, `in`, `not_in`
43
+ (scalars), `gt`, `gte`, `lt`, `lte`, `between` (numbers, dates, times),
44
+ `contains`, `icontains`, `startswith`, `istartswith`, `endswith`,
45
+ `iendswith`, `like`, `not_like`, `ilike` (strings).
46
+ * SQL functions per type (default): `lower`, `upper`, `trim`, `length`
47
+ (strings), `abs` (numbers), `date`, `year`, `month`, `day`, `hour`,
48
+ `minute` (dates / times). Functions return a filter of their result type,
49
+ e.g. `{"name": {"length": {"gt": 3}}}`.
50
+
51
+ Everything is built from generators and can be extended or restricted:
52
+
53
+ ```python
54
+ from querymodelling.sql_filter import (
55
+ SqlOperator, SqlFunction, default_sql_filter_mapping, for_types, without
56
+ )
57
+
58
+ def parity(annotation):
59
+ yield SqlOperator(
60
+ "even", lambda column, value: (column % 2 == 0) == value,
61
+ value_type=lambda annotation: bool)
62
+
63
+ mapping = default_sql_filter_mapping(name_prefix="Custom")
64
+ mapping.operator_generators = [
65
+ without(generator, "like", "ilike")
66
+ for generator in mapping.operator_generators
67
+ ]
68
+ mapping.add_operators(for_types(parity, int, exclude=(bool,)))
69
+
70
+ ExampleFilter = SqlFilterModel(ExampleModel, mapping=mapping)
71
+ ```
72
+
73
+ * operator generators `(annotation) -> Iterable[SqlOperator]`
74
+ * function generators `(annotation) -> Iterable[SqlFunction]`
75
+ * field generators `(context, model, name, field_info, annotation) ->
76
+ Iterable[SqlFilterField]` (`field_generators=...`, e.g. virtual fields
77
+ based on SQL expressions)
78
+ * logical generators `(model) -> Iterable[SqlLogicalOperator]`
79
+ (`logical_generators=...`)
80
+
81
+ The filter can be used within existing query models as well:
82
+
83
+ ```python
84
+ from querymodelling.pydantic import QueryField
85
+ from querymodelling.sql_filter import sql_filter_expression
86
+
87
+ class ExampleQuery(PageQuery, BaseModel):
88
+ filter: ExampleFilter | None = QueryField(
89
+ sql_filter_expression, default=None)
90
+ ```
91
+
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.0.16'
22
- __version_tuple__ = version_tuple = (0, 0, 16)
21
+ __version__ = version = '0.0.17'
22
+ __version_tuple__ = version_tuple = (0, 0, 17)
23
23
 
24
- __commit_id__ = commit_id = 'g1c20fef9c'
24
+ __commit_id__ = commit_id = 'g9261b3e64'