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.
- {ul_db_utils-6.0.0.dev3/ul_db_utils.egg-info → ul_db_utils-6.0.2}/PKG-INFO +2 -2
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/pyproject.toml +2 -2
- ul_db_utils-6.0.2/tests/test_db_search_operators.py +234 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/search/db_search.py +3 -3
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2/ul_db_utils.egg-info}/PKG-INFO +2 -2
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils.egg-info/SOURCES.txt +1 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils.egg-info/requires.txt +1 -1
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/LICENSE +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/README.md +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/setup.cfg +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/__init__.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/commands/__init__.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/commands/cmd_action.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/commands/cmd_docs.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/commands/cmd_dump.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/commands/cmd_restore.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/commands/cmd_waiting.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/conf.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/errors/__init__.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/errors/compare_null_error.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/errors/db_error.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/errors/db_filter_error.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/errors/db_sort_error.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/errors/deletion_not_allowed.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/errors/multiple_objects_returned.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/errors/unknow_field_error.py +0 -0
- {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
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/errors/update_not_allowed.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/main.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/__init__.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/api_user.py +0 -0
- {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
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/base_document.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/base_immutable_model.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/base_mater_pg_view.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/base_model.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/base_undeletable_model.py +0 -0
- {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
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/base_user_log_model.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/media_storage/__init__.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/media_storage/media_file.py +0 -0
- {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
- {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
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/methods/__init__.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/methods/make_immutable_column.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/referense_link.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/__init__.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/audit_manager.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/custom_query.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/db.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/db_context.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/mongo_db_modules/__init__.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/mongo_db_modules/db.py +0 -0
- {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
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/postgres_modules/__init__.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/postgres_modules/custom_query.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/postgres_modules/db.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/postgres_modules/db_context.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/postgres_modules/transaction_commit.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/transaction_commit.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/py.typed +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/repository/__init__.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/repository/abstract_repository.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/repository/mongoengine_repository.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/repository/sqlalchemy_repository.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/search/__init__.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/search/doc_db_search.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/search/helpers.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/__init__.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/camel_to_snake.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/__init__.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_bool.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_choices.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_dict_keys.py +0 -0
- {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
- {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
- {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
- {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
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_float.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_int.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_int_positive.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_len.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_list.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_list_of.py +0 -0
- {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
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_set.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_str.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_type.py +0 -0
- {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
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure_db_object_exists.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/filter_conversion_doc_db.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/get_model_template.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/query_soft_delete.py +0 -0
- {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
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/types.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/waiting_for_mongo.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/waiting_for_postgres.py +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils.egg-info/dependency_links.txt +0 -0
- {ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils.egg-info/entry_points.txt +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
#
|
|
151
|
-
#
|
|
152
|
-
numargs = len(inspect.
|
|
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.
|
|
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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/errors/multiple_objects_returned.py
RENAMED
|
File without changes
|
|
File without changes
|
{ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/errors/update_column_not_allowed_error.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/base_undeletable_user_log_model.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/media_storage/media_file_type.py
RENAMED
|
File without changes
|
|
File without changes
|
{ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/model/methods/make_immutable_column.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/mongo_db_modules/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/mongo_db_modules/db_context.py
RENAMED
|
File without changes
|
{ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/postgres_modules/__init__.py
RENAMED
|
File without changes
|
{ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/postgres_modules/custom_query.py
RENAMED
|
File without changes
|
|
File without changes
|
{ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/modules/postgres_modules/db_context.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/repository/mongoengine_repository.py
RENAMED
|
File without changes
|
{ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/repository/sqlalchemy_repository.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_dict_keys_choice.py
RENAMED
|
File without changes
|
{ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_dict_keys_strict.py
RENAMED
|
File without changes
|
{ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_dict_str_keys.py
RENAMED
|
File without changes
|
{ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_dict_upper_keys.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/ensure/ensure_int_positive.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ul_db_utils-6.0.0.dev3 → ul_db_utils-6.0.2}/ul_db_utils/utils/remove_duplicated_spaces_of_string.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|