nlbone 0.6.0__py3-none-any.whl → 0.6.8__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.
Files changed (37) hide show
  1. nlbone/adapters/__init__.py +1 -0
  2. nlbone/adapters/auth/keycloak.py +1 -1
  3. nlbone/adapters/auth/token_provider.py +1 -1
  4. nlbone/adapters/cache/async_redis.py +18 -8
  5. nlbone/adapters/cache/memory.py +21 -11
  6. nlbone/adapters/cache/pubsub_listener.py +3 -0
  7. nlbone/adapters/cache/redis.py +23 -8
  8. nlbone/adapters/db/__init__.py +0 -1
  9. nlbone/adapters/db/postgres/audit.py +14 -11
  10. nlbone/adapters/db/redis/client.py +1 -4
  11. nlbone/adapters/http_clients/__init__.py +2 -2
  12. nlbone/adapters/http_clients/pricing/__init__.py +1 -1
  13. nlbone/adapters/http_clients/pricing/pricing_service.py +22 -18
  14. nlbone/adapters/http_clients/uploadchi/__init__.py +1 -1
  15. nlbone/adapters/http_clients/uploadchi/uploadchi.py +12 -12
  16. nlbone/adapters/http_clients/uploadchi/uploadchi_async.py +14 -15
  17. nlbone/adapters/percolation/__init__.py +1 -1
  18. nlbone/adapters/percolation/connection.py +2 -1
  19. nlbone/config/logging.py +54 -24
  20. nlbone/container.py +10 -6
  21. nlbone/core/application/base_worker.py +1 -1
  22. nlbone/core/domain/models.py +4 -2
  23. nlbone/core/ports/cache.py +25 -9
  24. nlbone/interfaces/api/dependencies/auth.py +26 -0
  25. nlbone/interfaces/cli/init_db.py +1 -1
  26. nlbone/interfaces/cli/main.py +6 -5
  27. nlbone/utils/cache.py +10 -0
  28. nlbone/utils/cache_keys.py +6 -0
  29. nlbone/utils/cache_registry.py +5 -2
  30. nlbone/utils/http.py +1 -1
  31. nlbone/utils/redactor.py +2 -1
  32. nlbone/utils/time.py +1 -1
  33. {nlbone-0.6.0.dist-info → nlbone-0.6.8.dist-info}/METADATA +1 -1
  34. {nlbone-0.6.0.dist-info → nlbone-0.6.8.dist-info}/RECORD +37 -37
  35. {nlbone-0.6.0.dist-info → nlbone-0.6.8.dist-info}/WHEEL +0 -0
  36. {nlbone-0.6.0.dist-info → nlbone-0.6.8.dist-info}/entry_points.txt +0 -0
  37. {nlbone-0.6.0.dist-info → nlbone-0.6.8.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1 @@
1
+ import nlbone.adapters.db.postgres.audit
@@ -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,6 +1,6 @@
1
1
  import threading
2
2
  import time
3
- from typing import Optional, Dict, Any
3
+ from typing import Any, Dict, Optional
4
4
 
5
5
  from nlbone.adapters.auth.keycloak import KeycloakAuthService
6
6
 
@@ -1,14 +1,20 @@
1
1
  import asyncio
2
2
  import json
3
3
  import os
4
- from typing import Optional, Iterable, Any, Mapping, Sequence, List
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: return f"nsver:{ns}"
11
- def _tag_key(tag: str) -> str: return f"tag:{tag}"
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(self, key: str, value: bytes, *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None) -> None:
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(self, items: Mapping[str, bytes], *, ttl: Optional[int] = None,
70
- tags: Optional[Iterable[str]] = None) -> None:
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(self, key: str, value: Any, *, ttl: Optional[int] = None,
97
- tags: Optional[Iterable[str]] = None) -> None:
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 --------
@@ -1,5 +1,8 @@
1
- import json, threading, time
2
- from typing import Optional, Iterable, Any, Mapping, Sequence, Dict, Set
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: return True
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: return
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: return None
58
+ if not v:
59
+ return None
54
60
  _, exp = v
55
- if exp is None: return 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(self, items: Mapping[str, bytes], *, ttl: Optional[int] = None,
63
- tags: Optional[Iterable[str]] = None) -> None:
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(self, key: str, value: Any, *, ttl: Optional[int] = None,
72
- tags: Optional[Iterable[str]] = None) -> None:
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: self.delete(k)
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",
@@ -1,13 +1,22 @@
1
1
  from __future__ import annotations
2
- import json, os, time
3
- from typing import Optional, Iterable, Any, Mapping, Sequence, List, Set
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(self, items: Mapping[str, bytes], *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None) -> None:
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(self, key: str, value: Any, *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None) -> None:
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); cnt += len(keys)
113
- if cursor == 0: break
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:
@@ -1,4 +1,3 @@
1
1
  from .postgres import apply_pagination, get_paginated_response
2
2
  from .postgres.base import Base
3
3
  from .postgres.engine import async_ping, async_session, init_async_engine, init_sync_engine, sync_ping, sync_session
4
- import nlbone.adapters.db.postgres.audit
@@ -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
- from sqlalchemy import event, inspect as sa_inspect
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 getattr(getattr(obj, "__table__", None), "name", None) or getattr(obj, "__tablename__",
58
- None) or obj.__class__.__name__
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 # History object
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:
@@ -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 .uploadchi import UploadchiClient, UploadchiAsyncClient, UploadchiError
2
- from .pricing import PricingService, CalculatePriceIn, CalculatePriceOut
1
+ from .pricing import CalculatePriceIn, CalculatePriceOut, PricingService
2
+ from .uploadchi import UploadchiAsyncClient, UploadchiClient, UploadchiError
@@ -1 +1 @@
1
- from .pricing_service import PricingService, CalculatePriceIn, CalculatePriceOut
1
+ from .pricing_service import CalculatePriceIn, CalculatePriceOut, PricingService
@@ -1,5 +1,5 @@
1
1
  from enum import Enum
2
- from typing import Optional, Literal, List
2
+ from typing import List, Literal, Optional
3
3
 
4
4
  import httpx
5
5
  import requests
@@ -7,7 +7,7 @@ from pydantic import BaseModel, Field, NonNegativeInt, RootModel
7
7
 
8
8
  from nlbone.adapters.auth.token_provider import ClientTokenProvider
9
9
  from nlbone.config.settings import get_settings
10
- from nlbone.utils.http import normalize_https_base, auth_headers
10
+ from nlbone.utils.http import auth_headers, normalize_https_base
11
11
 
12
12
 
13
13
  class PricingError(Exception):
@@ -33,10 +33,17 @@ class Product(BaseModel):
33
33
 
34
34
  class Pricing(BaseModel):
35
35
  source: Literal["formula", "static"]
36
- price: float
36
+ price: Optional[float] = None
37
37
  discount: Optional[float] = None
38
38
  discount_type: Optional[DiscountType] = None
