ul-db-utils 6.0.0.dev3__tar.gz → 6.0.2__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 (100) hide show
  1. {ul_db_utils-6.0.0.dev3/ul_db_utils.egg-info → ul_db_utils-6.0.2}/PKG-INFO +2 -2
  2. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/pyproject.toml +2 -2
  3. ul_db_utils-6.0.2/tests/test_db_search_operators.py +234 -0
  4. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/search/db_search.py +3 -3
  5. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2/ul_db_utils.egg-info}/PKG-INFO +2 -2
  6. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils.egg-info/SOURCES.txt +1 -0
  7. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils.egg-info/requires.txt +1 -1
  8. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/LICENSE +0 -0
  9. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/README.md +0 -0
  10. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/setup.cfg +0 -0
  11. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/__init__.py +0 -0
  12. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/commands/__init__.py +0 -0
  13. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/commands/cmd_action.py +0 -0
  14. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/commands/cmd_docs.py +0 -0
  15. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/commands/cmd_dump.py +0 -0
  16. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/commands/cmd_restore.py +0 -0
  17. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/commands/cmd_waiting.py +0 -0
  18. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/conf.py +0 -0
  19. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/errors/__init__.py +0 -0
  20. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/errors/compare_null_error.py +0 -0
  21. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/errors/db_error.py +0 -0
  22. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/errors/db_filter_error.py +0 -0
  23. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/errors/db_sort_error.py +0 -0
  24. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/errors/deletion_not_allowed.py +0 -0
  25. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/errors/multiple_objects_returned.py +0 -0
  26. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/errors/unknow_field_error.py +0 -0
  27. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/errors/update_column_not_allowed_error.py +0 -0
  28. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/errors/update_not_allowed.py +0 -0
  29. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/main.py +0 -0
  30. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/__init__.py +0 -0
  31. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/api_user.py +0 -0
  32. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/base_api_user_log_model.py +0 -0
  33. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/base_document.py +0 -0
  34. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/base_immutable_model.py +0 -0
  35. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/base_mater_pg_view.py +0 -0
  36. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/base_model.py +0 -0
  37. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/base_undeletable_model.py +0 -0
  38. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/base_undeletable_user_log_model.py +0 -0
  39. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/base_user_log_model.py +0 -0
  40. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/media_storage/__init__.py +0 -0
  41. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/media_storage/media_file.py +0 -0
  42. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/media_storage/media_file_download_link.py +0 -0
  43. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/media_storage/media_file_type.py +0 -0
  44. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/methods/__init__.py +0 -0
  45. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/methods/make_immutable_column.py +0 -0
  46. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/referense_link.py +0 -0
  47. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/__init__.py +0 -0
  48. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/audit_manager.py +0 -0
  49. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/custom_query.py +0 -0
  50. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/db.py +0 -0
  51. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/db_context.py +0 -0
  52. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/mongo_db_modules/__init__.py +0 -0
  53. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/mongo_db_modules/db.py +0 -0
  54. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/mongo_db_modules/db_context.py +0 -0
  55. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/postgres_modules/__init__.py +0 -0
  56. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/postgres_modules/custom_query.py +0 -0
  57. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/postgres_modules/db.py +0 -0
  58. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/postgres_modules/db_context.py +0 -0
  59. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/postgres_modules/transaction_commit.py +0 -0
  60. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/transaction_commit.py +0 -0
  61. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/py.typed +0 -0
  62. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/repository/__init__.py +0 -0
  63. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/repository/abstract_repository.py +0 -0
  64. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/repository/mongoengine_repository.py +0 -0
  65. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/repository/sqlalchemy_repository.py +0 -0
  66. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/search/__init__.py +0 -0
  67. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/search/doc_db_search.py +0 -0
  68. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/search/helpers.py +0 -0
  69. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/__init__.py +0 -0
  70. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/camel_to_snake.py +0 -0
  71. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/__init__.py +0 -0
  72. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_bool.py +0 -0
  73. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_choices.py +0 -0
  74. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_dict_keys.py +0 -0
  75. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_dict_keys_choice.py +0 -0
  76. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_dict_keys_strict.py +0 -0
  77. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_dict_str_keys.py +0 -0
  78. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_dict_upper_keys.py +0 -0
  79. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_float.py +0 -0
  80. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_int.py +0 -0
  81. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_int_positive.py +0 -0
  82. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_len.py +0 -0
  83. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_list.py +0 -0
  84. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_list_of.py +0 -0
  85. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_positive_int_non_zero.py +0 -0
  86. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_set.py +0 -0
  87. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_str.py +0 -0
  88. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_type.py +0 -0
  89. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_url_with_scheme_and_netloc.py +0 -0
  90. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure_db_object_exists.py +0 -0
  91. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/filter_conversion_doc_db.py +0 -0
  92. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/get_model_template.py +0 -0
  93. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/query_soft_delete.py +0 -0
  94. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/remove_duplicated_spaces_of_string.py +0 -0
  95. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/types.py +0 -0
  96. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/waiting_for_mongo.py +0 -0
  97. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/waiting_for_postgres.py +0 -0
  98. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils.egg-info/dependency_links.txt +0 -0
  99. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils.egg-info/entry_points.txt +0 -0
  100. {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ul-db-utils
3
- Version: 6.0.0.dev3
3
+ Version: 6.0.2
4
4
  Summary: UL db utils Python package
5
5
  Author: Unic-lab
6
6
  License: MIT
@@ -22,7 +22,7 @@ Requires-Dist: alembic>=1.18.4
22
22
  Requires-Dist: flask-mongoengine-3>=1.1.0
23
23
  Requires-Dist: redis>=7.4.0
24
24
  Requires-Dist: psycopg2-binary>=2.9.11
25
- Requires-Dist: ul-py-tool>=3.0.2.dev1
25
+ Requires-Dist: ul-py-tool>=3.0.3
26
26
  Provides-Extra: dev
27
27
  Requires-Dist: uv-script>=0.1.9; extra == "dev"
28
28
  Dynamic: license-file
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ul-db-utils"
3
- version = "6.0.0.dev.3"
3
+ version = "6.0.2"
4
4
  description = "UL db utils Python package"
5
5
  requires-python = ">=3.14"
6
6
  readme = "README.md"
@@ -24,7 +24,7 @@ dependencies = [
24
24
  "flask-mongoengine-3 >=1.1.0",
25
25
  "redis >=7.4.0",
26
26
  "psycopg2-binary >=2.9.11",
27
- "ul-py-tool >=3.0.2.dev1",
27
+ "ul-py-tool >=3.0.3",
28
28
  ]
29
29
 
30
30
  [project.optional-dependencies]
@@ -0,0 +1,234 @@
1
+ """
2
+ Tests for ul_db_utils.search.db_search operator introspection and filtering.
3
+
4
+ Covers:
5
+ - Python 3.14 compatibility (no inspect.getargspec usage)
6
+ - Correct argument-count detection for 1-arg, 2-arg, and 3-arg operators
7
+ - create_operation for operators: ==, in, like, has, any, is_null
8
+ - Filter.from_dictionary with nested 'or' / 'and' structures
9
+ - create_filter with DisjunctionFilter (or) and ConjunctionFilter (and)
10
+ """
11
+ import inspect
12
+ import sys
13
+ import pytest
14
+ from unittest.mock import MagicMock, PropertyMock
15
+
16
+ from ul_db_utils.search.db_search import (
17
+ OPERATORS,
18
+ Filter,
19
+ ConjunctionFilter,
20
+ DisjunctionFilter,
21
+ create_operation,
22
+ create_filter,
23
+ )
24
+ from ul_db_utils.errors.compare_null_error import ComparisonToNullError
25
+
26
+
27
+ # ---------------------------------------------------------------------------
28
+ # Helpers
29
+ # ---------------------------------------------------------------------------
30
+
31
+ def _make_model(fields: dict):
32
+ """Return a simple mock that behaves like a SQLAlchemy model class."""
33
+ model = MagicMock()
34
+ for name, value in fields.items():
35
+ setattr(model, name, value)
36
+ model.__name__ = "FakeModel"
37
+ return model
38
+
39
+
40
+ # ---------------------------------------------------------------------------
41
+ # 1. Python 3.14 compatibility — getargspec must NOT be used
42
+ # ---------------------------------------------------------------------------
43
+
44
+ def test_no_getargspec_in_source():
45
+ """db_search.py must not call the removed inspect.getargspec."""
46
+ import ul_db_utils.search.db_search as module
47
+ source = inspect.getsource(module)
48
+ assert "getargspec(" not in source, (
49
+ "inspect.getargspec() call found in db_search.py — it is removed in Python 3.14"
50
+ )
51
+
52
+
53
+ def test_python_version_at_least_3_14():
54
+ """This package targets Python >=3.14; confirm runtime version."""
55
+ assert sys.version_info >= (3, 14), (
56
+ f"Expected Python >=3.14, got {sys.version_info}"
57
+ )
58
+
59
+
60
+ # ---------------------------------------------------------------------------
61
+ # 2. Operator argument-count introspection (the core fix)
62
+ # ---------------------------------------------------------------------------
63
+
64
+ class TestOperatorArgCounts:
65
+ """Ensure every OPERATORS entry is introspectable via inspect.signature."""
66
+
67
+ _one_arg_ops = ['is_null', 'is_not_null']
68
+ _two_arg_ops = [
69
+ '==', 'eq', 'equals', 'equal_to',
70
+ '!=', 'ne', 'neq', 'not_equal_to', 'does_not_equal',
71
+ '>', 'gt', '<', 'lt', '>=', 'ge', 'gte', 'geq',
72
+ '<=', 'le', 'lte', 'leq',
73
+ '<<', '<<=', '>>', '>>=', '<>', '&&',
74
+ 'ilike', 'like', 'not_like', 'in', 'not_in',
75
+ ]
76
+ _three_arg_ops = ['has', 'any']
77
+
78
+ def test_one_arg_operators(self):
79
+ for op in self._one_arg_ops:
80
+ nargs = len(inspect.signature(OPERATORS[op]).parameters)
81
+ assert nargs == 1, f"Operator '{op}' should have 1 arg, got {nargs}"
82
+
83
+ def test_two_arg_operators(self):
84
+ for op in self._two_arg_ops:
85
+ nargs = len(inspect.signature(OPERATORS[op]).parameters)
86
+ assert nargs == 2, f"Operator '{op}' should have 2 args, got {nargs}"
87
+
88
+ def test_three_arg_operators(self):
89
+ for op in self._three_arg_ops:
90
+ nargs = len(inspect.signature(OPERATORS[op]).parameters)
91
+ assert nargs == 3, f"Operator '{op}' should have 3 args, got {nargs}"
92
+
93
+
94
+ # ---------------------------------------------------------------------------
95
+ # 3. create_operation — direct unit tests
96
+ # ---------------------------------------------------------------------------
97
+
98
+ class TestCreateOperation:
99
+
100
+ def test_is_null_operator(self):
101
+ field = MagicMock()
102
+ field.__eq__ = lambda self, other: other is None # noqa: E731
103
+ model = _make_model({'status': field})
104
+ # Should not raise
105
+ result = create_operation(model, 'status', 'is_null', None)
106
+ # is_null uses `f == None`, result is a SQLAlchemy expression mock
107
+ assert result is not None
108
+
109
+ def test_eq_operator(self):
110
+ field = MagicMock()
111
+ model = _make_model({'id': field})
112
+ create_operation(model, 'id', '==', '629c46cc-c017-4a07-8445-4d2690119b69')
113
+ field.__eq__.assert_called_once_with('629c46cc-c017-4a07-8445-4d2690119b69')
114
+
115
+ def test_in_operator(self):
116
+ field = MagicMock()
117
+ model = _make_model({'status': field})
118
+ create_operation(model, 'status', 'in', ['a', 'b'])
119
+ field.in_.assert_called_once_with(['a', 'b'])
120
+
121
+ def test_like_operator(self):
122
+ field = MagicMock()
123
+ model = _make_model({'name': field})
124
+ create_operation(model, 'name', 'like', '%foo%')
125
+ field.like.assert_called_once_with('%foo%')
126
+
127
+ def test_comparison_to_null_raises(self):
128
+ field = MagicMock()
129
+ model = _make_model({'name': field})
130
+ with pytest.raises(ComparisonToNullError):
131
+ create_operation(model, 'name', '==', None)
132
+
133
+ def test_unknown_operator_raises_key_error(self):
134
+ model = _make_model({'name': MagicMock()})
135
+ with pytest.raises(KeyError):
136
+ create_operation(model, 'name', 'totally_unknown_op', 'x')
137
+
138
+
139
+ # ---------------------------------------------------------------------------
140
+ # 4. Filter.from_dictionary — or / and nesting
141
+ # ---------------------------------------------------------------------------
142
+
143
+ class TestFilterFromDictionary:
144
+
145
+ def _model_with_id(self):
146
+ model = MagicMock()
147
+ model.__name__ = "FakeModel"
148
+ model.id = MagicMock()
149
+ # hasattr must return True for 'id'
150
+ type(model).__contains__ = lambda s, k: k == 'id'
151
+ return model
152
+
153
+ def test_simple_filter(self):
154
+ model = self._model_with_id()
155
+ d = {'name': 'id', 'op': '==', 'val': '629c46cc-c017-4a07-8445-4d2690119b69'}
156
+ f = Filter.from_dictionary(model, d)
157
+ assert isinstance(f, Filter)
158
+ assert f.fieldname == 'id'
159
+ assert f.operator == '=='
160
+ assert f.argument == '629c46cc-c017-4a07-8445-4d2690119b69'
161
+
162
+ def test_or_filter_produces_disjunction(self):
163
+ model = self._model_with_id()
164
+ d = {
165
+ 'or': [
166
+ {'name': 'id', 'op': '==', 'val': '629c46cc-c017-4a07-8445-4d2690119b69'},
167
+ ]
168
+ }
169
+ f = Filter.from_dictionary(model, d)
170
+ assert isinstance(f, DisjunctionFilter)
171
+ subfilters = list(f)
172
+ assert len(subfilters) == 1
173
+ assert subfilters[0].fieldname == 'id'
174
+
175
+ def test_and_filter_produces_conjunction(self):
176
+ model = self._model_with_id()
177
+ d = {
178
+ 'and': [
179
+ {'name': 'id', 'op': '==', 'val': 'aaa'},
180
+ {'name': 'id', 'op': '!=', 'val': 'bbb'},
181
+ ]
182
+ }
183
+ f = Filter.from_dictionary(model, d)
184
+ assert isinstance(f, ConjunctionFilter)
185
+ assert len(list(f)) == 2
186
+
187
+ def test_nested_or_in_and(self):
188
+ model = self._model_with_id()
189
+ d = {
190
+ 'or': [
191
+ {'name': 'id', 'op': '==', 'val': 'x'},
192
+ {'name': 'id', 'op': '==', 'val': 'y'},
193
+ ]
194
+ }
195
+ f = Filter.from_dictionary(model, d)
196
+ assert isinstance(f, DisjunctionFilter)
197
+ assert len(list(f)) == 2
198
+
199
+
200
+ # ---------------------------------------------------------------------------
201
+ # 5. create_filter — disjunction and conjunction are passed through SQLAlchemy
202
+ # ---------------------------------------------------------------------------
203
+
204
+ class TestCreateFilter:
205
+
206
+ def _model_with_field(self, field_name='id'):
207
+ model = MagicMock()
208
+ model.__name__ = "FakeModel"
209
+ field = MagicMock()
210
+ setattr(model, field_name, field)
211
+ return model, field
212
+
213
+ def test_create_filter_simple_eq(self):
214
+ model, field = self._model_with_field('id')
215
+ filt = Filter('id', '==', '629c46cc-c017-4a07-8445-4d2690119b69')
216
+ create_filter(model, filt)
217
+ field.__eq__.assert_called_once_with('629c46cc-c017-4a07-8445-4d2690119b69')
218
+
219
+ def test_create_filter_disjunction_does_not_raise(self):
220
+ model, field = self._model_with_field('id')
221
+ f1 = Filter('id', '==', 'aaa')
222
+ f2 = Filter('id', '==', 'bbb')
223
+ disj = DisjunctionFilter(f1, f2)
224
+ # Should not raise; SQLAlchemy or_() wraps the generator
225
+ result = create_filter(model, disj)
226
+ assert result is not None
227
+
228
+ def test_create_filter_conjunction_does_not_raise(self):
229
+ model, field = self._model_with_field('id')
230
+ f1 = Filter('id', '==', 'aaa')
231
+ f2 = Filter('id', 'is_null', None)
232
+ conj = ConjunctionFilter(f1, f2)
233
+ result = create_filter(model, conj)
234
+ assert result is not None
@@ -147,9 +147,9 @@ def _sub_operator(model: Type[Model], argument: Dict[str, Any], fieldname: str)
147
147
  def create_operation(model: Type[Model], fieldname: str, operator: str, argument: Optional[Any]) -> bool:
148
148
  # raises KeyError if operator not in OPERATORS
149
149
  opfunc = OPERATORS[operator]
150
- # In Python 3.0 or later, this should be `inspect.getfullargspec`
151
- # because `inspect.getargspec` is deprecated.
152
- numargs = len(inspect.getargspec(opfunc).args)
150
+ # Use inspect.signature for Python 3.14+ compatibility
151
+ # (inspect.getargspec was removed in Python 3.14).
152
+ numargs = len(inspect.signature(opfunc).parameters)
153
153
  # raises AttributeError if `fieldname` does not exist
154
154
  field = getattr(model, fieldname)
155
155
  # each of these will raise a TypeError if the wrong number of argments
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ul-db-utils
3
- Version: 6.0.0.dev3
3
+ Version: 6.0.2
4
4
  Summary: UL db utils Python package
5
5
  Author: Unic-lab
6
6
  License: MIT
@@ -22,7 +22,7 @@ Requires-Dist: alembic>=1.18.4
22
22
  Requires-Dist: flask-mongoengine-3>=1.1.0
23
23
  Requires-Dist: redis>=7.4.0
24
24
  Requires-Dist: psycopg2-binary>=2.9.11
25
- Requires-Dist: ul-py-tool>=3.0.2.dev1
25
+ Requires-Dist: ul-py-tool>=3.0.3
26
26
  Provides-Extra: dev
27
27
  Requires-Dist: uv-script>=0.1.9; extra == "dev"
28
28
  Dynamic: license-file
@@ -1,6 +1,7 @@
1
1
  LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
+ tests/test_db_search_operators.py
4
5
  ul_db_utils/__init__.py
5
6
  ul_db_utils/conf.py
6
7
  ul_db_utils/main.py
@@ -8,7 +8,7 @@ alembic>=1.18.4
8
8
  flask-mongoengine-3>=1.1.0
9
9
  redis>=7.4.0
10
10
  psycopg2-binary>=2.9.11
11
- ul-py-tool>=3.0.2.dev1
11
+ ul-py-tool>=3.0.3
12
12
 
13
13
  [dev]
14
14
  uv-script>=0.1.9
File without changes
File without changes
File without changes