nlbone 0.5.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 (39) 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 -0
  12. nlbone/adapters/http_clients/pricing/__init__.py +1 -0
  13. nlbone/adapters/http_clients/pricing/pricing_service.py +100 -0
  14. nlbone/adapters/http_clients/uploadchi/__init__.py +2 -0
  15. nlbone/adapters/http_clients/{uploadchi.py → uploadchi/uploadchi.py} +22 -46
  16. nlbone/adapters/http_clients/{uploadchi_async.py → uploadchi/uploadchi_async.py} +23 -23
  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/config/settings.py +20 -12
  21. nlbone/container.py +12 -6
  22. nlbone/core/application/base_worker.py +1 -1
  23. nlbone/core/domain/models.py +4 -2
  24. nlbone/core/ports/cache.py +25 -9
  25. nlbone/interfaces/api/dependencies/auth.py +26 -0
  26. nlbone/interfaces/cli/init_db.py +1 -1
  27. nlbone/interfaces/cli/main.py +6 -5
  28. nlbone/utils/cache.py +10 -0
  29. nlbone/utils/cache_keys.py +6 -0
  30. nlbone/utils/cache_registry.py +5 -2
  31. nlbone/utils/http.py +29 -0
  32. nlbone/utils/redactor.py +2 -1
  33. nlbone/utils/time.py +1 -1
  34. {nlbone-0.5.0.dist-info → nlbone-0.6.8.dist-info}/METADATA +1 -1
  35. {nlbone-0.5.0.dist-info → nlbone-0.6.8.dist-info}/RECORD +38 -35
  36. nlbone/adapters/http_clients/email_gateway.py +0 -0
  37. {nlbone-0.5.0.dist-info → nlbone-0.6.8.dist-info}/WHEEL +0 -0
  38. {nlbone-0.5.0.dist-info → nlbone-0.6.8.dist-info}/entry_points.txt +0 -0
  39. {nlbone-0.5.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
@@ -0,0 +1,2 @@
1
+ from .pricing import CalculatePriceIn, CalculatePriceOut, PricingService
2
+ from .uploadchi import UploadchiAsyncClient, UploadchiClient, UploadchiError
@@ -0,0 +1 @@
1
+ from .pricing_service import CalculatePriceIn, CalculatePriceOut, PricingService
@@ -0,0 +1,100 @@
1
+ from enum import Enum
2
+ from typing import List, Literal, Optional
3
+
4
+ import httpx
5
+ import requests
6
+ from pydantic import BaseModel, Field, NonNegativeInt, RootModel
7
+
8
+ from nlbone.adapters.auth.token_provider import ClientTokenProvider
9
+ from nlbone.config.settings import get_settings
10
+ from nlbone.utils.http import auth_headers, normalize_https_base
11
+
12
+
13
+ class PricingError(Exception):
14
+ pass
15
+
16
+
17
+ class CalculatePriceIn(BaseModel):
18
+ params: dict[str, str]
19
+ product_id: NonNegativeInt | None = None
20
+ product_title: str | None = None
21
+
22
+
23
+ class DiscountType(str, Enum):
24
+ percent = "percent"
25
+ amount = "amount"
26
+
27
+
28
+ class Product(BaseModel):
29
+ id: Optional[int] = Field(None, description="Nullable product id")
30
+ service_product_id: NonNegativeInt
31
+ title: Optional[str] = None
32
+
33
+
34
+ class Pricing(BaseModel):
35
+ source: Literal["formula", "static"]
36
+ price: Optional[float] = None
37
+ discount: Optional[float] = None
38
+ discount_type: Optional[DiscountType] = None
39
+ params: Optional[dict] = None
40
+
41
+
42
+ class Segment(BaseModel):
43
+ id: str
44
+ name: str
45
+ specificity: int
46
+ matched_fields: list
47
+
48
+
49
+ class Formula(BaseModel):
50
+ id: int
51
+ title: str
52
+ key: str
53
+ status: str
54
+ description: str
55
+
56
+
57
+ class PricingRule(BaseModel):
58
+ product: Product
59
+ segment: Segment | None
60
+ formula: Optional[Formula] = None
61
+ pricing: Pricing
62
+
63
+
64
+ class CalculatePriceOut(RootModel[List[PricingRule]]):
65
+ pass
66
+
67
+
68
+ class PricingService:
69
+ def __init__(
70
+ self,
71
+ token_provider: ClientTokenProvider,
72
+ base_url: Optional[str] = None,
73
+ timeout_seconds: Optional[float] = None,
74
+ client: httpx.Client | None = None,
75
+ ) -> None:
76
+ s = get_settings()
77
+ self._base_url = normalize_https_base(base_url or str(s.PRICING_SERVICE_URL), enforce_https=False)
78
+ self._timeout = timeout_seconds or float(s.HTTP_TIMEOUT_SECONDS)
79
+ self._client = client or requests.session()
80
+ self._token_provider = token_provider
81
+
82
+ def calculate(self, items: list[CalculatePriceIn], response: Literal["list", "dict"] = "dict") -> CalculatePriceOut:
83
+ body = {"items": [i.model_dump() for i in items]}
84
+
85
+ r = self._client.post(
86
+ f"{self._base_url}/price/calculate",
87
+ params={"response": response},
88
+ headers=auth_headers(self._token_provider.get_access_token()),
89
+ json=body,
90
+ timeout=self._timeout,
91
+ verify=False,
92
+ )
93
+
94
+ if r.status_code not in (200, 204):
95
+ raise PricingError(r.status_code, r.text)
96
+
97
+ if r.status_code == 204 or not r.content:
98
+ return CalculatePriceOut.model_validate(root=[])
99
+
100
+ return CalculatePriceOut.model_validate(r.json())
@@ -0,0 +1,2 @@
1
+ from .uploadchi import UploadchiClient, UploadchiError
2
+ from .uploadchi_async import UploadchiAsyncClient
@@ -1,8 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- import json
4
3
  from typing import Any, Optional
5
- from urllib.parse import urlparse, urlunparse
6
4
 
7
5
  import httpx
8
6
  import requests
@@ -10,6 +8,7 @@ import requests
10
8
  from nlbone.adapters.auth.token_provider import ClientTokenProvider
11
9
  from nlbone.config.settings import get_settings
12
10
  from nlbone.core.ports.files import FileServicePort
11
+ from nlbone.utils.http import auth_headers, build_list_query, normalize_https_base
13
12
 
14
13
 
15
14
  class UploadchiError(RuntimeError):
@@ -26,21 +25,6 @@ def _resolve_token(explicit: str | None) -> str | None:
26
25
  return s.UPLOADCHI_TOKEN.get_secret_value() if s.UPLOADCHI_TOKEN else None
27
26
 
28
27
 
29
- def _auth_headers(token: str | None) -> dict[str, str]:
30
- return {"Authorization": f"Bearer {token}"} if token else {}
31
-
32
-
33
- def _build_list_query(
34
- limit: int, offset: int, filters: dict[str, Any] | None, sort: list[tuple[str, str]] | None
35
- ) -> dict[str, Any]:
36
- q: dict[str, Any] = {"limit": limit, "offset": offset}
37
- if filters:
38
- q["filters"] = json.dumps(filters)
39
- if sort:
40
- q["sort"] = ",".join([f"{f}:{o}" for f, o in sort])
41
- return q
42
-
43
-
44
28
  def _filename_from_cd(cd: str | None, fallback: str) -> str:
45
29
  if not cd:
46
30
  return fallback
@@ -49,24 +33,16 @@ def _filename_from_cd(cd: str | None, fallback: str) -> str:
49
33
  return fallback
50
34
 
51
35
 
52
- def _normalize_https_base(url: str) -> str:
53
- p = urlparse(url.strip())
54
- p = p._replace(scheme="https") # enforce https
55
- if p.path.endswith("/"):
56
- p = p._replace(path=p.path.rstrip("/"))
57
- return str(urlunparse(p))
58
-
59
-
60
36
  class UploadchiClient(FileServicePort):
61
37
  def __init__(
62
- self,
63
- token_provider: ClientTokenProvider | None = None,
64
- base_url: Optional[str] = None,
65
- timeout_seconds: Optional[float] = None,
66
- 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,
67
43
  ) -> None:
68
44
  s = get_settings()
69
- self._base_url = _normalize_https_base(base_url or str(s.UPLOADCHI_BASE_URL))
45
+ self._base_url = normalize_https_base(base_url or str(s.UPLOADCHI_BASE_URL))
70
46
  self._timeout = timeout_seconds or float(s.HTTP_TIMEOUT_SECONDS)
71
47
  self._client = client or requests.session()
72
48
  self._token_provider = token_provider
@@ -75,12 +51,12 @@ class UploadchiClient(FileServicePort):
75
51
  self._client.close()
76
52
 
77
53
  def upload_file(
78
- 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
79
55
  ) -> dict:
80
56
  tok = _resolve_token(token)
81
57
  files = {"file": (filename, file_bytes)}
82
58
  data = (params or {}).copy()
83
- r = self._client.post(self._base_url, files=files, data=data, headers=_auth_headers(tok))
59
+ r = self._client.post(self._base_url, files=files, data=data, headers=auth_headers(tok))
84
60
  if r.status_code >= 400:
85
61
  raise UploadchiError(r.status_code, r.text)
86
62
  return r.json()
@@ -91,7 +67,7 @@ class UploadchiClient(FileServicePort):
91
67
  tok = _resolve_token(token)
92
68
  r = self._client.post(
93
69
  f"{self._base_url}/{file_id}/commit",
94
- headers=_auth_headers(tok or self._token_provider.get_access_token()),
70
+ headers=auth_headers(tok or self._token_provider.get_access_token()),
95
71
  )
96
72
  if r.status_code not in (204, 200):
97
73
  raise UploadchiError(r.status_code, r.text)
@@ -102,36 +78,36 @@ class UploadchiClient(FileServicePort):
102
78
  tok = _resolve_token(token)
103
79
  r = self._client.post(
104
80
  f"{self._base_url}/{file_id}/rollback",
105
- headers=_auth_headers(tok or self._token_provider.get_access_token()),
81
+ headers=auth_headers(tok or self._token_provider.get_access_token()),
106
82
  )
107
83
  if r.status_code not in (204, 200):
108
84
  raise UploadchiError(r.status_code, r.text)
109
85
 
110
86
  def list_files(
111
- self,
112
- limit: int = 10,
113
- offset: int = 0,
114
- filters: dict[str, Any] | None = None,
115
- sort: list[tuple[str, str]] | None = None,
116
- 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,
117
93
  ) -> dict:
118
94
  tok = _resolve_token(token)
119
- q = _build_list_query(limit, offset, filters, sort)
120
- r = self._client.get(self._base_url, params=q, headers=_auth_headers(tok))
95
+ q = build_list_query(limit, offset, filters, sort)
96
+ r = self._client.get(self._base_url, params=q, headers=auth_headers(tok))
121
97
  if r.status_code >= 400:
122
98
  raise UploadchiError(r.status_code, r.text)
123
99
  return r.json()
124
100
 
125
101
  def get_file(self, file_id: str, token: str | None = None) -> dict:
126
102
  tok = _resolve_token(token)
127
- r = self._client.get(f"{self._base_url}/{file_id}", headers=_auth_headers(tok))
103
+ r = self._client.get(f"{self._base_url}/{file_id}", headers=auth_headers(tok))
128
104
  if r.status_code >= 400:
129
105
  raise UploadchiError(r.status_code, r.text)
130
106
  return r.json()
131
107
 
132
108
  def download_file(self, file_id: str, token: str | None = None) -> tuple[bytes, str, str]:
133
109
  tok = _resolve_token(token)
134
- r = self._client.get(f"{self._base_url}/{file_id}/download", headers=_auth_headers(tok))
110
+ r = self._client.get(f"{self._base_url}/{file_id}/download", headers=auth_headers(tok))
135
111
  if r.status_code >= 400:
136
112
  raise UploadchiError(r.status_code, r.text)
137
113
  filename = _filename_from_cd(r.headers.get("content-disposition"), fallback=f"file-{file_id}")
@@ -140,6 +116,6 @@ class UploadchiClient(FileServicePort):
140
116
 
141
117
  def delete_file(self, file_id: str, token: str | None = None) -> None:
142
118
  tok = _resolve_token(token)
143
- r = self._client.delete(f"{self._base_url}/{file_id}", headers=_auth_headers(tok))
119
+ r = self._client.delete(f"{self._base_url}/{file_id}", headers=auth_headers(tok))
144
120
  if r.status_code not in (204, 200):
145
121
  raise UploadchiError(r.status_code, r.text)