39
- params: dict
39
+ params: Optional[dict] = None
40
+
41
+
42
+ class Segment(BaseModel):
43
+ id: str
44
+ name: str
45
+ specificity: int
46
+ matched_fields: list
40
47
 
41
48
 
42
49
  class Formula(BaseModel):
@@ -49,10 +56,8 @@ class Formula(BaseModel):
49
56
 
50
57
  class PricingRule(BaseModel):
51
58
  product: Product
52
- segment_name: str | None
59
+ segment: Segment | None
53
60
  formula: Optional[Formula] = None
54
- specificity: int
55
- matched_fields: list
56
61
  pricing: Pricing
57
62
 
58
63
 
@@ -62,11 +67,11 @@ class CalculatePriceOut(RootModel[List[PricingRule]]):
62
67
 
63
68
  class PricingService:
64
69
  def __init__(
65
- self,
66
- token_provider: ClientTokenProvider,
67
- base_url: Optional[str] = None,
68
- timeout_seconds: Optional[float] = None,
69
- client: httpx.Client | None = None,
70
+ self,
71
+ token_provider: ClientTokenProvider,
72
+ base_url: Optional[str] = None,
73
+ timeout_seconds: Optional[float] = None,
74
+ client: httpx.Client | None = None,
70
75
  ) -> None:
71
76
  s = get_settings()
72
77
  self._base_url = normalize_https_base(base_url or str(s.PRICING_SERVICE_URL), enforce_https=False)
@@ -74,17 +79,16 @@ class PricingService:
74
79
  self._client = client or requests.session()
75
80
  self._token_provider = token_provider
76
81
 
77
- def calculate(self, items: list[CalculatePriceIn]) -> CalculatePriceOut:
78
- body = {
79
- "items": [i.model_dump() for i in items]
80
- }
82
+ def calculate(self, items: list[CalculatePriceIn], response: Literal["list", "dict"] = "dict") -> CalculatePriceOut:
83
+ body = {"items": [i.model_dump() for i in items]}
81
84
 
82
85
  r = self._client.post(
83
- f"{self._base_url}/priced",
86
+ f"{self._base_url}/price/calculate",
87
+ params={"response": response},
84
88
  headers=auth_headers(self._token_provider.get_access_token()),
85
89
  json=body,
86
90
  timeout=self._timeout,
87
- verify=False
91
+ verify=False,
88
92
  )
89
93
 
90
94
  if r.status_code not in (200, 204):
@@ -1,2 +1,2 @@
1
1
  from .uploadchi import UploadchiClient, UploadchiError
2
- from .uploadchi_async import UploadchiAsyncClient
2
+ from .uploadchi_async import UploadchiAsyncClient
@@ -35,11 +35,11 @@ def _filename_from_cd(cd: str | None, fallback: str) -> str:
35
35
 
36
36
  class UploadchiClient(FileServicePort):
37
37
  def __init__(
38
- self,
39
- token_provider: ClientTokenProvider | None = None,
40
- base_url: Optional[str] = None,
41
- timeout_seconds: Optional[float] = None,
42
- client: httpx.Client | None = None,
38
+ self,
39
+ token_provider: ClientTokenProvider | None = None,
40
+ base_url: Optional[str] = None,
41
+ timeout_seconds: Optional[float] = None,
42
+ client: httpx.Client | None = None,
43
43
  ) -> None:
44
44
  s = get_settings()
45
45
  self._base_url = normalize_https_base(base_url or str(s.UPLOADCHI_BASE_URL))
@@ -51,7 +51,7 @@ class UploadchiClient(FileServicePort):
51
51
  self._client.close()
52
52
 
53
53
  def upload_file(
54
- self, file_bytes: bytes, filename: str, params: dict[str, Any] | None = None, token: str | None = None
54
+ self, file_bytes: bytes, filename: str, params: dict[str, Any] | None = None, token: str | None = None
55
55
  ) -> dict:
56
56
  tok = _resolve_token(token)
57
57
  files = {"file": (filename, file_bytes)}
@@ -84,12 +84,12 @@ class UploadchiClient(FileServicePort):
84
84
  raise UploadchiError(r.status_code, r.text)
85
85
 
86
86
  def list_files(
87
- self,
88
- limit: int = 10,
89
- offset: int = 0,
90
- filters: dict[str, Any] | None = None,
91
- sort: list[tuple[str, str]] | None = None,
92
- token: str | None = None,
87
+ self,
88
+ limit: int = 10,
89
+ offset: int = 0,
90
+ filters: dict[str, Any] | None = None,
91
+ sort: list[tuple[str, str]] | None = None,
92
+ token: str | None = None,
93
93
  ) -> dict:
94
94
  tok = _resolve_token(token)
95
95
  q = build_list_query(limit, offset, filters, sort)
@@ -4,21 +4,20 @@ from typing import Any, AsyncIterator, Optional
4
4
 
5
5
  import httpx
6
6
 
7
+ from nlbone.adapters.auth.token_provider import ClientTokenProvider
8
+ from nlbone.adapters.http_clients.uploadchi.uploadchi import UploadchiError, _filename_from_cd, _resolve_token
7
9
  from nlbone.config.settings import get_settings
8
10
  from nlbone.core.ports.files import AsyncFileServicePort
9
-
10
- from nlbone.adapters.http_clients.uploadchi.uploadchi import UploadchiError, _filename_from_cd, _resolve_token
11
- from nlbone.adapters.auth.token_provider import ClientTokenProvider
12
11
  from nlbone.utils.http import auth_headers, build_list_query
13
12
 
14
13
 
15
14
  class UploadchiAsyncClient(AsyncFileServicePort):
16
15
  def __init__(
17
- self,
18
- token_provider: ClientTokenProvider | None = None,
19
- base_url: Optional[str] = None,
20
- timeout_seconds: Optional[float] = None,
21
- client: httpx.AsyncClient | None = None,
16
+ self,
17
+ token_provider: ClientTokenProvider | None = None,
18
+ base_url: Optional[str] = None,
19
+ timeout_seconds: Optional[float] = None,
20
+ client: httpx.AsyncClient | None = None,
22
21
  ) -> None:
23
22
  s = get_settings()
24
23
  self._base_url = base_url or str(s.UPLOADCHI_BASE_URL)
@@ -32,7 +31,7 @@ class UploadchiAsyncClient(AsyncFileServicePort):
32
31
  await self._client.aclose()
33
32
 
34
33
  async def upload_file(
35
- self, file_bytes: bytes, filename: str, params: dict[str, Any] | None = None, token: str | None = None
34
+ self, file_bytes: bytes, filename: str, params: dict[str, Any] | None = None, token: str | None = None
36
35
  ) -> dict:
37
36
  tok = _resolve_token(token)
38
37
  files = {"file": (filename, file_bytes)}
@@ -63,12 +62,12 @@ class UploadchiAsyncClient(AsyncFileServicePort):
63
62
  raise UploadchiError(r.status_code, await r.aread())
