nlbone 0.6.0__py3-none-any.whl → 0.6.9__py3-none-any.whl
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.
- nlbone/adapters/__init__.py +1 -0
- nlbone/adapters/auth/keycloak.py +1 -1
- nlbone/adapters/auth/token_provider.py +1 -1
- nlbone/adapters/cache/async_redis.py +18 -8
- nlbone/adapters/cache/memory.py +21 -11
- nlbone/adapters/cache/pubsub_listener.py +3 -0
- nlbone/adapters/cache/redis.py +23 -8
- nlbone/adapters/db/__init__.py +0 -1
- nlbone/adapters/db/postgres/audit.py +14 -11
- nlbone/adapters/db/postgres/query_builder.py +103 -86
- nlbone/adapters/db/redis/client.py +1 -4
- nlbone/adapters/http_clients/__init__.py +2 -2
- nlbone/adapters/http_clients/pricing/__init__.py +1 -1
- nlbone/adapters/http_clients/pricing/pricing_service.py +40 -20
- nlbone/adapters/http_clients/uploadchi/__init__.py +1 -1
- nlbone/adapters/http_clients/uploadchi/uploadchi.py +12 -12
- nlbone/adapters/http_clients/uploadchi/uploadchi_async.py +14 -15
- nlbone/adapters/percolation/__init__.py +1 -1
- nlbone/adapters/percolation/connection.py +2 -1
- nlbone/config/logging.py +54 -24
- nlbone/container.py +14 -9
- nlbone/core/application/base_worker.py +1 -1
- nlbone/core/domain/models.py +4 -2
- nlbone/core/ports/cache.py +25 -9
- nlbone/interfaces/api/dependencies/auth.py +26 -0
- nlbone/interfaces/api/pagination/offset_base.py +14 -12
- nlbone/interfaces/cli/init_db.py +1 -1
- nlbone/interfaces/cli/main.py +6 -5
- nlbone/utils/cache.py +10 -0
- nlbone/utils/cache_keys.py +6 -0
- nlbone/utils/cache_registry.py +5 -2
- nlbone/utils/http.py +1 -1
- nlbone/utils/redactor.py +2 -1
- nlbone/utils/time.py +1 -1
- {nlbone-0.6.0.dist-info → nlbone-0.6.9.dist-info}/METADATA +1 -1
- {nlbone-0.6.0.dist-info → nlbone-0.6.9.dist-info}/RECORD +39 -39
- {nlbone-0.6.0.dist-info → nlbone-0.6.9.dist-info}/WHEEL +0 -0
- {nlbone-0.6.0.dist-info → nlbone-0.6.9.dist-info}/entry_points.txt +0 -0
- {nlbone-0.6.0.dist-info → nlbone-0.6.9.dist-info}/licenses/LICENSE +0 -0
nlbone/adapters/__init__.py
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import nlbone.adapters.db.postgres.audit
|
nlbone/adapters/auth/keycloak.py
CHANGED
|
@@ -75,4 +75,4 @@ class KeycloakAuthService(AuthService):
|
|
|
75
75
|
def client_has_access(self, token: str, permissions: list[str], allowed_clients: set[str] | None = None) -> bool:
|
|
76
76
|
if not self.is_client_token(token, allowed_clients):
|
|
77
77
|
return False
|
|
78
|
-
return self.has_access(token, permissions)
|
|
78
|
+
return self.has_access(token, permissions)
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import json
|
|
3
3
|
import os
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Any, Iterable, Mapping, Optional, Sequence
|
|
5
5
|
|
|
6
6
|
from redis.asyncio import Redis
|
|
7
|
+
|
|
7
8
|
from nlbone.core.ports.cache import AsyncCachePort
|
|
8
9
|
|
|
9
10
|
|
|
10
|
-
def _nsver_key(ns: str) -> str:
|
|
11
|
-
|
|
11
|
+
def _nsver_key(ns: str) -> str:
|
|
12
|
+
return f"nsver:{ns}"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _tag_key(tag: str) -> str:
|
|
16
|
+
return f"tag:{tag}"
|
|
17
|
+
|
|
12
18
|
|
|
13
19
|
class AsyncRedisCache(AsyncCachePort):
|
|
14
20
|
def __init__(self, url: str, *, invalidate_channel: str | None = None):
|
|
@@ -36,7 +42,9 @@ class AsyncRedisCache(AsyncCachePort):
|
|
|
36
42
|
fk = await self._full_key(key)
|
|
37
43
|
return await self._r.get(fk)
|
|
38
44
|
|
|
39
|
-
async def set(
|
|
45
|
+
async def set(
|
|
46
|
+
self, key: str, value: bytes, *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None
|
|
47
|
+
) -> None:
|
|
40
48
|
fk = await self._full_key(key)
|
|
41
49
|
if ttl is None:
|
|
42
50
|
await self._r.set(fk, value)
|
|
@@ -66,8 +74,9 @@ class AsyncRedisCache(AsyncCachePort):
|
|
|
66
74
|
fks = [await self._full_key(k) for k in keys]
|
|
67
75
|
return await self._r.mget(fks)
|
|
68
76
|
|
|
69
|
-
async def mset(
|
|
70
|
-
|
|
77
|
+
async def mset(
|
|
78
|
+
self, items: Mapping[str, bytes], *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None
|
|
79
|
+
) -> None:
|
|
71
80
|
pipe = self._r.pipeline()
|
|
72
81
|
if ttl is None:
|
|
73
82
|
for k, v in items.items():
|
|
@@ -93,8 +102,9 @@ class AsyncRedisCache(AsyncCachePort):
|
|
|
93
102
|
b = await self.get(key)
|
|
94
103
|
return None if b is None else json.loads(b)
|
|
95
104
|
|
|
96
|
-
async def set_json(
|
|
97
|
-
|
|
105
|
+
async def set_json(
|
|
106
|
+
self, key: str, value: Any, *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None
|
|
107
|
+
) -> None:
|
|
98
108
|
await self.set(key, json.dumps(value).encode("utf-8"), ttl=ttl, tags=tags)
|
|
99
109
|
|
|
100
110
|
# -------- invalidation --------
|
nlbone/adapters/cache/memory.py
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import json
|
|
2
|
-
|
|
1
|
+
import json
|
|
2
|
+
import threading
|
|
3
|
+
import time
|
|
4
|
+
from typing import Any, Dict, Iterable, Mapping, Optional, Sequence, Set
|
|
5
|
+
|
|
3
6
|
from nlbone.core.ports.cache import CachePort
|
|
4
7
|
|
|
5
8
|
|
|
@@ -12,7 +15,8 @@ class InMemoryCache(CachePort):
|
|
|
12
15
|
|
|
13
16
|
def _expired(self, key: str) -> bool:
|
|
14
17
|
v = self._data.get(key)
|
|
15
|
-
if not v:
|
|
18
|
+
if not v:
|
|
19
|
+
return True
|
|
16
20
|
_, exp = v
|
|
17
21
|
return exp is not None and time.time() > exp
|
|
18
22
|
|
|
@@ -21,7 +25,8 @@ class InMemoryCache(CachePort):
|
|
|
21
25
|
self._data.pop(key, None)
|
|
22
26
|
|
|
23
27
|
def _attach_tags(self, key: str, tags: Optional[Iterable[str]]) -> None:
|
|
24
|
-
if not tags:
|
|
28
|
+
if not tags:
|
|
29
|
+
return
|
|
25
30
|
for t in tags:
|
|
26
31
|
self._tags.setdefault(t, set()).add(key)
|
|
27
32
|
|
|
@@ -50,17 +55,20 @@ class InMemoryCache(CachePort):
|
|
|
50
55
|
with self._lock:
|
|
51
56
|
self._gc(key)
|
|
52
57
|
v = self._data.get(key)
|
|
53
|
-
if not v:
|
|
58
|
+
if not v:
|
|
59
|
+
return None
|
|
54
60
|
_, exp = v
|
|
55
|
-
if exp is None:
|
|
61
|
+
if exp is None:
|
|
62
|
+
return None
|
|
56
63
|
rem = int(exp - time.time())
|
|
57
64
|
return rem if rem >= 0 else 0
|
|
58
65
|
|
|
59
66
|
def mget(self, keys: Sequence[str]) -> list[Optional[bytes]]:
|
|
60
67
|
return [self.get(k) for k in keys]
|
|
61
68
|
|
|
62
|
-
def mset(
|
|
63
|
-
|
|
69
|
+
def mset(
|
|
70
|
+
self, items: Mapping[str, bytes], *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None
|
|
71
|
+
) -> None:
|
|
64
72
|
for k, v in items.items():
|
|
65
73
|
self.set(k, v, ttl=ttl, tags=tags)
|
|
66
74
|
|
|
@@ -68,8 +76,9 @@ class InMemoryCache(CachePort):
|
|
|
68
76
|
b = self.get(key)
|
|
69
77
|
return None if b is None else json.loads(b)
|
|
70
78
|
|
|
71
|
-
def set_json(
|
|
72
|
-
|
|
79
|
+
def set_json(
|
|
80
|
+
self, key: str, value: Any, *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None
|
|
81
|
+
) -> None:
|
|
73
82
|
self.set(key, json.dumps(value).encode("utf-8"), ttl=ttl, tags=tags)
|
|
74
83
|
|
|
75
84
|
def invalidate_tags(self, tags: Iterable[str]) -> int:
|
|
@@ -91,7 +100,8 @@ class InMemoryCache(CachePort):
|
|
|
91
100
|
def clear_namespace(self, namespace: str) -> int:
|
|
92
101
|
with self._lock:
|
|
93
102
|
keys = [k for k in self._data.keys() if k.startswith(namespace + ":")]
|
|
94
|
-
for k in keys:
|
|
103
|
+
for k in keys:
|
|
104
|
+
self.delete(k)
|
|
95
105
|
return len(keys)
|
|
96
106
|
|
|
97
107
|
def get_or_set(self, key: str, producer, *, ttl: int, tags=None) -> bytes:
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
|
|
2
3
|
import asyncio
|
|
3
4
|
import json
|
|
4
5
|
from typing import Awaitable, Callable, Optional
|
|
6
|
+
|
|
5
7
|
from redis.asyncio import Redis
|
|
6
8
|
|
|
9
|
+
|
|
7
10
|
async def run_cache_invalidation_listener(
|
|
8
11
|
redis: Redis,
|
|
9
12
|
channel: str = "cache:invalidate",
|
nlbone/adapters/cache/redis.py
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import time
|
|
6
|
+
from typing import Any, Iterable, Mapping, Optional, Sequence
|
|
7
|
+
|
|
4
8
|
import redis # redis-py (sync)
|
|
9
|
+
|
|
5
10
|
from nlbone.core.ports.cache import CachePort
|
|
6
11
|
|
|
7
12
|
|
|
13
|
+
def _nsver_key(ns: str) -> str:
|
|
14
|
+
return f"nsver:{ns}"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _tag_key(tag: str) -> str:
|
|
18
|
+
return f"tag:{tag}"
|
|
8
19
|
|
|
9
|
-
def _nsver_key(ns: str) -> str: return f"nsver:{ns}"
|
|
10
|
-
def _tag_key(tag: str) -> str: return f"tag:{tag}"
|
|
11
20
|
|
|
12
21
|
class RedisCache(CachePort):
|
|
13
22
|
def __init__(self, url: str):
|
|
@@ -57,7 +66,9 @@ class RedisCache(CachePort):
|
|
|
57
66
|
fks = [self._full_key(k) for k in keys]
|
|
58
67
|
return self.r.mget(fks)
|
|
59
68
|
|
|
60
|
-
def mset(
|
|
69
|
+
def mset(
|
|
70
|
+
self, items: Mapping[str, bytes], *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None
|
|
71
|
+
) -> None:
|
|
61
72
|
pipe = self.r.pipeline()
|
|
62
73
|
if ttl is None:
|
|
63
74
|
for k, v in items.items():
|
|
@@ -77,7 +88,9 @@ class RedisCache(CachePort):
|
|
|
77
88
|
b = self.get(key)
|
|
78
89
|
return None if b is None else json.loads(b)
|
|
79
90
|
|
|
80
|
-
def set_json(
|
|
91
|
+
def set_json(
|
|
92
|
+
self, key: str, value: Any, *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None
|
|
93
|
+
) -> None:
|
|
81
94
|
self.set(key, json.dumps(value).encode("utf-8"), ttl=ttl, tags=tags)
|
|
82
95
|
|
|
83
96
|
def invalidate_tags(self, tags: Iterable[str]) -> int:
|
|
@@ -109,8 +122,10 @@ class RedisCache(CachePort):
|
|
|
109
122
|
while True:
|
|
110
123
|
cursor, keys = self.r.scan(cursor=cursor, match=pattern, count=1000)
|
|
111
124
|
if keys:
|
|
112
|
-
self.r.delete(*keys)
|
|
113
|
-
|
|
125
|
+
self.r.delete(*keys)
|
|
126
|
+
cnt += len(keys)
|
|
127
|
+
if cursor == 0:
|
|
128
|
+
break
|
|
114
129
|
return cnt
|
|
115
130
|
|
|
116
131
|
def get_or_set(self, key: str, producer, *, ttl: int, tags=None) -> bytes:
|
nlbone/adapters/db/__init__.py
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import uuid
|
|
2
2
|
from datetime import date, datetime
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
from enum import Enum as _Enum
|
|
3
5
|
from typing import Any
|
|
4
|
-
|
|
6
|
+
|
|
7
|
+
from sqlalchemy import event
|
|
8
|
+
from sqlalchemy import inspect as sa_inspect
|
|
5
9
|
from sqlalchemy.orm import Session as SASession
|
|
6
|
-
from enum import Enum as _Enum
|
|
7
|
-
from decimal import Decimal
|
|
8
10
|
|
|
9
11
|
from nlbone.core.domain.models import AuditLog
|
|
10
12
|
from nlbone.utils.context import current_context_dict
|
|
@@ -54,8 +56,11 @@ def _ser(val):
|
|
|
54
56
|
|
|
55
57
|
|
|
56
58
|
def _entity_name(obj: Any) -> str:
|
|
57
|
-
return
|
|
58
|
-
|
|
59
|
+
return (
|
|
60
|
+
getattr(getattr(obj, "__table__", None), "name", None)
|
|
61
|
+
or getattr(obj, "__tablename__", None)
|
|
62
|
+
or obj.__class__.__name__
|
|
63
|
+
)
|
|
59
64
|
|
|
60
65
|
|
|
61
66
|
def _entity_id(obj: Any) -> str:
|
|
@@ -84,13 +89,15 @@ def _changes_for_update(obj: any) -> dict[str, dict[str, any]]:
|
|
|
84
89
|
except KeyError:
|
|
85
90
|
continue
|
|
86
91
|
|
|
87
|
-
hist = state.history
|
|
92
|
+
hist = state.history # History object
|
|
88
93
|
if hist.has_changes():
|
|
89
94
|
old = hist.deleted[0] if hist.deleted else None
|
|
90
95
|
new = hist.added[0] if hist.added else None
|
|
91
96
|
if old != new:
|
|
92
97
|
changes[key] = {"old": _ser(old), "new": _ser(new)}
|
|
93
98
|
return changes
|
|
99
|
+
|
|
100
|
+
|
|
94
101
|
@event.listens_for(SASession, "before_flush")
|
|
95
102
|
def before_flush(session: SASession, flush_context, instances):
|
|
96
103
|
entries = session.info.setdefault("_audit_entries", [])
|
|
@@ -107,11 +114,7 @@ def before_flush(session: SASession, flush_context, instances):
|
|
|
107
114
|
if key in exclude:
|
|
108
115
|
continue
|
|
109
116
|
row[key] = _ser(getattr(obj, key, None))
|
|
110
|
-
entries.append({
|
|
111
|
-
"obj": obj,
|
|
112
|
-
"op": "INSERT",
|
|
113
|
-
"changes": {k: {"old": None, "new": v} for k, v in row.items()}
|
|
114
|
-
})
|
|
117
|
+
entries.append({"obj": obj, "op": "INSERT", "changes": {k: {"old": None, "new": v} for k, v in row.items()}})
|
|
115
118
|
|
|
116
119
|
# UPDATE
|
|
117
120
|
for obj in session.dirty:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from typing import Any, Callable, Optional, Sequence, Type, Union
|
|
2
2
|
|
|
3
|
-
from sqlalchemy import asc, desc, or_
|
|
3
|
+
from sqlalchemy import asc, desc, or_, and_, case, literal
|
|
4
4
|
from sqlalchemy.dialects.postgresql import ENUM as PGEnum
|
|
5
5
|
from sqlalchemy.orm import Query, Session
|
|
6
6
|
from sqlalchemy.orm.interfaces import LoaderOption
|
|
@@ -29,8 +29,15 @@ class _InvalidEnum(Exception):
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def _apply_order(pagination: PaginateRequest, entity, query):
|
|
32
|
+
order_clauses = []
|
|
33
|
+
|
|
34
|
+
include_ids = getattr(pagination, "include_ids", []) or []
|
|
35
|
+
if include_ids and hasattr(entity, "id"):
|
|
36
|
+
id_col = getattr(entity, "id")
|
|
37
|
+
whens = [(id_col == _id, idx) for idx, _id in enumerate(include_ids)]
|
|
38
|
+
order_clauses.append(asc(case(*whens, else_=literal(999_999))))
|
|
39
|
+
|
|
32
40
|
if pagination.sort:
|
|
33
|
-
order_clauses = []
|
|
34
41
|
for sort in pagination.sort:
|
|
35
42
|
field = sort["field"]
|
|
36
43
|
order = sort["order"]
|
|
@@ -42,8 +49,8 @@ def _apply_order(pagination: PaginateRequest, entity, query):
|
|
|
42
49
|
else:
|
|
43
50
|
order_clauses.append(desc(column))
|
|
44
51
|
|
|
45
|
-
|
|
46
|
-
|
|
52
|
+
if order_clauses:
|
|
53
|
+
query = query.order_by(*order_clauses)
|
|
47
54
|
return query
|
|
48
55
|
|
|
49
56
|
|
|
@@ -101,89 +108,100 @@ def _parse_field_and_op(field: str):
|
|
|
101
108
|
|
|
102
109
|
|
|
103
110
|
def _apply_filters(pagination, entity, query):
|
|
104
|
-
if not getattr(pagination, "filters", None):
|
|
111
|
+
if not getattr(pagination, "filters", None) and not getattr(pagination, "include_ids", None):
|
|
105
112
|
return query
|
|
106
113
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if isinstance(
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
114
|
+
predicates = []
|
|
115
|
+
|
|
116
|
+
if getattr(pagination, "filters", None):
|
|
117
|
+
for raw_field, value in pagination.filters.items():
|
|
118
|
+
if value is None or value in NULL_SENTINELS or value == [] or value == {}:
|
|
119
|
+
value = None
|
|
120
|
+
|
|
121
|
+
field, op_hint = _parse_field_and_op(raw_field)
|
|
122
|
+
|
|
123
|
+
if not hasattr(entity, field):
|
|
124
|
+
continue
|
|
125
|
+
|
|
126
|
+
col = getattr(entity, field)
|
|
127
|
+
coltype = getattr(col, "type", None)
|
|
128
|
+
|
|
129
|
+
def coerce(v):
|
|
130
|
+
if v is None:
|
|
131
|
+
return None
|
|
132
|
+
# Enums
|
|
133
|
+
if isinstance(coltype, (SAEnum, PGEnum)):
|
|
134
|
+
return _coerce_enum(coltype, v)
|
|
135
|
+
# Text
|
|
136
|
+
if _is_text_type(coltype):
|
|
137
|
+
return str(v)
|
|
138
|
+
# Numbers
|
|
139
|
+
if isinstance(coltype, (Integer, BigInteger, SmallInteger)):
|
|
140
|
+
return int(v)
|
|
141
|
+
if isinstance(coltype, (Float, Numeric)):
|
|
142
|
+
return float(v)
|
|
143
|
+
# Booleans
|
|
144
|
+
if isinstance(coltype, Boolean):
|
|
145
|
+
if isinstance(v, bool):
|
|
146
|
+
return v
|
|
147
|
+
if isinstance(v, (int, float)):
|
|
148
|
+
return bool(v)
|
|
149
|
+
if isinstance(v, str):
|
|
150
|
+
vl = v.strip().lower()
|
|
151
|
+
if vl in {"true", "1", "yes", "y", "t"}:
|
|
152
|
+
return True
|
|
153
|
+
if vl in {"false", "0", "no", "n", "f"}:
|
|
154
|
+
return False
|
|
155
|
+
return None
|
|
156
|
+
return v
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
def _use_ilike(v) -> bool:
|
|
160
|
+
if op_hint == "ilike":
|
|
161
|
+
return True
|
|
162
|
+
if _is_text_type(coltype) and isinstance(v, str) and _looks_like_wildcard(v):
|
|
142
163
|
return True
|
|
143
|
-
|
|
144
|
-
return False
|
|
145
|
-
return None
|
|
146
|
-
# fallback
|
|
147
|
-
return v
|
|
164
|
+
return False
|
|
148
165
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if op_hint == "ilike":
|
|
153
|
-
return True
|
|
154
|
-
if _is_text_type(coltype) and isinstance(v, str) and _looks_like_wildcard(v):
|
|
155
|
-
return True
|
|
156
|
-
return False
|
|
157
|
-
|
|
158
|
-
if isinstance(value, (list, tuple, set)):
|
|
159
|
-
vals = [v for v in value if v not in (None, "", "null", "None")]
|
|
160
|
-
if not vals:
|
|
161
|
-
continue
|
|
162
|
-
|
|
163
|
-
# if any value signals ilike, apply OR of ilike; else IN / EQs
|
|
164
|
-
if any(_use_ilike(v) for v in vals) and _is_text_type(coltype):
|
|
165
|
-
patterns = [_to_sql_like_pattern(str(v)) for v in vals]
|
|
166
|
-
query = query.filter(or_(*[col.ilike(p) for p in patterns]))
|
|
167
|
-
else:
|
|
168
|
-
coerced = [coerce(v) for v in vals]
|
|
169
|
-
if not coerced:
|
|
166
|
+
if isinstance(value, (list, tuple, set)):
|
|
167
|
+
vals = [v for v in value if v not in (None, "", "null", "None")]
|
|
168
|
+
if not vals:
|
|
170
169
|
continue
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
170
|
+
|
|
171
|
+
if any(_use_ilike(v) for v in vals) and _is_text_type(coltype):
|
|
172
|
+
patterns = [_to_sql_like_pattern(str(v)) for v in vals]
|
|
173
|
+
predicates.append(or_(*[col.ilike(p) for p in patterns]))
|
|
174
|
+
else:
|
|
175
|
+
coerced = [coerce(v) for v in vals]
|
|
176
|
+
if not coerced:
|
|
177
|
+
continue
|
|
178
|
+
predicates.append(col.in_(coerced))
|
|
176
179
|
else:
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
+
if _use_ilike(value) and _is_text_type(coltype):
|
|
181
|
+
pattern = _to_sql_like_pattern(str(value))
|
|
182
|
+
predicates.append(col.ilike(pattern))
|
|
180
183
|
else:
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
184
|
+
v = coerce(value)
|
|
185
|
+
if v is None:
|
|
186
|
+
predicates.append(col.is_(None))
|
|
187
|
+
else:
|
|
188
|
+
predicates.append(col == v)
|
|
189
|
+
|
|
190
|
+
except _InvalidEnum as e:
|
|
191
|
+
raise UnprocessableEntityException(str(e), loc=["query", "filters", raw_field]) from e
|
|
192
|
+
|
|
193
|
+
include_ids = getattr(pagination, "include_ids", []) or []
|
|
194
|
+
if include_ids and hasattr(entity, "id"):
|
|
195
|
+
id_col = getattr(entity, "id")
|
|
196
|
+
include_pred = id_col.in_(include_ids)
|
|
197
|
+
if predicates:
|
|
198
|
+
final_pred = or_(and_(*predicates), include_pred)
|
|
199
|
+
else:
|
|
200
|
+
final_pred = or_(and_(*[1 == 1]), include_pred)
|
|
201
|
+
return query.filter(final_pred)
|
|
202
|
+
|
|
203
|
+
if predicates:
|
|
204
|
+
query = query.filter(and_(*predicates))
|
|
187
205
|
return query
|
|
188
206
|
|
|
189
207
|
|
|
@@ -210,24 +228,24 @@ def _serialize_item(item: Any, output_cls: OutputType) -> Any:
|
|
|
210
228
|
|
|
211
229
|
if hasattr(output_cls, "model_validate"):
|
|
212
230
|
try:
|
|
213
|
-
model = output_cls.model_validate(item, from_attributes=True)
|
|
231
|
+
model = output_cls.model_validate(item, from_attributes=True)
|
|
214
232
|
if hasattr(model, "model_dump"):
|
|
215
|
-
return model.model_dump()
|
|
233
|
+
return model.model_dump()
|
|
216
234
|
return model
|
|
217
235
|
except Exception:
|
|
218
236
|
pass
|
|
219
237
|
|
|
220
238
|
if hasattr(output_cls, "from_orm"):
|
|
221
239
|
try:
|
|
222
|
-
model = output_cls.from_orm(item)
|
|
240
|
+
model = output_cls.from_orm(item)
|
|
223
241
|
if hasattr(model, "dict"):
|
|
224
|
-
return model.dict()
|
|
242
|
+
return model.dict()
|
|
225
243
|
return model
|
|
226
244
|
except Exception:
|
|
227
245
|
pass
|
|
228
246
|
|
|
229
247
|
try:
|
|
230
|
-
obj = output_cls(item)
|
|
248
|
+
obj = output_cls(item)
|
|
231
249
|
try:
|
|
232
250
|
from dataclasses import asdict, is_dataclass
|
|
233
251
|
|
|
@@ -249,7 +267,6 @@ def get_paginated_response(
|
|
|
249
267
|
output_cls: Optional[Type] = None,
|
|
250
268
|
eager_options: Optional[Sequence[LoaderOption]] = None,
|
|
251
269
|
) -> dict:
|
|
252
|
-
# پایهی کوئری
|
|
253
270
|
query = session.query(entity)
|
|
254
271
|
if eager_options:
|
|
255
272
|
query = query.options(*eager_options)
|
|
@@ -9,10 +9,7 @@ class RedisClient:
|
|
|
9
9
|
@classmethod
|
|
10
10
|
def get_client(cls) -> redis.Redis:
|
|
11
11
|
if cls._client is None:
|
|
12
|
-
cls._client = redis.from_url(
|
|
13
|
-
get_settings().REDIS_URL,
|
|
14
|
-
decode_responses=True
|
|
15
|
-
)
|
|
12
|
+
cls._client = redis.from_url(get_settings().REDIS_URL, decode_responses=True)
|
|
16
13
|
return cls._client
|
|
17
14
|
|
|
18
15
|
@classmethod
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
from .
|
|
2
|
-
from .
|
|
1
|
+
from .pricing import CalculatePriceIn, CalculatePriceOut, PricingService
|
|
2
|
+
from .uploadchi import UploadchiAsyncClient, UploadchiClient, UploadchiError
|
|
@@ -1 +1 @@
|
|
|
1
|
-
from .pricing_service import
|
|
1
|
+
from .pricing_service import CalculatePriceIn, CalculatePriceOut, PricingService
|