nlbone 0.9.0__py3-none-any.whl → 0.9.1__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/cache/async_redis.py +123 -73
- nlbone/adapters/cache/redis.py +81 -39
- nlbone/adapters/db/postgres/engine.py +30 -19
- nlbone/adapters/db/redis/client.py +21 -4
- nlbone/config/settings.py +18 -5
- nlbone/interfaces/api/dependencies/async_auth.py +4 -9
- nlbone/interfaces/api/dependencies/auth.py +3 -3
- nlbone/interfaces/api/exception_handlers.py +3 -3
- nlbone/interfaces/api/middleware/authentication.py +2 -3
- nlbone/utils/cache.py +25 -27
- {nlbone-0.9.0.dist-info → nlbone-0.9.1.dist-info}/METADATA +1 -1
- {nlbone-0.9.0.dist-info → nlbone-0.9.1.dist-info}/RECORD +15 -15
- {nlbone-0.9.0.dist-info → nlbone-0.9.1.dist-info}/WHEEL +0 -0
- {nlbone-0.9.0.dist-info → nlbone-0.9.1.dist-info}/entry_points.txt +0 -0
- {nlbone-0.9.0.dist-info → nlbone-0.9.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
import
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
2
4
|
import json
|
|
3
5
|
import os
|
|
4
|
-
from typing import Any, Iterable, Mapping, Optional, Sequence
|
|
6
|
+
from typing import Any, Awaitable, Callable, Iterable, Mapping, Optional, Sequence, Union
|
|
5
7
|
|
|
6
|
-
from redis.asyncio import Redis
|
|
8
|
+
from redis.asyncio import ConnectionPool, Redis
|
|
9
|
+
from redis.asyncio.retry import Retry
|
|
10
|
+
from redis.backoff import ExponentialBackoff
|
|
11
|
+
from redis.exceptions import LockError, RedisError
|
|
7
12
|
|
|
13
|
+
from nlbone.config.settings import get_settings
|
|
8
14
|
from nlbone.core.ports.cache import AsyncCachePort
|
|
9
15
|
|
|
10
16
|
|
|
@@ -17,23 +23,41 @@ def _tag_key(tag: str) -> str:
|
|
|
17
23
|
|
|
18
24
|
|
|
19
25
|
class AsyncRedisCache(AsyncCachePort):
|
|
20
|
-
def __init__(self, url: str, *, invalidate_channel: str
|
|
21
|
-
self.
|
|
26
|
+
def __init__(self, url: str, *, invalidate_channel: Optional[str] = None):
|
|
27
|
+
self._pool = ConnectionPool.from_url(
|
|
28
|
+
url,
|
|
29
|
+
decode_responses=False,
|
|
30
|
+
max_connections=get_settings().REDIS_MAX_CONNECTIONS,
|
|
31
|
+
socket_timeout=get_settings().REDIS_TIMEOUT,
|
|
32
|
+
socket_connect_timeout=get_settings().REDIS_TIMEOUT,
|
|
33
|
+
health_check_interval=get_settings().REDIS_CHECK_INTERVAL,
|
|
34
|
+
retry_on_timeout=True,
|
|
35
|
+
retry=Retry(ExponentialBackoff(), 3),
|
|
36
|
+
)
|
|
37
|
+
self._r = Redis(connection_pool=self._pool)
|
|
22
38
|
self._ch = invalidate_channel or os.getenv("NLBONE_REDIS_INVALIDATE_CHANNEL", "cache:invalidate")
|
|
23
39
|
|
|
24
40
|
@property
|
|
25
41
|
def redis(self) -> Redis:
|
|
26
42
|
return self._r
|
|
27
43
|
|
|
44
|
+
async def close(self):
|
|
45
|
+
await self._r.close()
|
|
46
|
+
await self._pool.disconnect()
|
|
47
|
+
|
|
28
48
|
async def _current_ver(self, ns: str) -> int:
|
|
29
|
-
|
|
30
|
-
|
|
49
|
+
try:
|
|
50
|
+
v = await self._r.get(_nsver_key(ns))
|
|
51
|
+
return int(v) if v else 1
|
|
52
|
+
except (ValueError, TypeError):
|
|
53
|
+
return 1
|
|
31
54
|
|
|
32
55
|
async def _full_key(self, key: str) -> str:
|
|
33
56
|
try:
|
|
34
57
|
ns, rest = key.split(":", 1)
|
|
35
58
|
except ValueError:
|
|
36
59
|
ns, rest = "app", key
|
|
60
|
+
|
|
37
61
|
ver = await self._current_ver(ns)
|
|
38
62
|
return f"{ns}:{ver}:{rest}"
|
|
39
63
|
|
|
@@ -46,14 +70,17 @@ class AsyncRedisCache(AsyncCachePort):
|
|
|
46
70
|
self, key: str, value: bytes, *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None
|
|
47
71
|
) -> None:
|
|
48
72
|
fk = await self._full_key(key)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
73
|
+
|
|
74
|
+
async with self._r.pipeline() as pipe:
|
|
75
|
+
if ttl is None:
|
|
76
|
+
await pipe.set(fk, value)
|
|
77
|
+
else:
|
|
78
|
+
await pipe.setex(fk, ttl, value)
|
|
79
|
+
|
|
80
|
+
if tags:
|
|
81
|
+
for t in tags:
|
|
82
|
+
await pipe.sadd(_tag_key(t), fk)
|
|
83
|
+
|
|
57
84
|
await pipe.execute()
|
|
58
85
|
|
|
59
86
|
async def delete(self, key: str) -> None:
|
|
@@ -61,83 +88,87 @@ class AsyncRedisCache(AsyncCachePort):
|
|
|
61
88
|
await self._r.delete(fk)
|
|
62
89
|
|
|
63
90
|
async def exists(self, key: str) -> bool:
|
|
64
|
-
|
|
91
|
+
fk = await self._full_key(key)
|
|
92
|
+
return bool(await self._r.exists(fk))
|
|
65
93
|
|
|
66
94
|
async def ttl(self, key: str) -> Optional[int]:
|
|
67
95
|
fk = await self._full_key(key)
|
|
68
96
|
t = await self._r.ttl(fk)
|
|
69
|
-
return
|
|
97
|
+
return int(t) if t >= 0 else None
|
|
70
98
|
|
|
71
|
-
|
|
99
|
+
# -------- multi --------
|
|
72
100
|
|
|
73
101
|
async def mget(self, keys: Sequence[str]) -> list[Optional[bytes]]:
|
|
102
|
+
if not keys:
|
|
103
|
+
return []
|
|
104
|
+
# Alternatively, await asyncio.gather(*[self._full_key(k) for k in keys])
|
|
74
105
|
fks = [await self._full_key(k) for k in keys]
|
|
75
106
|
return await self._r.mget(fks)
|
|
76
107
|
|
|
77
108
|
async def mset(
|
|
78
109
|
self, items: Mapping[str, bytes], *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None
|
|
79
110
|
) -> None:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
pipe.set(fk, v)
|
|
85
|
-
else:
|
|
111
|
+
if not items:
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
async with self._r.pipeline() as pipe:
|
|
86
115
|
for k, v in items.items():
|
|
87
116
|
fk = await self._full_key(k)
|
|
88
|
-
|
|
89
|
-
|
|
117
|
+
if ttl is None:
|
|
118
|
+
await pipe.set(fk, v)
|
|
119
|
+
else:
|
|
120
|
+
await pipe.setex(fk, ttl, v)
|
|
121
|
+
|
|
122
|
+
if tags:
|
|
123
|
+
for t in tags:
|
|
124
|
+
await pipe.sadd(_tag_key(t), fk)
|
|
90
125
|
|
|
91
|
-
if tags:
|
|
92
|
-
pipe = self._r.pipeline()
|
|
93
|
-
for t in tags:
|
|
94
|
-
for k in items.keys():
|
|
95
|
-
fk = await self._full_key(k)
|
|
96
|
-
pipe.sadd(_tag_key(t), fk)
|
|
97
126
|
await pipe.execute()
|
|
98
127
|
|
|
99
|
-
|
|
128
|
+
# -------- json --------
|
|
100
129
|
|
|
101
130
|
async def get_json(self, key: str) -> Optional[Any]:
|
|
102
131
|
b = await self.get(key)
|
|
103
|
-
|
|
132
|
+
if b is None:
|
|
133
|
+
return None
|
|
134
|
+
try:
|
|
135
|
+
return json.loads(b)
|
|
136
|
+
except json.JSONDecodeError:
|
|
137
|
+
return None
|
|
104
138
|
|
|
105
139
|
async def set_json(
|
|
106
140
|
self, key: str, value: Any, *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None
|
|
107
141
|
) -> None:
|
|
108
|
-
|
|
142
|
+
payload = json.dumps(value).encode("utf-8")
|
|
143
|
+
await self.set(key, payload, ttl=ttl, tags=tags)
|
|
109
144
|
|
|
110
|
-
|
|
145
|
+
# -------- invalidation --------
|
|
111
146
|
|
|
112
147
|
async def invalidate_tags(self, tags: Iterable[str]) -> int:
|
|
113
148
|
removed = 0
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
pipe.delete(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
await pipe.execute()
|
|
125
|
-
|
|
126
|
-
# publish notification for other processes
|
|
149
|
+
async with self._r.pipeline() as pipe:
|
|
150
|
+
for t in tags:
|
|
151
|
+
tk = _tag_key(t)
|
|
152
|
+
members = await self._r.smembers(tk)
|
|
153
|
+
if members:
|
|
154
|
+
await pipe.delete(*members)
|
|
155
|
+
await pipe.delete(tk)
|
|
156
|
+
removed += len(members or [])
|
|
157
|
+
await pipe.execute()
|
|
158
|
+
|
|
127
159
|
try:
|
|
128
160
|
payload = json.dumps({"tags": list(tags)}).encode("utf-8")
|
|
129
161
|
await self._r.publish(self._ch, payload)
|
|
130
|
-
except
|
|
162
|
+
except RedisError:
|
|
131
163
|
pass
|
|
132
164
|
|
|
133
165
|
return removed
|
|
134
166
|
|
|
135
167
|
async def bump_namespace(self, namespace: str) -> int:
|
|
136
168
|
v = await self._r.incr(_nsver_key(namespace))
|
|
137
|
-
# اطلاعرسانی اختیاری
|
|
138
169
|
try:
|
|
139
170
|
await self._r.publish(self._ch, json.dumps({"ns_bump": namespace}).encode("utf-8"))
|
|
140
|
-
except
|
|
171
|
+
except RedisError:
|
|
141
172
|
pass
|
|
142
173
|
return int(v)
|
|
143
174
|
|
|
@@ -145,6 +176,7 @@ class AsyncRedisCache(AsyncCachePort):
|
|
|
145
176
|
cnt = 0
|
|
146
177
|
cursor = 0
|
|
147
178
|
pattern = f"{namespace}:*"
|
|
179
|
+
|
|
148
180
|
while True:
|
|
149
181
|
cursor, keys = await self._r.scan(cursor=cursor, match=pattern, count=1000)
|
|
150
182
|
if keys:
|
|
@@ -152,39 +184,57 @@ class AsyncRedisCache(AsyncCachePort):
|
|
|
152
184
|
cnt += len(keys)
|
|
153
185
|
if cursor == 0:
|
|
154
186
|
break
|
|
187
|
+
|
|
155
188
|
try:
|
|
156
189
|
await self._r.publish(self._ch, json.dumps({"ns_clear": namespace}).encode("utf-8"))
|
|
157
|
-
except
|
|
190
|
+
except RedisError:
|
|
158
191
|
pass
|
|
192
|
+
|
|
159
193
|
return cnt
|
|
160
194
|
|
|
161
|
-
|
|
195
|
+
# -------- dogpile-safe get_or_set --------
|
|
162
196
|
|
|
163
|
-
async def get_or_set(
|
|
197
|
+
async def get_or_set(
|
|
198
|
+
self,
|
|
199
|
+
key: str,
|
|
200
|
+
producer: Callable[[], Union[bytes, str, Awaitable[Union[bytes, str]]]],
|
|
201
|
+
*,
|
|
202
|
+
ttl: int,
|
|
203
|
+
tags: Optional[Iterable[str]] = None,
|
|
204
|
+
) -> bytes:
|
|
164
205
|
fk = await self._full_key(key)
|
|
206
|
+
|
|
165
207
|
val = await self._r.get(fk)
|
|
166
208
|
if val is not None:
|
|
167
209
|
return val
|
|
168
210
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
211
|
+
lock_name = f"lock:{fk}"
|
|
212
|
+
|
|
213
|
+
try:
|
|
214
|
+
async with self._r.lock(lock_name, timeout=10, blocking_timeout=5):
|
|
215
|
+
val = await self._r.get(fk)
|
|
216
|
+
if val is not None:
|
|
217
|
+
return val
|
|
218
|
+
|
|
219
|
+
if inspect.iscoroutinefunction(producer):
|
|
220
|
+
produced = await producer()
|
|
221
|
+
else:
|
|
222
|
+
produced = producer()
|
|
223
|
+
|
|
174
224
|
if isinstance(produced, str):
|
|
175
225
|
produced = produced.encode("utf-8")
|
|
226
|
+
|
|
176
227
|
await self.set(key, produced, ttl=ttl, tags=tags)
|
|
177
228
|
return produced
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
produced =
|
|
189
|
-
|
|
190
|
-
return produced
|
|
229
|
+
|
|
230
|
+
except LockError:
|
|
231
|
+
if inspect.iscoroutinefunction(producer):
|
|
232
|
+
produced = await producer()
|
|
233
|
+
else:
|
|
234
|
+
produced = producer()
|
|
235
|
+
|
|
236
|
+
if isinstance(produced, str):
|
|
237
|
+
produced = produced.encode("utf-8")
|
|
238
|
+
|
|
239
|
+
await self.set(key, produced, ttl=ttl, tags=tags)
|
|
240
|
+
return produced
|
nlbone/adapters/cache/redis.py
CHANGED
|
@@ -2,11 +2,15 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
|
-
import time
|
|
6
5
|
from typing import Any, Iterable, Mapping, Optional, Sequence
|
|
7
6
|
|
|
8
|
-
import redis
|
|
7
|
+
import redis
|
|
8
|
+
from redis import RedisError
|
|
9
|
+
from redis.backoff import ExponentialBackoff
|
|
10
|
+
from redis.exceptions import LockError
|
|
11
|
+
from redis.retry import Retry
|
|
9
12
|
|
|
13
|
+
from nlbone.config.settings import get_settings
|
|
10
14
|
from nlbone.core.ports.cache import CachePort
|
|
11
15
|
|
|
12
16
|
|
|
@@ -20,17 +24,32 @@ def _tag_key(tag: str) -> str:
|
|
|
20
24
|
|
|
21
25
|
class RedisCache(CachePort):
|
|
22
26
|
def __init__(self, url: str):
|
|
23
|
-
self.
|
|
27
|
+
self._pool = redis.ConnectionPool.from_url(
|
|
28
|
+
url,
|
|
29
|
+
decode_responses=False,
|
|
30
|
+
max_connections=get_settings().REDIS_MAX_CONNECTIONS,
|
|
31
|
+
socket_timeout=get_settings().REDIS_TIMEOUT,
|
|
32
|
+
socket_connect_timeout=get_settings().REDIS_TIMEOUT,
|
|
33
|
+
health_check_interval=get_settings().REDIS_CHECK_INTERVAL,
|
|
34
|
+
retry_on_timeout=True,
|
|
35
|
+
retry=Retry(ExponentialBackoff(), 3),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
self.r = redis.Redis(connection_pool=self._pool)
|
|
24
39
|
|
|
25
40
|
def _current_ver(self, ns: str) -> int:
|
|
26
|
-
|
|
27
|
-
|
|
41
|
+
try:
|
|
42
|
+
v = self.r.get(_nsver_key(ns))
|
|
43
|
+
return int(v) if v else 1
|
|
44
|
+
except (ValueError, TypeError):
|
|
45
|
+
return 1
|
|
28
46
|
|
|
29
47
|
def _full_key(self, key: str) -> str:
|
|
30
48
|
try:
|
|
31
49
|
ns, rest = key.split(":", 1)
|
|
32
50
|
except ValueError:
|
|
33
51
|
ns, rest = "app", key
|
|
52
|
+
|
|
34
53
|
ver = self._current_ver(ns)
|
|
35
54
|
return f"{ns}:{ver}:{rest}"
|
|
36
55
|
|
|
@@ -40,53 +59,67 @@ class RedisCache(CachePort):
|
|
|
40
59
|
|
|
41
60
|
def set(self, key: str, value: bytes, *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None) -> None:
|
|
42
61
|
fk = self._full_key(key)
|
|
62
|
+
|
|
63
|
+
pipe = self.r.pipeline()
|
|
64
|
+
|
|
43
65
|
if ttl is None:
|
|
44
|
-
|
|
66
|
+
pipe.set(fk, value)
|
|
45
67
|
else:
|
|
46
|
-
|
|
68
|
+
pipe.setex(fk, ttl, value)
|
|
69
|
+
|
|
47
70
|
if tags:
|
|
48
|
-
pipe = self.r.pipeline()
|
|
49
71
|
for t in tags:
|
|
50
72
|
pipe.sadd(_tag_key(t), fk)
|
|
51
|
-
|
|
73
|
+
|
|
74
|
+
pipe.execute()
|
|
52
75
|
|
|
53
76
|
def delete(self, key: str) -> None:
|
|
54
77
|
fk = self._full_key(key)
|
|
55
78
|
self.r.delete(fk)
|
|
56
79
|
|
|
57
80
|
def exists(self, key: str) -> bool:
|
|
58
|
-
return bool(self.
|
|
81
|
+
return bool(self.r.exists(self._full_key(key)))
|
|
59
82
|
|
|
60
83
|
def ttl(self, key: str) -> Optional[int]:
|
|
61
84
|
fk = self._full_key(key)
|
|
62
85
|
t = self.r.ttl(fk)
|
|
63
|
-
return
|
|
86
|
+
return int(t) if t >= 0 else None
|
|
64
87
|
|
|
65
88
|
def mget(self, keys: Sequence[str]) -> list[Optional[bytes]]:
|
|
89
|
+
if not keys:
|
|
90
|
+
return []
|
|
66
91
|
fks = [self._full_key(k) for k in keys]
|
|
67
92
|
return self.r.mget(fks)
|
|
68
93
|
|
|
69
94
|
def mset(
|
|
70
95
|
self, items: Mapping[str, bytes], *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None
|
|
71
96
|
) -> None:
|
|
97
|
+
if not items:
|
|
98
|
+
return
|
|
99
|
+
|
|
72
100
|
pipe = self.r.pipeline()
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
101
|
+
|
|
102
|
+
for k, v in items.items():
|
|
103
|
+
fk = self._full_key(k)
|
|
104
|
+
if ttl is None:
|
|
105
|
+
pipe.set(fk, v)
|
|
106
|
+
else:
|
|
107
|
+
pipe.setex(fk, ttl, v)
|
|
108
|
+
|
|
109
|
+
if tags:
|
|
110
|
+
for t in tags:
|
|
111
|
+
pipe.sadd(_tag_key(t), fk)
|
|
112
|
+
|
|
79
113
|
pipe.execute()
|
|
80
|
-
if tags:
|
|
81
|
-
pipe = self.r.pipeline()
|
|
82
|
-
for t in tags:
|
|
83
|
-
for k in items.keys():
|
|
84
|
-
pipe.sadd(_tag_key(t), self._full_key(k))
|
|
85
|
-
pipe.execute()
|
|
86
114
|
|
|
87
115
|
def get_json(self, key: str) -> Optional[Any]:
|
|
88
116
|
b = self.get(key)
|
|
89
|
-
|
|
117
|
+
if b is None:
|
|
118
|
+
return None
|
|
119
|
+
try:
|
|
120
|
+
return json.loads(b)
|
|
121
|
+
except json.JSONDecodeError:
|
|
122
|
+
return None
|
|
90
123
|
|
|
91
124
|
def set_json(
|
|
92
125
|
self, key: str, value: Any, *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None
|
|
@@ -96,6 +129,7 @@ class RedisCache(CachePort):
|
|
|
96
129
|
def invalidate_tags(self, tags: Iterable[str]) -> int:
|
|
97
130
|
removed = 0
|
|
98
131
|
pipe = self.r.pipeline()
|
|
132
|
+
|
|
99
133
|
for t in tags:
|
|
100
134
|
tk = _tag_key(t)
|
|
101
135
|
keys = self.r.smembers(tk)
|
|
@@ -103,12 +137,15 @@ class RedisCache(CachePort):
|
|
|
103
137
|
pipe.delete(*keys)
|
|
104
138
|
pipe.delete(tk)
|
|
105
139
|
removed += len(keys or [])
|
|
140
|
+
|
|
106
141
|
pipe.execute()
|
|
142
|
+
|
|
107
143
|
try:
|
|
108
144
|
ch = os.getenv("NLBONE_REDIS_INVALIDATE_CHANNEL", "cache:invalidate")
|
|
109
145
|
self.r.publish(ch, json.dumps({"tags": list(tags)}).encode("utf-8"))
|
|
110
|
-
except
|
|
146
|
+
except RedisError:
|
|
111
147
|
pass
|
|
148
|
+
|
|
112
149
|
return removed
|
|
113
150
|
|
|
114
151
|
def bump_namespace(self, namespace: str) -> int:
|
|
@@ -119,6 +156,7 @@ class RedisCache(CachePort):
|
|
|
119
156
|
cnt = 0
|
|
120
157
|
cursor = 0
|
|
121
158
|
pattern = f"{namespace}:*"
|
|
159
|
+
|
|
122
160
|
while True:
|
|
123
161
|
cursor, keys = self.r.scan(cursor=cursor, match=pattern, count=1000)
|
|
124
162
|
if keys:
|
|
@@ -128,24 +166,28 @@ class RedisCache(CachePort):
|
|
|
128
166
|
break
|
|
129
167
|
return cnt
|
|
130
168
|
|
|
131
|
-
def get_or_set(self, key: str, producer, *, ttl: int, tags=None) -> bytes:
|
|
169
|
+
def get_or_set(self, key: str, producer, *, ttl: int, tags: Optional[Iterable[str]] = None) -> bytes:
|
|
132
170
|
fk = self._full_key(key)
|
|
171
|
+
|
|
133
172
|
val = self.r.get(fk)
|
|
134
173
|
if val is not None:
|
|
135
174
|
return val
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
175
|
+
|
|
176
|
+
lock_name = f"lock:{fk}"
|
|
177
|
+
try:
|
|
178
|
+
with self.r.lock(lock_name, timeout=10, blocking_timeout=5):
|
|
179
|
+
val = self.r.get(fk)
|
|
180
|
+
if val is not None:
|
|
181
|
+
return val
|
|
182
|
+
|
|
140
183
|
produced: bytes = producer()
|
|
141
184
|
self.set(key, produced, ttl=ttl, tags=tags)
|
|
142
185
|
return produced
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
return produced
|
|
186
|
+
|
|
187
|
+
except LockError:
|
|
188
|
+
try:
|
|
189
|
+
produced = producer()
|
|
190
|
+
self.set(key, produced, ttl=ttl, tags=tags)
|
|
191
|
+
return produced
|
|
192
|
+
except Exception:
|
|
193
|
+
raise
|
|
@@ -15,12 +15,16 @@ from nlbone.config.settings import get_settings
|
|
|
15
15
|
|
|
16
16
|
_settings = get_settings()
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
_dsn = _settings.POSTGRES_DB_DSN
|
|
19
19
|
|
|
20
|
-
if "+asyncpg" in
|
|
21
|
-
|
|
20
|
+
if "+asyncpg" in _dsn:
|
|
21
|
+
ASYNC_DSN = _dsn.replace("+asyncpg", "+psycopg")
|
|
22
|
+
elif "+psycopg" not in _dsn:
|
|
23
|
+
ASYNC_DSN = _dsn.replace("postgresql://", "postgresql+psycopg://")
|
|
22
24
|
else:
|
|
23
|
-
|
|
25
|
+
ASYNC_DSN = _dsn
|
|
26
|
+
|
|
27
|
+
SYNC_DSN = ASYNC_DSN
|
|
24
28
|
|
|
25
29
|
_async_engine: Optional[AsyncEngine] = None
|
|
26
30
|
_async_session_factory: Optional[async_sessionmaker[AsyncSession]] = None
|
|
@@ -38,14 +42,19 @@ def init_async_engine(echo: Optional[bool] = None) -> AsyncEngine:
|
|
|
38
42
|
ASYNC_DSN,
|
|
39
43
|
echo=_settings.DEBUG if echo is None else echo,
|
|
40
44
|
pool_pre_ping=True,
|
|
41
|
-
pool_size=
|
|
42
|
-
max_overflow=
|
|
45
|
+
pool_size=_settings.POSTGRES_POOL_SIZE,
|
|
46
|
+
max_overflow=_settings.POSTGRES_MAX_OVERFLOW,
|
|
47
|
+
pool_recycle=_settings.POSTGRES_POOL_RECYCLE,
|
|
48
|
+
pool_timeout=_settings.POSTGRES_POOL_TIMEOUT,
|
|
43
49
|
)
|
|
50
|
+
|
|
44
51
|
_async_session_factory = async_sessionmaker(
|
|
45
52
|
bind=_async_engine,
|
|
46
53
|
expire_on_commit=False,
|
|
47
54
|
autoflush=False,
|
|
55
|
+
class_=AsyncSession,
|
|
48
56
|
)
|
|
57
|
+
|
|
49
58
|
return _async_engine
|
|
50
59
|
|
|
51
60
|
|
|
@@ -54,6 +63,7 @@ async def async_session() -> AsyncGenerator[AsyncSession, Any]:
|
|
|
54
63
|
if _async_session_factory is None:
|
|
55
64
|
init_async_engine()
|
|
56
65
|
assert _async_session_factory is not None
|
|
66
|
+
|
|
57
67
|
session = _async_session_factory()
|
|
58
68
|
try:
|
|
59
69
|
yield session
|
|
@@ -64,12 +74,6 @@ async def async_session() -> AsyncGenerator[AsyncSession, Any]:
|
|
|
64
74
|
await session.close()
|
|
65
75
|
|
|
66
76
|
|
|
67
|
-
async def async_ping() -> None:
|
|
68
|
-
eng = init_async_engine()
|
|
69
|
-
async with eng.connect() as conn:
|
|
70
|
-
await conn.execute(text("SELECT 1"))
|
|
71
|
-
|
|
72
|
-
|
|
73
77
|
def init_sync_engine(echo: Optional[bool] = None) -> Engine:
|
|
74
78
|
global _sync_engine, _sync_session_factory
|
|
75
79
|
if _sync_engine is not None:
|
|
@@ -80,11 +84,12 @@ def init_sync_engine(echo: Optional[bool] = None) -> Engine:
|
|
|
80
84
|
echo=_settings.DEBUG if echo is None else echo,
|
|
81
85
|
pool_pre_ping=True,
|
|
82
86
|
pool_size=_settings.POSTGRES_POOL_SIZE,
|
|
83
|
-
max_overflow=_settings.
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
max_overflow=_settings.POSTGRES_MAX_OVERFLOW,
|
|
88
|
+
pool_recycle=_settings.POSTGRES_POOL_RECYCLE,
|
|
89
|
+
pool_timeout=_settings.POSTGRES_POOL_TIMEOUT,
|
|
86
90
|
future=True,
|
|
87
91
|
)
|
|
92
|
+
|
|
88
93
|
_sync_session_factory = sessionmaker(
|
|
89
94
|
bind=_sync_engine,
|
|
90
95
|
autocommit=False,
|
|
@@ -110,11 +115,17 @@ def sync_session() -> Generator[Session, None, None]:
|
|
|
110
115
|
s.close()
|
|
111
116
|
|
|
112
117
|
|
|
118
|
+
# --- Health Checks & Getters ---
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
async def async_ping() -> None:
|
|
122
|
+
async with async_session() as session:
|
|
123
|
+
await session.execute(text("SELECT 1"))
|
|
124
|
+
|
|
125
|
+
|
|
113
126
|
def sync_ping() -> None:
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
with eng.connect() as conn:
|
|
117
|
-
conn.execute(text("SELECT 1"))
|
|
127
|
+
with sync_session() as session:
|
|
128
|
+
session.execute(text("SELECT 1"))
|
|
118
129
|
|
|
119
130
|
|
|
120
131
|
def get_async_session_factory() -> async_sessionmaker[AsyncSession]:
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import
|
|
1
|
+
from redis import Redis
|
|
2
|
+
from redis.asyncio import Redis as AsyncRedis
|
|
2
3
|
|
|
3
4
|
from nlbone.config.settings import get_settings
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
class RedisClient:
|
|
7
|
-
_client:
|
|
8
|
+
_client: Redis | None = None
|
|
8
9
|
|
|
9
10
|
@classmethod
|
|
10
|
-
def get_client(cls) ->
|
|
11
|
+
def get_client(cls) -> Redis:
|
|
11
12
|
if cls._client is None:
|
|
12
|
-
cls._client =
|
|
13
|
+
cls._client = Redis.from_url(get_settings().REDIS_URL, decode_responses=True)
|
|
13
14
|
return cls._client
|
|
14
15
|
|
|
15
16
|
@classmethod
|
|
@@ -17,3 +18,19 @@ class RedisClient:
|
|
|
17
18
|
if cls._client is not None:
|
|
18
19
|
cls._client.close()
|
|
19
20
|
cls._client = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AsyncRedisClient:
|
|
24
|
+
_client: AsyncRedis | None = None
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def get_client(cls) -> Redis:
|
|
28
|
+
if cls._client is None:
|
|
29
|
+
cls._client = AsyncRedis.from_url(get_settings().REDIS_URL, decode_responses=True)
|
|
30
|
+
return cls._client
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
async def close(cls):
|
|
34
|
+
if cls._client is not None:
|
|
35
|
+
await cls._client.close()
|
|
36
|
+
cls._client = None
|
nlbone/config/settings.py
CHANGED
|
@@ -70,24 +70,37 @@ class Settings(BaseSettings):
|
|
|
70
70
|
# Database
|
|
71
71
|
# ---------------------------
|
|
72
72
|
POSTGRES_DB_DSN: str = Field(default="postgresql+asyncpg://user:pass@localhost:5432/nlbone")
|
|
73
|
+
POSTGRES_DB_ECHO: bool = Field(default=False)
|
|
73
74
|
POSTGRES_POOL_SIZE: int = Field(default=5)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
DB_MAX_OVERFLOW: int = Field(default=10)
|
|
75
|
+
POSTGRES_MAX_OVERFLOW: int = Field(default=10)
|
|
76
|
+
POSTGRES_POOL_TIMEOUT: int = Field(default=30)
|
|
77
|
+
POSTGRES_POOL_RECYCLE: int = Field(default=1800)
|
|
78
78
|
|
|
79
79
|
# ---------------------------
|
|
80
80
|
# Messaging / Cache
|
|
81
81
|
# ---------------------------
|
|
82
82
|
REDIS_URL: str = Field(default="redis://localhost:6379/0")
|
|
83
|
+
REDIS_MAX_CONNECTIONS: int = Field(default=5)
|
|
84
|
+
REDIS_CHECK_INTERVAL: int = Field(default=30)
|
|
85
|
+
REDIS_TIMEOUT: float = Field(default=3.0)
|
|
86
|
+
|
|
83
87
|
CACHE_BACKEND: Literal["memory", "redis"] = Field(default="memory")
|
|
84
88
|
CACHE_DEFAULT_TTL_S: int = Field(default=300)
|
|
85
89
|
|
|
86
|
-
#
|
|
90
|
+
# ---------------------------
|
|
91
|
+
# Event bus / Outbox
|
|
92
|
+
# ---------------------------
|
|
87
93
|
EVENT_BUS_BACKEND: Literal["inmemory"] = Field(default="inmemory")
|
|
88
94
|
OUTBOX_ENABLED: bool = Field(default=False)
|
|
89
95
|
OUTBOX_POLL_INTERVAL_MS: int = Field(default=500)
|
|
90
96
|
|
|
97
|
+
# ---------------------------
|
|
98
|
+
# APM
|
|
99
|
+
# ---------------------------
|
|
100
|
+
APM_SERVER_URL: str = "https://apm.numberland.dev"
|
|
101
|
+
APM_SECRET_TOKEN: str = ""
|
|
102
|
+
APM_SAMPLE_RATE: float = Field(default=0.5)
|
|
103
|
+
|
|
91
104
|
# ---------------------------
|
|
92
105
|
# UPLOADCHI
|
|
93
106
|
# ---------------------------
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import functools
|
|
2
2
|
|
|
3
3
|
from nlbone.adapters.auth.auth_service import get_auth_service
|
|
4
|
-
from nlbone.interfaces.api.exceptions import
|
|
4
|
+
from nlbone.interfaces.api.exceptions import UnauthorizedException
|
|
5
5
|
from nlbone.utils.context import current_request
|
|
6
6
|
|
|
7
|
-
from .auth import client_has_access_func, client_or_user_has_access_func
|
|
7
|
+
from .auth import client_has_access_func, client_or_user_has_access_func, user_has_access_func
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
async def current_user_id() -> int:
|
|
@@ -47,18 +47,14 @@ def has_access(*, permissions=None):
|
|
|
47
47
|
def decorator(func):
|
|
48
48
|
@functools.wraps(func)
|
|
49
49
|
async def wrapper(*args, **kwargs):
|
|
50
|
-
|
|
51
|
-
if not await current_user_id():
|
|
52
|
-
raise UnauthorizedException()
|
|
53
|
-
if not get_auth_service().has_access(request.state.token, permissions=permissions):
|
|
54
|
-
raise ForbiddenException(f"Forbidden {permissions}")
|
|
55
|
-
|
|
50
|
+
user_has_access_func(permissions=permissions)
|
|
56
51
|
return await func(*args, **kwargs)
|
|
57
52
|
|
|
58
53
|
return wrapper
|
|
59
54
|
|
|
60
55
|
return decorator
|
|
61
56
|
|
|
57
|
+
|
|
62
58
|
def client_or_user_has_access(*, permissions=None, client_permissions=None):
|
|
63
59
|
def decorator(func):
|
|
64
60
|
@functools.wraps(func)
|
|
@@ -69,4 +65,3 @@ def client_or_user_has_access(*, permissions=None, client_permissions=None):
|
|
|
69
65
|
return wrapper
|
|
70
66
|
|
|
71
67
|
return decorator
|
|
72
|
-
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import functools
|
|
2
2
|
|
|
3
|
-
from nlbone.core.domain.models import CurrentUserData
|
|
4
3
|
from nlbone.adapters.auth.auth_service import get_auth_service
|
|
5
4
|
from nlbone.config.settings import get_settings
|
|
5
|
+
from nlbone.core.domain.models import CurrentUserData
|
|
6
6
|
from nlbone.interfaces.api.exceptions import ForbiddenException, UnauthorizedException
|
|
7
7
|
from nlbone.utils.context import current_request
|
|
8
8
|
|
|
@@ -66,7 +66,7 @@ def user_authenticated(func):
|
|
|
66
66
|
|
|
67
67
|
def user_has_access_func(*, permissions=None):
|
|
68
68
|
if bypass_authz():
|
|
69
|
-
return
|
|
69
|
+
return True
|
|
70
70
|
request = current_request()
|
|
71
71
|
if not current_user_id():
|
|
72
72
|
raise UnauthorizedException()
|
|
@@ -89,7 +89,7 @@ def has_access(*, permissions=None):
|
|
|
89
89
|
|
|
90
90
|
def client_or_user_has_access_func(permissions=None, client_permissions=None):
|
|
91
91
|
if bypass_authz():
|
|
92
|
-
return
|
|
92
|
+
return True
|
|
93
93
|
request = current_request()
|
|
94
94
|
token = getattr(request.state, "token", None)
|
|
95
95
|
if not token:
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Any, Mapping, Optional
|
|
3
|
+
from typing import Any, List, Mapping, Optional
|
|
4
4
|
from uuid import uuid4
|
|
5
5
|
|
|
6
6
|
from fastapi import FastAPI, Request
|
|
7
|
-
from fastapi import HTTPException as FastAPIHTTPException
|
|
8
7
|
from fastapi.exceptions import RequestValidationError
|
|
9
8
|
from fastapi.responses import JSONResponse
|
|
10
9
|
from pydantic import BaseModel, ValidationError
|
|
11
10
|
from starlette.exceptions import HTTPException as StarletteHTTPException
|
|
12
|
-
from starlette.status import HTTP_500_INTERNAL_SERVER_ERROR
|
|
11
|
+
from starlette.status import HTTP_500_INTERNAL_SERVER_ERROR
|
|
13
12
|
|
|
14
13
|
from nlbone.adapters.i18n import translator as _
|
|
14
|
+
|
|
15
15
|
from .exceptions import BaseHttpException, ErrorDetail, UnprocessableEntityException
|
|
16
16
|
|
|
17
17
|
|
|
@@ -3,9 +3,9 @@ from typing import Callable, Optional, Union
|
|
|
3
3
|
from fastapi import Request
|
|
4
4
|
from starlette.middleware.base import BaseHTTPMiddleware
|
|
5
5
|
|
|
6
|
-
from nlbone.core.domain.models import CurrentUserData
|
|
7
6
|
from nlbone.adapters.auth.auth_service import AuthService
|
|
8
7
|
from nlbone.config.settings import get_settings
|
|
8
|
+
from nlbone.core.domain.models import CurrentUserData
|
|
9
9
|
|
|
10
10
|
try:
|
|
11
11
|
from dependency_injector import providers
|
|
@@ -54,8 +54,7 @@ def authenticate_admin_user(request, auth_service):
|
|
|
54
54
|
|
|
55
55
|
def authenticate_user(request):
|
|
56
56
|
token = (
|
|
57
|
-
|
|
58
|
-
"Authorization")
|
|
57
|
+
request.cookies.get("access_token") or request.cookies.get("j_token") or request.headers.get("Authorization")
|
|
59
58
|
)
|
|
60
59
|
if request.headers.get("Authorization"):
|
|
61
60
|
scheme, token = request.headers.get("Authorization").split(" ", 1)
|
nlbone/utils/cache.py
CHANGED
|
@@ -111,14 +111,18 @@ def default_deserialize(b: bytes) -> Any:
|
|
|
111
111
|
|
|
112
112
|
def _is_async_method(obj: Any, name: str) -> bool:
|
|
113
113
|
meth = getattr(obj, name, None)
|
|
114
|
-
return
|
|
114
|
+
return inspect.iscoroutinefunction(meth)
|
|
115
115
|
|
|
116
116
|
|
|
117
117
|
def _run_maybe_async(func: Callable, *args, **kwargs):
|
|
118
118
|
"""Call a function that may be async from sync context."""
|
|
119
119
|
result = func(*args, **kwargs)
|
|
120
120
|
if inspect.isawaitable(result):
|
|
121
|
-
|
|
121
|
+
try:
|
|
122
|
+
return asyncio.run(result)
|
|
123
|
+
except RuntimeError:
|
|
124
|
+
result.close()
|
|
125
|
+
raise
|
|
122
126
|
return result
|
|
123
127
|
|
|
124
128
|
|
|
@@ -135,7 +139,7 @@ def cached(
|
|
|
135
139
|
cache_resolver: Optional[Callable[[], Any]] = None,
|
|
136
140
|
):
|
|
137
141
|
def deco(func: Callable):
|
|
138
|
-
is_async_func =
|
|
142
|
+
is_async_func = inspect.iscoroutinefunction(func)
|
|
139
143
|
|
|
140
144
|
if is_async_func:
|
|
141
145
|
|
|
@@ -150,10 +154,11 @@ def cached(
|
|
|
150
154
|
# SAFE GET
|
|
151
155
|
cached_bytes = None
|
|
152
156
|
try:
|
|
153
|
-
|
|
154
|
-
|
|
157
|
+
result = cache.get(k)
|
|
158
|
+
if inspect.isawaitable(result):
|
|
159
|
+
cached_bytes = await result
|
|
155
160
|
else:
|
|
156
|
-
cached_bytes =
|
|
161
|
+
cached_bytes = result
|
|
157
162
|
except Exception:
|
|
158
163
|
pass
|
|
159
164
|
|
|
@@ -166,10 +171,9 @@ def cached(
|
|
|
166
171
|
# SAFE SET
|
|
167
172
|
data = serializer(result)
|
|
168
173
|
try:
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
cache.set(k, data, ttl=ttl, tags=tg)
|
|
174
|
+
res = cache.set(k, data, ttl=ttl, tags=tg)
|
|
175
|
+
if inspect.isawaitable(res):
|
|
176
|
+
await res
|
|
173
177
|
except Exception:
|
|
174
178
|
pass
|
|
175
179
|
|
|
@@ -190,10 +194,7 @@ def cached(
|
|
|
190
194
|
# SAFE GET (maybe async)
|
|
191
195
|
cached_bytes = None
|
|
192
196
|
try:
|
|
193
|
-
|
|
194
|
-
cached_bytes = _run_maybe_async(cache.get, k)
|
|
195
|
-
else:
|
|
196
|
-
cached_bytes = cache.get(k)
|
|
197
|
+
cached_bytes = _run_maybe_async(cache.get, k)
|
|
197
198
|
except Exception:
|
|
198
199
|
pass
|
|
199
200
|
|
|
@@ -206,10 +207,7 @@ def cached(
|
|
|
206
207
|
# SAFE SET (maybe async)
|
|
207
208
|
data = serializer(result)
|
|
208
209
|
try:
|
|
209
|
-
|
|
210
|
-
_run_maybe_async(cache.set, k, data, ttl=ttl, tags=tg)
|
|
211
|
-
else:
|
|
212
|
-
cache.set(k, data, ttl=ttl, tags=tg)
|
|
210
|
+
_run_maybe_async(cache.set, k, data, ttl=ttl, tags=tg)
|
|
213
211
|
except Exception:
|
|
214
212
|
pass
|
|
215
213
|
|
|
@@ -227,7 +225,7 @@ def invalidate_by_tags(tags_builder: Callable[..., Iterable[str]]):
|
|
|
227
225
|
"""
|
|
228
226
|
|
|
229
227
|
def deco(func: Callable):
|
|
230
|
-
is_async_func =
|
|
228
|
+
is_async_func = inspect.iscoroutinefunction(func)
|
|
231
229
|
|
|
232
230
|
if is_async_func:
|
|
233
231
|
|
|
@@ -236,10 +234,11 @@ def invalidate_by_tags(tags_builder: Callable[..., Iterable[str]]):
|
|
|
236
234
|
out = await func(*args, **kwargs)
|
|
237
235
|
cache = get_cache()
|
|
238
236
|
tags = list(tags_builder(*args, **kwargs))
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
237
|
+
|
|
238
|
+
res = cache.invalidate_tags(tags)
|
|
239
|
+
if inspect.isawaitable(res):
|
|
240
|
+
await res
|
|
241
|
+
|
|
243
242
|
return out
|
|
244
243
|
|
|
245
244
|
return aw
|
|
@@ -249,10 +248,9 @@ def invalidate_by_tags(tags_builder: Callable[..., Iterable[str]]):
|
|
|
249
248
|
out = func(*args, **kwargs)
|
|
250
249
|
cache = get_cache()
|
|
251
250
|
tags = list(tags_builder(*args, **kwargs))
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
cache.invalidate_tags(tags)
|
|
251
|
+
|
|
252
|
+
_run_maybe_async(cache.invalidate_tags, tags)
|
|
253
|
+
|
|
256
254
|
return out
|
|
257
255
|
|
|
258
256
|
return sw
|
|
@@ -8,22 +8,22 @@ nlbone/adapters/auth/auth_service.py,sha256=l8SyskSyswas940i-hXEnJ-gboTpjsXURm_G
|
|
|
8
8
|
nlbone/adapters/auth/keycloak.py,sha256=IhEriaFl5mjIGT6ZUCU9qROd678ARchvWgd4UJ6zH7s,4925
|
|
9
9
|
nlbone/adapters/auth/token_provider.py,sha256=kzjFAaFY8SPnU0Tn6l-YVrhEOAiFV0QE3eit3D7u2VQ,1438
|
|
10
10
|
nlbone/adapters/cache/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
-
nlbone/adapters/cache/async_redis.py,sha256=
|
|
11
|
+
nlbone/adapters/cache/async_redis.py,sha256=GRooGZvGdPih9XOSe_lnmV-jQFrg-vZRARdNmOWOkQw,7380
|
|
12
12
|
nlbone/adapters/cache/memory.py,sha256=y8M4erHQXApiSMAqG6Qk4pxEb60hRdu1szPv6iqvO9c,3738
|
|
13
13
|
nlbone/adapters/cache/pubsub_listener.py,sha256=3vfK4O4EzuQQhTsbZ_bweP06o99kDSyHJ5PrfUotUaE,1460
|
|
14
|
-
nlbone/adapters/cache/redis.py,sha256=
|
|
14
|
+
nlbone/adapters/cache/redis.py,sha256=cVBEfT2bO7bF8lq_G50EcIDmXgLyRbeTCCxRl1tGFmA,5445
|
|
15
15
|
nlbone/adapters/db/__init__.py,sha256=0CDSySEk4jJsqmwI0eNuaaLJOJDt8_iSiHBsFdC-L3s,212
|
|
16
16
|
nlbone/adapters/db/postgres/__init__.py,sha256=tvCpHOdZbpQ57o7k-plq7L0e1uZe5_Frbh7I-LxW7zM,313
|
|
17
17
|
nlbone/adapters/db/postgres/audit.py,sha256=IuWkPitr70UyQ6-GkAedckp8U-Z4cTgzFbdt_bQv1VQ,4800
|
|
18
18
|
nlbone/adapters/db/postgres/base.py,sha256=I89PsEeR9ADEScG8D5pVSncPrPRBmf-KQQkjajl7Koo,132
|
|
19
|
-
nlbone/adapters/db/postgres/engine.py,sha256=
|
|
19
|
+
nlbone/adapters/db/postgres/engine.py,sha256=8bA5qbDN9kcbI2uFitxbD8bseRlI_wQRQQSgfRTH6l8,3812
|
|
20
20
|
nlbone/adapters/db/postgres/query_builder.py,sha256=YSlrj7lEGI9RiJ__El5_4j7nCuFogg4pR0oVcBQ9h90,15836
|
|
21
21
|
nlbone/adapters/db/postgres/repository.py,sha256=n01TAzdKd-UbOhirE6KMosuvRdJG2l1cszwVHjTM-Ks,10345
|
|
22
22
|
nlbone/adapters/db/postgres/schema.py,sha256=NlE7Rr8uXypsw4oWkdZhZwcIBHQEPIpoHLxcUo98i6s,1039
|
|
23
23
|
nlbone/adapters/db/postgres/types.py,sha256=0SVuIKokog6_ByrYUsYAoIypVM2-uKJhUTeDPtm0qhs,602
|
|
24
24
|
nlbone/adapters/db/postgres/uow.py,sha256=I4RVeIbGEVhVGcuzhdEUJuX11RhEHTn0egrkCbcsz24,3852
|
|
25
25
|
nlbone/adapters/db/redis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
-
nlbone/adapters/db/redis/client.py,sha256=
|
|
26
|
+
nlbone/adapters/db/redis/client.py,sha256=cT9TCigE-ndB-ckcpuh8cTS1AOVKS3rWNG_WYxD0CAM,930
|
|
27
27
|
nlbone/adapters/http_clients/__init__.py,sha256=w-Yr9CLuXMU71N0Ada5HbvP1DB53wqeP6B-i5rALlTo,150
|
|
28
28
|
nlbone/adapters/http_clients/pricing/__init__.py,sha256=ElA9NFcAR9u4cqb_w3PPqKU3xGeyjNLQ8veJ0ql2iz0,81
|
|
29
29
|
nlbone/adapters/http_clients/pricing/pricing_service.py,sha256=_15vyEwJD3S2DJG-yyKhKfiLDbcwZg5XdQFe0b3oadM,3441
|
|
@@ -47,7 +47,7 @@ nlbone/adapters/ticketing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
|
|
|
47
47
|
nlbone/adapters/ticketing/client.py,sha256=J1-eT3qQDAJqrHcVpP1oqWNsRNnJ54dDdBeez-m9wyY,1291
|
|
48
48
|
nlbone/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
49
49
|
nlbone/config/logging.py,sha256=Ot6Ctf7EQZlW8YNB-uBdleqI6wixn5fH0Eo6QRgNkQk,4358
|
|
50
|
-
nlbone/config/settings.py,sha256=
|
|
50
|
+
nlbone/config/settings.py,sha256=4OLG9M9wB5H6slNcg61NuNEPoWQsjoA6RvesRYa1C7c,5330
|
|
51
51
|
nlbone/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
52
52
|
nlbone/core/application/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
53
53
|
nlbone/core/application/base_worker.py,sha256=5brIToSd-vi6tw0ukhHnUZGZhOLq1SQ-NRRy-kp6D24,1193
|
|
@@ -70,7 +70,7 @@ nlbone/core/ports/translation.py,sha256=pnqbxhdRCR7eprm8UI8ZKKx7VDUPntvBtlytrnTG
|
|
|
70
70
|
nlbone/core/ports/uow.py,sha256=VhqSc-Ryt9m-rlNMiXTzD3dPGz6mM_JxND8D0UJGRu4,962
|
|
71
71
|
nlbone/interfaces/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
72
72
|
nlbone/interfaces/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
73
|
-
nlbone/interfaces/api/exception_handlers.py,sha256=
|
|
73
|
+
nlbone/interfaces/api/exception_handlers.py,sha256=YpnYAwmmWaLLJs7YKLdspWuVjCkelpPnVBw5VQkTkco,5494
|
|
74
74
|
nlbone/interfaces/api/exceptions.py,sha256=IggZxV9q6l4jqw-G7SWEmuyXnWgbNXJJT-rmnirRIK4,5057
|
|
75
75
|
nlbone/interfaces/api/routers.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
76
76
|
nlbone/interfaces/api/schemas.py,sha256=34Tz2EeXyf12rFL9iyYWaB2ftuaXUebQQQxSO9ouV94,133
|
|
@@ -81,15 +81,15 @@ nlbone/interfaces/api/additional_filed/resolver.py,sha256=jv1TIBBHN4LBIMwHGipcy4
|
|
|
81
81
|
nlbone/interfaces/api/additional_filed/default_field_rules/__init__.py,sha256=LUSAOO3xRUt5ptlraIx7H-7dSkdr1D-WprmnqXRB16g,48
|
|
82
82
|
nlbone/interfaces/api/additional_filed/default_field_rules/image_field_rules.py,sha256=ecKqPeXZ-YiF14RK9PmK7ln3PCzpCUc18S5zm5IF3fw,339
|
|
83
83
|
nlbone/interfaces/api/dependencies/__init__.py,sha256=rnYRrFVZCfICQrp_PVFlzNg3BeC57yM08wn2DbOHCfk,359
|
|
84
|
-
nlbone/interfaces/api/dependencies/async_auth.py,sha256=
|
|
85
|
-
nlbone/interfaces/api/dependencies/auth.py,sha256=
|
|
84
|
+
nlbone/interfaces/api/dependencies/async_auth.py,sha256=TZlFzT-mnPz1WBL0wh3nOlBXDjY-7B0-b4wBT8O6pLM,1890
|
|
85
|
+
nlbone/interfaces/api/dependencies/auth.py,sha256=4WrjR3UgHI4S-HnBwdyLKEKBtofHkNuJ63Piayu9Dh4,3200
|
|
86
86
|
nlbone/interfaces/api/dependencies/client_credential.py,sha256=Bo4dYx75Qw0JzTKD9ZfV5EXDEOuwndJk2D-V37K2ePg,1293
|
|
87
87
|
nlbone/interfaces/api/dependencies/db.py,sha256=-UD39J_86UU7ZJs2ZncpdND0yhAG0NeeeALrgSDuuFw,466
|
|
88
88
|
nlbone/interfaces/api/dependencies/uow.py,sha256=QfLEvLYLNWZJQN1k-0q0hBVtUld3D75P4j39q_RjcnE,1181
|
|
89
89
|
nlbone/interfaces/api/middleware/__init__.py,sha256=zbX2vaEAfxRMIYwO2MVY_2O6bqG5H9o7HqGpX14U3Is,158
|
|
90
90
|
nlbone/interfaces/api/middleware/access_log.py,sha256=vIkxxxfy2HcjqqKb8XCfGCcSrivAC8u6ie75FMq5x-U,1032
|
|
91
91
|
nlbone/interfaces/api/middleware/add_request_context.py,sha256=o8mdo-D6fODM9OyHunE5UodkVxsh4F__5tDv8ju8Sxg,1952
|
|
92
|
-
nlbone/interfaces/api/middleware/authentication.py,sha256=
|
|
92
|
+
nlbone/interfaces/api/middleware/authentication.py,sha256=dWxA9Aw8eFUqsufT2mHGk6mgehXKdAA9AAEIOk_jZWY,3326
|
|
93
93
|
nlbone/interfaces/api/pagination/__init__.py,sha256=pA1uC4rK6eqDI5IkLVxmgO2B6lExnOm8Pje2-hifJZw,431
|
|
94
94
|
nlbone/interfaces/api/pagination/offset_base.py,sha256=pdfNgmP99eFC5qCWyY1JgW8hNhOuEGnmrlvQPGArdj8,4709
|
|
95
95
|
nlbone/interfaces/api/schema/__init__.py,sha256=LAqgynfupeqOQ6u0I5ucrcYnojRMZUg9yW8IjKSQTNI,119
|
|
@@ -104,7 +104,7 @@ nlbone/interfaces/jobs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
|
|
|
104
104
|
nlbone/interfaces/jobs/dispatch_outbox.py,sha256=yLZSC3nvkgxT2LL4Pq_DYzCyf_tZB-FknrjjgN89GFg,809
|
|
105
105
|
nlbone/interfaces/jobs/sync_tokens.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
106
106
|
nlbone/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
107
|
-
nlbone/utils/cache.py,sha256=
|
|
107
|
+
nlbone/utils/cache.py,sha256=DkOSwKXG6nShaUq5_MfZIxFWXem_CIZpbgO4CJEaRrE,6918
|
|
108
108
|
nlbone/utils/cache_keys.py,sha256=Y2YSellHTbUOcoaNbl1jaD4r485VU_e4KXsfBWhYTBo,1075
|
|
109
109
|
nlbone/utils/cache_registry.py,sha256=3FWYyhujW8oPBiVUPzk1CqJ3jJfxs9729Sbb1pQ5Fag,707
|
|
110
110
|
nlbone/utils/context.py,sha256=Wq3QLYsMzo_xUiVAHLgEPQUG6LhgJTmFn8MO5Qa7S8w,1837
|
|
@@ -116,8 +116,8 @@ nlbone/utils/normalize_mobile.py,sha256=sGH4tV9gX-6eVKozviNWJhm1DN1J28Nj-ERldCYk
|
|
|
116
116
|
nlbone/utils/read_files.py,sha256=mx8dfvtaaARQFRp_U7OOiERg-GT62h09_lpTzIQsVhs,291
|
|
117
117
|
nlbone/utils/redactor.py,sha256=-V4HrHmHwPi3Kez587Ek1uJlgK35qGSrwBOvcbw8Jas,1279
|
|
118
118
|
nlbone/utils/time.py,sha256=DjjyQ9GLsfXoT6NK8RDW2rOlJg3e6sF04Jw6PBUrSvg,1268
|
|
119
|
-
nlbone-0.9.
|
|
120
|
-
nlbone-0.9.
|
|
121
|
-
nlbone-0.9.
|
|
122
|
-
nlbone-0.9.
|
|
123
|
-
nlbone-0.9.
|
|
119
|
+
nlbone-0.9.1.dist-info/METADATA,sha256=zXLVlrhKwEg_MAP8KqHT1DGOF21PHj5f4YOVM6cUPZo,2294
|
|
120
|
+
nlbone-0.9.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
121
|
+
nlbone-0.9.1.dist-info/entry_points.txt,sha256=CpIL45t5nbhl1dGQPhfIIDfqqak3teK0SxPGBBr7YCk,59
|
|
122
|
+
nlbone-0.9.1.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
123
|
+
nlbone-0.9.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|