64
63
 
65
64
  async def list_files(
66
- self,
67
- limit: int = 10,
68
- offset: int = 0,
69
- filters: dict[str, Any] | None = None,
70
- sort: list[tuple[str, str]] | None = None,
71
- token: str | None = None,
65
+ self,
66
+ limit: int = 10,
67
+ offset: int = 0,
68
+ filters: dict[str, Any] | None = None,
69
+ sort: list[tuple[str, str]] | None = None,
70
+ token: str | None = None,
72
71
  ) -> dict:
73
72
  tok = _resolve_token(token)
74
73
  q = build_list_query(limit, offset, filters, sort)
@@ -1 +1 @@
1
- from .connection import get_es_client
1
+ from .connection import get_es_client
@@ -4,9 +4,10 @@ from nlbone.config.settings import get_settings
4
4
 
5
5
  setting = get_settings()
6
6
 
7
+
7
8
  def get_es_client():
8
9
  es = Elasticsearch(
9
10
  setting.ELASTIC_PERCOLATE_URL,
10
- basic_auth=(setting.ELASTIC_PERCOLATE_USER, setting.ELASTIC_PERCOLATE_PASS.get_secret_value().strip())
11
+ basic_auth=(setting.ELASTIC_PERCOLATE_USER, setting.ELASTIC_PERCOLATE_PASS.get_secret_value().strip()),
11
12
  )
12
13
  return es
nlbone/config/logging.py CHANGED
@@ -11,6 +11,7 @@ from nlbone.utils.redactor import PiiRedactor
11
11
 
12
12
  settings = get_settings()
13
13
 
14
+
14
15
  # ---------- Filters ----------
15
16
  class ContextFilter(logging.Filter):
16
17
  def filter(self, record: logging.LogRecord) -> bool:
@@ -21,12 +22,32 @@ class ContextFilter(logging.Filter):
21
22
  record.user_agent = ctx.get("user_agent")
22
23
  return True
23
24
 
25
+
24
26
  # ---------- Formatter ----------
25
27
  class JsonFormatter(logging.Formatter):
26
28
  RESERVED = {
27
- "args","asctime","created","exc_info","exc_text","filename","funcName","levelname","levelno",
28
- "lineno","module","msecs","message","msg","name","pathname","process","processName",
29
- "relativeCreated","stack_info","thread","threadName",
29
+ "args",
30
+ "asctime",
31
+ "created",
32
+ "exc_info",
33
+ "exc_text",
34
+ "filename",
35
+ "funcName",
36
+ "levelname",
37
+ "levelno",
38
+ "lineno",
39
+ "module",
40
+ "msecs",
41
+ "message",
42
+ "msg",
43
+ "name",
44
+ "pathname",
45
+ "process",
46
+ "processName",
47
+ "relativeCreated",
48
+ "stack_info",
49
+ "thread",
50
+ "threadName",
30
51
  }
31
52
 
32
53
  def format(self, record: logging.LogRecord) -> str:
@@ -53,17 +74,23 @@ class JsonFormatter(logging.Formatter):
53
74
 
54
75
  return json.dumps(payload, ensure_ascii=False)
55
76
 
77
+
56
78
  class PlainFormatter(logging.Formatter):
57
79
  def __init__(self):
58
80
  super().__init__(
59
- fmt="%(asctime)s | %(levelname)s | %(name)s | "
60
- "req=%(request_id)s user=%(user_id)s ip=%(ip)s | %(message)s",
81
+ fmt="%(asctime)s | %(levelname)s | %(name)s | req=%(request_id)s user=%(user_id)s ip=%(ip)s | %(message)s",
61
82
  datefmt="%Y-%m-%dT%H:%M:%S%z",
62
83
  )
63
84
 
85
+
64
86
  # ---------- Setup ----------
65
- def setup_logging(*, log_json: bool=settings.LOG_JSON, log_level: str =settings.LOG_LEVEL,
66
- log_file: str | None = None, silence_uvicorn_access: bool = True):
87
+ def setup_logging(
88
+ *,
89
+ log_json: bool = settings.LOG_JSON,
90
+ log_level: str = settings.LOG_LEVEL,
91
+ log_file: str | None = None,
92
+ silence_uvicorn_access: bool = True,
93
+ ):
67
94
  handlers = {
68
95
  "console": {
69
96
  "class": "logging.StreamHandler",
@@ -82,23 +109,25 @@ def setup_logging(*, log_json: bool=settings.LOG_JSON, log_level: str =settings.
82
109
  "formatter": "json" if log_json else "plain",
83
110
  }
84
111
 
85
- dictConfig({
86
- "version": 1,
87
- "disable_existing_loggers": False,
88
- "filters": {
89
- "ctx": {"()": ContextFilter},
90
- "pii": {"()": PiiRedactor},
91
- },
92
- "formatters": {
93
- "json": {"()": JsonFormatter},
94
- "plain": {"()": PlainFormatter},
95
- },
96
- "handlers": handlers,
97
- "root": {
98
- "level": log_level,
99
- "handlers": list(handlers.keys()),
100
- },
101
- })
112
+ dictConfig(
113
+ {
114
+ "version": 1,
115
+ "disable_existing_loggers": False,
116
+ "filters": {
117
+ "ctx": {"()": ContextFilter},
118
+ "pii": {"()": PiiRedactor},
119
+ },
120
+ "formatters": {
121
+ "json": {"()": JsonFormatter},
122
+ "plain": {"()": PlainFormatter},
123
+ },
124
+ "handlers": handlers,
125
+ "root": {
126
+ "level": log_level,
127
+ "handlers": list(handlers.keys()),
128
+ },
129
+ }
130
+ )
102
131
 
103
132
  logging.getLogger("asyncio").setLevel(logging.WARNING)
104
133
  logging.getLogger("httpx").setLevel(logging.WARNING)
@@ -115,5 +144,6 @@ def setup_logging(*, log_json: bool=settings.LOG_JSON, log_level: str =settings.
115
144
  uvicorn_access.handlers = []
116
145
  uvicorn_access.propagate = True
117
146
 
147
+
118
148
  def get_logger(name: str | None = None) -> logging.Logger:
119
149
  return logging.getLogger(name or "app")
nlbone/container.py CHANGED
@@ -16,7 +16,7 @@ from nlbone.adapters.http_clients.uploadchi import UploadchiClient
16
16
  from nlbone.adapters.http_clients.uploadchi.uploadchi_async import UploadchiAsyncClient
17
17
  from nlbone.adapters.messaging import InMemoryEventBus
18
18
  from nlbone.core.ports import EventBusPort
19
- from nlbone.core.ports.cache import CachePort, AsyncCachePort
19
+ from nlbone.core.ports.cache import AsyncCachePort, CachePort
20
20
  from nlbone.core.ports.files import AsyncFileServicePort, FileServicePort
21
21
 
22
22
 
@@ -36,11 +36,15 @@ class Container(containers.DeclarativeContainer):
36
36
  # --- Services ---
37
37
  auth: providers.Singleton[KeycloakAuthService] = providers.Singleton(KeycloakAuthService, settings=config)
38
38
  token_provider = providers.Singleton(ClientTokenProvider, auth=auth, skew_seconds=30)
39
- file_service: providers.Singleton[FileServicePort] = providers.Singleton(UploadchiClient,
40
- token_provider=token_provider)
41
- afiles_service: providers.Singleton[AsyncFileServicePort] = providers.Singleton(UploadchiAsyncClient,
42
- token_provider=token_provider)
43
- pricing_service: providers.Singleton[PricingService] = providers.Singleton(PricingService, token_provider=token_provider)
39
+ file_service: providers.Singleton[FileServicePort] = providers.Singleton(
40
+ UploadchiClient, token_provider=token_provider
41
+ )
42
+ afiles_service: providers.Singleton[AsyncFileServicePort] = providers.Singleton(
43
+ UploadchiAsyncClient, token_provider=token_provider
44
+ )
45
+ pricing_service: providers.Singleton[PricingService] = providers.Singleton(
46
+ PricingService, token_provider=token_provider
47
+ )
44
48
 
45
49
  cache: providers.Singleton[CachePort] = providers.Selector(
46
50
  config.CACHE_BACKEND,
@@ -33,4 +33,4 @@ class BaseWorker(ABC):
33
33
 
34
34
  @abstractmethod
35
35
  async def process(self, *args, **kwargs) -> Any:
36
- pass
36
+ pass
@@ -1,7 +1,8 @@
1
1
  import uuid
2
2
  from datetime import datetime
3
- from sqlalchemy import String, DateTime, Index, Text
3
+
4
4
  from sqlalchemy import JSON as SA_JSON
5
+ from sqlalchemy import DateTime, Index, String, Text
5
6
  from sqlalchemy.orm import Mapped, mapped_column
6
7
  from sqlalchemy.sql import func
7
8
 
@@ -9,6 +10,7 @@ from nlbone.adapters.db import Base
9
10
 
10
11
  try:
11
12
  from sqlalchemy.dialects.postgresql import JSONB, UUID
13
+
12
14
  JSONType = JSONB
13
15
  UUIDType = UUID(as_uuid=True)
14
16
  except Exception:
@@ -35,4 +37,4 @@ class AuditLog(Base):
35
37
  __table_args__ = (
36
38
  Index("ix_audit_entity_entityid", "entity", "entity_id"),
37
39
  Index("ix_audit_created_at", "created_at"),
38
- )
40
+ )
@@ -1,37 +1,53 @@
1
- from typing import Protocol, Optional, Iterable, Any, Mapping, Sequence, Tuple, TypeVar, Callable
1
+ from typing import Any, Callable, Iterable, Mapping, Optional, Protocol, Sequence, TypeVar
2
2
 
3
3
  T = TypeVar("T")
4
4
 
5
+
5
6
  class CachePort(Protocol):
6
7
  def get(self, key: str) -> Optional[bytes]: ...
7
- def set(self, key: str, value: bytes, *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None) -> None: ...
8
+ def set(
9
+ self, key: str, value: bytes, *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None
10
+ ) -> None: ...
8
11
  def delete(self, key: str) -> None: ...
9
12
  def exists(self, key: str) -> bool: ...
10
13
  def ttl(self, key: str) -> Optional[int]: ...
11
14
 
12
15
  def mget(self, keys: Sequence[str]) -> list[Optional[bytes]]: ...
13
- def mset(self, items: Mapping[str, bytes], *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None) -> None: ...
16
+ def mset(
17
+ self, items: Mapping[str, bytes], *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None
18
+ ) -> None: ...
14
19
 
15
20
  def get_json(self, key: str) -> Optional[Any]: ...
16
- def set_json(self, key: str, value: Any, *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None) -> None: ...
21
+ def set_json(
22
+ self, key: str, value: Any, *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None
23
+ ) -> None: ...
17
24
 
18
25
  def invalidate_tags(self, tags: Iterable[str]) -> int: ...
19
26
  def bump_namespace(self, namespace: str) -> int: ... # versioned keys
20
27
  def clear_namespace(self, namespace: str) -> int: ...
21
28
 
22
- def get_or_set(self, key: str, producer: Callable[[], bytes], *, ttl: int, tags: Optional[Iterable[str]] = None) -> bytes: ...
29
+ def get_or_set(
30
+ self, key: str, producer: Callable[[], bytes], *, ttl: int, tags: Optional[Iterable[str]] = None
31
+ ) -> bytes: ...
32
+
23
33
 
24
34
  class AsyncCachePort(Protocol):
25
35
  async def get(self, key: str) -> Optional[bytes]: ...
26
- async def set(self, key: str, value: bytes, *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None) -> None: ...
36
+ async def set(
37
+ self, key: str, value: bytes, *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None
38
+ ) -> None: ...
27
39
  async def delete(self, key: str) -> None: ...
28
40
  async def exists(self, key: str) -> bool: ...
29
41
  async def ttl(self, key: str) -> Optional[int]: ...
30
42
  async def mget(self, keys: Sequence[str]) -> list[Optional[bytes]]: ...
31
- async def mset(self, items: Mapping[str, bytes], *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None) -> None: ...
43
+ async def mset(
44
+ self, items: Mapping[str, bytes], *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None
45
+ ) -> None: ...
32
46
  async def get_json(self, key: str) -> Optional[Any]: ...
33
- async def set_json(self, key: str, value: Any, *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None) -> None: ...
47
+ async def set_json(
48
+ self, key: str, value: Any, *, ttl: Optional[int] = None, tags: Optional[Iterable[str]] = None
49
+ ) -> None: ...
34
50
  async def invalidate_tags(self, tags: Iterable[str]) -> int: ...
35
51
  async def bump_namespace(self, namespace: str) -> int: ...
36
52
  async def clear_namespace(self, namespace: str) -> int: ...
37
- async def get_or_set(self, key: str, producer, *, ttl: int, tags: Optional[Iterable[str]] = None) -> bytes: ...
53
+ async def get_or_set(self, key: str, producer, *, ttl: int, tags: Optional[Iterable[str]] = None) -> bytes: ...
@@ -59,3 +59,29 @@ def has_access(*, permissions=None):
59
59
  return wrapper
60
60
 
61
61
  return decorator
62
+
63
+
64
+ def client_or_user_has_access(*, permissions=None, client_permissions=None):
65
+ def decorator(func):
66
+ @functools.wraps(func)
67
+ def wrapper(*args, **kwargs):
68
+ request = current_request()
69
+ token = getattr(request.state, "token", None)
70
+ if not token:
71
+ raise UnauthorizedException()
72
+
73
+ auth = KeycloakAuthService()
74
+
75
+ if auth.get_client_id(token):
76
+ needed = client_permissions or permissions
77
+ if not auth.client_has_access(token, permissions=needed):
78
+ raise ForbiddenException(f"Forbidden (client) {needed}")
79
+ else:
80
+ if not current_user_id():
81
+ raise UnauthorizedException()
82
+ if not auth.has_access(token, permissions=permissions):
83
+ raise ForbiddenException(f"Forbidden (user) {permissions}")
84
+
85
+ return func(*args, **kwargs)
86
+ return wrapper
87
+ return decorator
@@ -1,6 +1,6 @@
1
1
  import typer
2
2
 
3
- from nlbone.adapters.db import init_sync_engine, Base, sync_ping
3
+ from nlbone.adapters.db import Base, init_sync_engine, sync_ping
4
4
 
5
5
  init_db_command = typer.Typer(help="Database utilities")
6
6
 
@@ -1,6 +1,7 @@
1
- import typer
2
1
  from typing import Optional
3
2
 
3
+ import typer
4
+
4
5
  from nlbone.adapters.db import init_sync_engine
5
6
  from nlbone.config.settings import get_settings
6
7
  from nlbone.interfaces.cli.init_db import init_db_command
@@ -9,12 +10,10 @@ app = typer.Typer(help="NLBone CLI")
9
10
 
10
11
  app.add_typer(init_db_command, name="db")
11
12
 
13
+
12
14
  @app.callback()
13
15
  def common(
14
- env_file: Optional[str] = typer.Option(
15
- None, "--env-file", "-e",
16
- help="Path to .env file. In prod omit this."
17
- ),
16
+ env_file: Optional[str] = typer.Option(None, "--env-file", "-e", help="Path to .env file. In prod omit this."),
18
17
  debug: bool = typer.Option(False, "--debug", help="Enable debug logging"),
19
18
  ):
20
19
  settings = get_settings(env_file=env_file)
@@ -22,8 +21,10 @@ def common(
22
21
  pass
23
22
  init_sync_engine(echo=settings.DEBUG)
24
23
 
24
+
25
25
  def main():
26
26
  app()
27
27
 
28
+
28
29
  if __name__ == "__main__":
29
30
  main()
nlbone/utils/cache.py CHANGED
@@ -4,17 +4,20 @@ import json
4
4
  from typing import Any, Callable, Iterable, Optional
5
5
 
6
6
  from makefun import wraps as mf_wraps
7
+
7
8
  from nlbone.utils.cache_registry import get_cache
8
9
 
9
10
  try:
10
11
  from pydantic import BaseModel # v1/v2
11
12
  except Exception: # pragma: no cover
13
+
12
14
  class BaseModel: # minimal fallback
13
15
  pass
14
16
 
15
17
 
16
18
  # -------- helpers --------
17
19
 
20
+
18
21
  def _bind(func: Callable, args, kwargs):
19
22
  sig = inspect.signature(func)
20
23
  bound = sig.bind_partial(*args, **kwargs)
@@ -79,6 +82,7 @@ def _run_maybe_async(func: Callable, *args, **kwargs):
79
82
 
80
83
  # -------- cache decorators --------
81
84
 
85
+
82
86
  def cached(
83
87
  *,
84
88
  ttl: int,
@@ -94,10 +98,12 @@ def cached(
94
98
  - Works with sync/async cache backends (CachePort / AsyncCachePort).
95
99
  - `key` & `tags` are string templates, e.g. "file:{file_id}".
96
100
  """
101
+
97
102
  def deco(func: Callable):
98
103
  is_async_func = asyncio.iscoroutinefunction(func)
99
104
 
100
105
  if is_async_func:
106
+
101
107
  @mf_wraps(func)
102
108
  async def aw(*args, **kwargs):
103
109
  cache = (cache_resolver or get_cache)()
@@ -165,10 +171,12 @@ def invalidate_by_tags(tags_builder: Callable[..., Iterable[str]]):
165
171
  Invalidate computed tags after function finishes.
166
172
  Works with sync or async functions and cache backends.
167
173
  """
174
+
168
175
  def deco(func: Callable):
169
176
  is_async_func = asyncio.iscoroutinefunction(func)
170
177
 
171
178
  if is_async_func:
179
+
172
180
  @mf_wraps(func)
173
181
  async def aw(*args, **kwargs):
174
182
  out = await func(*args, **kwargs)
@@ -179,6 +187,7 @@ def invalidate_by_tags(tags_builder: Callable[..., Iterable[str]]):
179
187
  else:
180
188
  cache.invalidate_tags(tags)
181
189
  return out
190
+
182
191
  return aw
183
192
 
184
193
  @mf_wraps(func)
@@ -191,6 +200,7 @@ def invalidate_by_tags(tags_builder: Callable[..., Iterable[str]]):
191
200
  else:
192
201
  cache.invalidate_tags(tags)
193
202
  return out
203
+
194
204
  return sw
195
205
 
196
206
  return deco
@@ -3,21 +3,26 @@ import json
3
3
  import random
4
4
  from typing import Any, Mapping
5
5
 
6
+
6
7
  def _stable_params(params: Mapping[str, Any]) -> str:
7
8
  return json.dumps(params, sort_keys=True, separators=(",", ":"))
8
9
 
10
+
9
11
  def make_key(ns: str, *parts: str) -> str:
10
12
  safe_parts = [p.replace(" ", "_") for p in parts if p]
11
13
  return f"{ns}:{':'.join(safe_parts)}" if safe_parts else f"{ns}:root"
12
14
 
15
+
13
16
  def make_param_key(ns: str, base: str, params: Mapping[str, Any]) -> str:
14
17
  payload = _stable_params(params)
15
18
  digest = hashlib.sha256(payload.encode("utf-8")).hexdigest()[:16]
16
19
  return f"{ns}:{base}:{digest}"
17
20
 
21
+
18
22
  def tag_entity(ns: str, entity_id: Any) -> str:
19
23
  return f"{ns}:{entity_id}"
20
24
 
25
+
21
26
  def tag_list(ns: str, **filters) -> str:
22
27
  if not filters:
23
28
  return f"{ns}:list"
@@ -25,6 +30,7 @@ def tag_list(ns: str, **filters) -> str:
25
30
  digest = hashlib.md5(payload.encode("utf-8")).hexdigest()[:12]
26
31
  return f"{ns}:list:{digest}"
27
32
 
33
+
28
34
  def ttl_with_jitter(base_ttl: int, *, jitter_ratio: float = 0.1) -> int:
29
35
  jitter = int(base_ttl * jitter_ratio)
30
36
  return base_ttl + random.randint(-jitter, jitter)
@@ -1,5 +1,5 @@
1
- from typing import Callable, Optional, TypeVar
2
1
  from contextvars import ContextVar
2
+ from typing import Callable, Optional, TypeVar
3
3
 
4
4
  T = TypeVar("T")
5
5
 
@@ -7,17 +7,20 @@ _global_resolver: Optional[Callable[[], T]] = None
7
7
 
8
8
  _ctx_resolver: ContextVar[Optional[Callable[[], T]]] = ContextVar("_ctx_resolver", default=None)
9
9
 
10
+
10
11
  def set_cache_resolver(fn: Callable[[], T]) -> None:
11
12
  """Set process-wide cache resolver (e.g., lambda: container.cache())."""
12
13
  global _global_resolver
13
14
  _global_resolver = fn
14
15
 
16
+
15
17
  def set_context_cache_resolver(fn: Optional[Callable[[], T]]) -> None:
16
18
  """Override resolver in current context (useful in tests/background tasks)."""
17
19
  _ctx_resolver.set(fn)
18
20
 
21
+
19
22
  def get_cache() -> T:
20
23
  fn = _ctx_resolver.get() or _global_resolver
21
24
  if fn is None:
22
25
  raise RuntimeError("Cache resolver not configured. Call set_cache_resolver(...) first.")
23
- return fn()
26
+ return fn()
nlbone/utils/http.py CHANGED
@@ -10,7 +10,7 @@ def auth_headers(token: str | None) -> dict[str, str]:
10
10
 
11
11
 
12
12
  def build_list_query(
13
- limit: int, offset: int, filters: dict[str, Any] | None, sort: list[tuple[str, str]] | None
13
+ limit: int, offset: int, filters: dict[str, Any] | None, sort: list[tuple[str, str]] | None
14
14
  ) -> dict[str, Any]:
15
15
  q: dict[str, Any] = {"limit": limit, "offset": offset}
16
16
  if filters:
nlbone/utils/redactor.py CHANGED
@@ -5,6 +5,7 @@ from typing import Any
5
5
 
6
6
  SENSITIVE_KEYS = {"password", "token", "access_token", "refresh_token", "secret", "card_number", "cvv", "pan"}
7
7
 
8
+
8
9
  class PiiRedactor(logging.Filter):
9
10
  def _redact_in_obj(self, obj: Any):
10
11
  if isinstance(obj, dict):
@@ -29,4 +30,4 @@ class PiiRedactor(logging.Filter):
29
30
  record.msg = self._redact_in_obj(record.msg)
30
31
  except Exception:
31
32
  pass
32
- return True
33
+ return True
nlbone/utils/time.py CHANGED
@@ -30,7 +30,7 @@ class TimeUtility:
30
30
 
31
31
  @classmethod
32
32
  def get_past_datetime(
33
- cls, days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0
33
+ cls, days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0
34
34
  ) -> datetime:
35
35
  delta = timedelta(
36
36
  days=days,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nlbone
3
- Version: 0.6.0
3
+ Version: 0.6.8
4
4
  Summary: Backbone package for interfaces and infrastructure in Python projects
5
5
  Author-email: Amir Hosein Kahkbazzadeh <a.khakbazzadeh@gmail.com>
6
6
  License: MIT
@@ -1,18 +1,18 @@
1
1
  nlbone/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- nlbone/container.py,sha256=hOkc8F6Zx4fP5HxRp_uBYQyxhi6QYRAIqUPFEYd18aQ,3361
2
+ nlbone/container.py,sha256=ZD5sF1aByHUR_SoYo2Cw0_LMgGtw1pQluiG1afbzgRI,3242
3
3
  nlbone/types.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- nlbone/adapters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ nlbone/adapters/__init__.py,sha256=NzUmk4XPyp3GJOw7VSE86xkQMZLtG3MrOoXLeoB551M,41
5
5
  nlbone/adapters/auth/__init__.py,sha256=hkDHvsFhw_UiOHG9ZSMqjiAhK4wumEforitveSZswVw,42
6
- nlbone/adapters/auth/keycloak.py,sha256=dfAxODiARfR8y3FKoWNo9fjfb6QyWd_Qr7AbJ0E78AM,2729
7
- nlbone/adapters/auth/token_provider.py,sha256=NhqjqTUsoZO4gbK-cybs0OkKydFN7CPTxAiypEw081o,1433
6
+ nlbone/adapters/auth/keycloak.py,sha256=j5KWMXGZN4Sey34I-dbaHrPG37hAaDPs4Utcra6UgWY,2730
7
+ nlbone/adapters/auth/token_provider.py,sha256=vL2Hk6HXnBbpk40Tq1wpqak5QQ7KEQf3nRquT0N8V4Q,1433
8
8
  nlbone/adapters/cache/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- nlbone/adapters/cache/async_redis.py,sha256=E61tpBwAElMoGgyJ8CWO5G81n7u1-Wi_kVRDoh5rJuM,6206
10
- nlbone/adapters/cache/memory.py,sha256=lRJqMdD2lbojndQ_dJ7AulfhSYY_uqjgXvd4ZT_J8co,3655
11
- nlbone/adapters/cache/pubsub_listener.py,sha256=2y6DbWsERXlMOkmJSJMg8hNU9MTGwR7BhwQRveivh50,1457
12
- nlbone/adapters/cache/redis.py,sha256=gMNfUIk1HkeXVBmtAtVchcr59ll06E4wam9rGWAhalM,4535
13
- nlbone/adapters/db/__init__.py,sha256=saW-wN4E0NZ2_ldi-nrm5AgsH7EULNSa62lYMwfy1oo,252
9
+ nlbone/adapters/cache/async_redis.py,sha256=vvu5w4ANx0BVRHL95RAMGsD8CcaC-tSBMbCius2cuNc,6212
10
+ nlbone/adapters/cache/memory.py,sha256=y8M4erHQXApiSMAqG6Qk4pxEb60hRdu1szPv6iqvO9c,3738
11
+ nlbone/adapters/cache/pubsub_listener.py,sha256=3vfK4O4EzuQQhTsbZ_bweP06o99kDSyHJ5PrfUotUaE,1460
12
+ nlbone/adapters/cache/redis.py,sha256=2Y1DYHBLCrbHTO6O7pw85u3qY6OnCIFTYJ-HBvBs0FM,4608
13
+ nlbone/adapters/db/__init__.py,sha256=0CDSySEk4jJsqmwI0eNuaaLJOJDt8_iSiHBsFdC-L3s,212
14
14
  nlbone/adapters/db/postgres/__init__.py,sha256=6JYJH0xZs3aR-zuyMpRhsdzFugmqz8nprwTQLprqhZc,313
15
- nlbone/adapters/db/postgres/audit.py,sha256=zFzL-pXmfjcp5YLx6vBYczprsJjEPxSYKhQNR3WjKL0,4675
15
+ nlbone/adapters/db/postgres/audit.py,sha256=8f5XOuW7_ybJyy_STam1FNzqmZAAVAu7tmMRUkCGJOM,4594
16
16
  nlbone/adapters/db/postgres/base.py,sha256=kha9xmklzhuQAK8QEkNBn-mAHq8dUKbOM-3abaBpWmQ,71
17
17
  nlbone/adapters/db/postgres/engine.py,sha256=UCegauVB1gvo42ThytYnn5VIcQBwR-5xhcXYFApRFNk,3448
18
18
  nlbone/adapters/db/postgres/query_builder.py,sha256=U5pqpCfJKuMIxIEHyodoHuPgE8jf53slC1ScKZR5xa4,8653
@@ -20,24 +20,24 @@ nlbone/adapters/db/postgres/repository.py,sha256=J_DBE73JhHPYCk90c5-O7lQtZbxDgqj
20
20
  nlbone/adapters/db/postgres/schema.py,sha256=NlE7Rr8uXypsw4oWkdZhZwcIBHQEPIpoHLxcUo98i6s,1039
21
21
  nlbone/adapters/db/postgres/uow.py,sha256=nRxNpY-WoWHpym-XeZ8VHm0MYvtB9wuopOeNdV_ebk8,2088
22
22
  nlbone/adapters/db/redis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
- nlbone/adapters/db/redis/client.py,sha256=XAKcmU0lpPvWPMS0fChVQ3iSJfHV1g4bMOCgJaj2bCI,512
24
- nlbone/adapters/http_clients/__init__.py,sha256=Ed75OHxkJ401XNHpfx3pz20e7Z_YFSAoKUct8o-SI9k,150
25
- nlbone/adapters/http_clients/pricing/__init__.py,sha256=RLSnG9mgaw1Qwt5Qsc4SwRCjNiqWeyDpvFY79PlhkFE,81
26
- nlbone/adapters/http_clients/pricing/pricing_service.py,sha256=cdYEF_ZY01QbhSXoifotELdp2n8GyzdKIC_nWHP696E,2568
27
- nlbone/adapters/http_clients/uploadchi/__init__.py,sha256=VjLtgOPvNufoQvNCrwPLwlzJicdfJCZUqBK4VEXNQ3o,104
28
- nlbone/adapters/http_clients/uploadchi/uploadchi.py,sha256=_D6Gv5nj4TJXcEq_YJWut92vEu69LflLTw6m2dlD7C0,4864
29
- nlbone/adapters/http_clients/uploadchi/uploadchi_async.py,sha256=EGFI1ZhfMxotHkORyU1cJVGG4tsFAbnITZViIc3J7vI,4766
23
+ nlbone/adapters/db/redis/client.py,sha256=5SUnwP2-GrueSFimUbiqDvrQsumvIE2aeozk8l-vOfQ,466
24
+ nlbone/adapters/http_clients/__init__.py,sha256=w-Yr9CLuXMU71N0Ada5HbvP1DB53wqeP6B-i5rALlTo,150
25
+ nlbone/adapters/http_clients/pricing/__init__.py,sha256=ElA9NFcAR9u4cqb_w3PPqKU3xGeyjNLQ8veJ0ql2iz0,81
26
+ nlbone/adapters/http_clients/pricing/pricing_service.py,sha256=KzLRfNxrFjFi74EXahRVS7EH9qx8dbWoFB9WsmG15Og,2710
27
+ nlbone/adapters/http_clients/uploadchi/__init__.py,sha256=uBzEOuVtY22teWW2b36Pitkdk5yVdSqa6xbg22JfTNg,105
28
+ nlbone/adapters/http_clients/uploadchi/uploadchi.py,sha256=ABFiH3bLsxFtB-4Si4SEedE2bMUVz5hWXGwD4RkV3ws,4816
29
+ nlbone/adapters/http_clients/uploadchi/uploadchi_async.py,sha256=PQbVNeaYde5CmgT3vcnQoI1PGeSs9AxHlPFuB8biOmU,4717
30
30
  nlbone/adapters/messaging/__init__.py,sha256=UDAwu3s-JQmOZjWz2Nu0SgHhnkbeOhKDH_zLD75oWMY,40
31
31
  nlbone/adapters/messaging/event_bus.py,sha256=w-NPwDiPMLFPU_enRQCtfQXOALsXfg31u57R8sG_-1U,781
32
32
  nlbone/adapters/messaging/redis.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
- nlbone/adapters/percolation/__init__.py,sha256=viq5WZqcSLlRBF5JwuyTD_IZaNWfpKzGJIECgKOWgOw,37
34
- nlbone/adapters/percolation/connection.py,sha256=xZ-OtQVbyQYH83TUizS0UWI85Iic-AhUjiuyzO0e46s,331
33
+ nlbone/adapters/percolation/__init__.py,sha256=0h1Bw7FzxgkDIHxeoyQXSfegrhP6VbpYV4QC8njYdRE,38
34
+ nlbone/adapters/percolation/connection.py,sha256=yuqcboFLsd4FRfywfXatbe8NjVtzyEWHGOZ8OVmmQaI,333
35
35
  nlbone/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
- nlbone/config/logging.py,sha256=rGQz9W5ZgUFXBK74TFmTuwx_WMJhD8zPN39zfKVxwnI,4115
36
+ nlbone/config/logging.py,sha256=Ot6Ctf7EQZlW8YNB-uBdleqI6wixn5fH0Eo6QRgNkQk,4358
37
37
  nlbone/config/settings.py,sha256=W3NHZP6yjIyyKiGWNkjlUt_RYFKkcIfMBoKih_z_0Bs,3911
38
38
  nlbone/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
39
  nlbone/core/application/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
- nlbone/core/application/base_worker.py,sha256=uHqglsd33jXl_0kmkFlB4KQ5NdI1wArcOeQmdcifPQc,1192
40
+ nlbone/core/application/base_worker.py,sha256=5brIToSd-vi6tw0ukhHnUZGZhOLq1SQ-NRRy-kp6D24,1193
41
41
  nlbone/core/application/events.py,sha256=eQGLE0aZHuWJsy9J-qRse4CMXOtweH9-2rQ7AIPRMEQ,614
42
42
  nlbone/core/application/services.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
43
  nlbone/core/application/use_case.py,sha256=3GMQZ3CFK5cbLoBNBgohPft6GBq2j9_wr8iKRq_osQA,247
@@ -45,10 +45,10 @@ nlbone/core/application/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRk
45
45
  nlbone/core/domain/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
46
  nlbone/core/domain/base.py,sha256=5oUfbpaI8juJ28Api8J9IXOSm55VI2bp4QNhA0U8h2Y,1251
47
47
  nlbone/core/domain/events.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
- nlbone/core/domain/models.py,sha256=e2ig7PMBBpmc8pdHLNMnXhucMXr9OUq-G7bKGTq9Qj0,1458
48
+ nlbone/core/domain/models.py,sha256=Zn_rwtlzfjOEJZo6HS9M8UsMk-HpMJrHAKn05UA-u2k,1461
49
49
  nlbone/core/ports/__init__.py,sha256=gx-Ubj7h-1vvnu56sNnRqmer7HHfW3rX2WLl-0AX5U0,214
50
50
  nlbone/core/ports/auth.py,sha256=Gh0yQsxx2OD6pDH2_p-khsA-bVoypP1juuqMoSfjZUo,493
51
- nlbone/core/ports/cache.py,sha256=C9exWYPZsppCpkrAMiGfJuf4ehHkibtFfvB1aFbWuO4,2257
51
+ nlbone/core/ports/cache.py,sha256=8pP_z4ta7PNNG8UiSrEF4xMZRm2wLPxISZvdPt7QnxQ,2351
52
52
  nlbone/core/ports/event_bus.py,sha256=_Om1GOOT-F325oV6_LJXtLdx4vu5i7KrpTDD3qPJXU0,325
53
53
  nlbone/core/ports/files.py,sha256=7Ov2ITYRpPwwDTZGCeNVISg8e3A9l08jbOgpTImgfK8,1863
54
54
  nlbone/core/ports/messaging.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -62,7 +62,7 @@ nlbone/interfaces/api/routers.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hS
62
62
  nlbone/interfaces/api/schemas.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
63
63
  nlbone/interfaces/api/dependencies/__init__.py,sha256=rnYRrFVZCfICQrp_PVFlzNg3BeC57yM08wn2DbOHCfk,359
64
64
  nlbone/interfaces/api/dependencies/async_auth.py,sha256=bfxgBXhp29WqevjTG4jrdPNR-75APm4jKyHdOOtxnp4,1825
65
- nlbone/interfaces/api/dependencies/auth.py,sha256=VcWnEipQr4dqhzGbP0G9a5nJdplnpFG3wm_NlDCb8z4,1765
65
+ nlbone/interfaces/api/dependencies/auth.py,sha256=Tp4_DjJ-7hHWiWdvPaD922DGdauuzDXl5aqALBtk-QM,2750
66
66
  nlbone/interfaces/api/dependencies/db.py,sha256=-UD39J_86UU7ZJs2ZncpdND0yhAG0NeeeALrgSDuuFw,466
67
67
  nlbone/interfaces/api/dependencies/uow.py,sha256=QfLEvLYLNWZJQN1k-0q0hBVtUld3D75P4j39q_RjcnE,1181
68
68
  nlbone/interfaces/api/middleware/__init__.py,sha256=zbX2vaEAfxRMIYwO2MVY_2O6bqG5H9o7HqGpX14U3Is,158
@@ -72,20 +72,20 @@ nlbone/interfaces/api/middleware/authentication.py,sha256=ze7vCm492QsX9nPL6A-PqZ
72
72
  nlbone/interfaces/api/pagination/__init__.py,sha256=sWKKQFa2Z-1SlprQOqImOa2c9exq4wueKpUL_9QM7wc,417
73
73
  nlbone/interfaces/api/pagination/offset_base.py,sha256=B6rHxzDsxQbm-d2snM6tjgnhWyZw7zvs7fcehV0gpa0,3621
74
74
  nlbone/interfaces/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
75
- nlbone/interfaces/cli/init_db.py,sha256=49TmA-LwUsqGVttjU-w2EzZ-vzQCULNlhKP_0sfl1_Y,790
76
- nlbone/interfaces/cli/main.py,sha256=65XXNmH0dX9Lib_yW5iQXo7wp_GRFwx9xXDYgy2LJtY,704
75
+ nlbone/interfaces/cli/init_db.py,sha256=C67n2MsJ1vzkJxC8zfUYOxFdd6mEB_vT9agxN6jWoG8,790
76
+ nlbone/interfaces/cli/main.py,sha256=pNldsTgplVyXa-Hx96dySO2I9gFRi49nDXv7J_dO73s,686
77
77
  nlbone/interfaces/jobs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
78
78
  nlbone/interfaces/jobs/sync_tokens.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
79
79
  nlbone/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
80
- nlbone/utils/cache.py,sha256=3sKtWoGBlrEvkg74r4C-Sx8u41OTup_G4s-Q57IaOXg,5921
81
- nlbone/utils/cache_keys.py,sha256=a1yRMUuyRJ2-CswjtgVkLcJAeT2QmThLQ5kQWvpOKL4,1069
82
- nlbone/utils/cache_registry.py,sha256=0csax1-GmKBcsZmQYWI4Bs0X9_BMo6Jdoac-e9Zusv8,819
80
+ nlbone/utils/cache.py,sha256=hVfkR62o5vllDrE_nY4At10wK0It6qpZ45K1xoj10cQ,5931
81
+ nlbone/utils/cache_keys.py,sha256=Y2YSellHTbUOcoaNbl1jaD4r485VU_e4KXsfBWhYTBo,1075
82
+ nlbone/utils/cache_registry.py,sha256=w28sEfUQZAhzCCqVH5TflWQY3nyDXyEcFWt8hkuHRHw,823
83
83
  nlbone/utils/context.py,sha256=MmclJ24BG2uvSTg1IK7J-Da9BhVFDQ5ag4Ggs2FF1_w,1600
84
- nlbone/utils/http.py,sha256=krNfMoD9kTFE_E5yX-0DzgH97H-UgAiKRMXmATlwgIM,876
85
- nlbone/utils/redactor.py,sha256=JbbPs2Qtnz0zHN85BGPYQNWwBigXMSzmMEmmZZOTs_U,1277
86
- nlbone/utils/time.py,sha256=6e0A4_hG1rYDCrWoOklEGVJstBf8j9XSSTT7VNV2K9Y,1272
87
- nlbone-0.6.0.dist-info/METADATA,sha256=402hd-duWpvMKlnSwcgbbYLJQ0OQFgwKtWd9zlfkdB8,2194
88
- nlbone-0.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
89
- nlbone-0.6.0.dist-info/entry_points.txt,sha256=CpIL45t5nbhl1dGQPhfIIDfqqak3teK0SxPGBBr7YCk,59
90
- nlbone-0.6.0.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
91
- nlbone-0.6.0.dist-info/RECORD,,
84
+ nlbone/utils/http.py,sha256=UXUoXgQdTRNT08ho8zl-C5ekfDsD8uf-JiMQ323ooqw,872
85
+ nlbone/utils/redactor.py,sha256=-V4HrHmHwPi3Kez587Ek1uJlgK35qGSrwBOvcbw8Jas,1279
86
+ nlbone/utils/time.py,sha256=DjjyQ9GLsfXoT6NK8RDW2rOlJg3e6sF04Jw6PBUrSvg,1268
87
+ nlbone-0.6.8.dist-info/METADATA,sha256=GRppq6YJdTQl_op7vf2oWBZP3IX2TNznHWhTFtvXJOM,2194
88
+ nlbone-0.6.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
89
+ nlbone-0.6.8.dist-info/entry_points.txt,sha256=CpIL45t5nbhl1dGQPhfIIDfqqak3teK0SxPGBBr7YCk,59
90
+ nlbone-0.6.8.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
91
+ nlbone-0.6.8.dist-info/RECORD,,
File without changes