nlbone 0.3.3__py3-none-any.whl → 0.4.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.
Files changed (49) hide show
  1. nlbone/adapters/auth/__init__.py +1 -1
  2. nlbone/adapters/auth/keycloak.py +3 -2
  3. nlbone/adapters/db/__init__.py +2 -2
  4. nlbone/adapters/db/sqlalchemy/__init__.py +4 -1
  5. nlbone/adapters/db/sqlalchemy/base.py +0 -2
  6. nlbone/adapters/db/sqlalchemy/engine.py +15 -3
  7. nlbone/adapters/db/sqlalchemy/query_builder.py +27 -11
  8. nlbone/adapters/db/sqlalchemy/repository.py +54 -0
  9. nlbone/adapters/db/sqlalchemy/schema.py +5 -5
  10. nlbone/adapters/db/sqlalchemy/uow.py +71 -0
  11. nlbone/adapters/http_clients/uploadchi.py +27 -11
  12. nlbone/adapters/http_clients/uploadchi_async.py +29 -7
  13. nlbone/adapters/messaging/__init__.py +1 -0
  14. nlbone/adapters/messaging/event_bus.py +23 -0
  15. nlbone/config/logging.py +3 -8
  16. nlbone/config/settings.py +18 -22
  17. nlbone/container.py +18 -2
  18. nlbone/core/application/events.py +20 -0
  19. nlbone/core/application/use_case.py +12 -0
  20. nlbone/core/domain/base.py +51 -0
  21. nlbone/core/ports/__init__.py +5 -1
  22. nlbone/core/ports/auth.py +1 -0
  23. nlbone/core/ports/event_bus.py +10 -0
  24. nlbone/core/ports/files.py +26 -5
  25. nlbone/core/ports/repo.py +19 -0
  26. nlbone/core/ports/uow.py +19 -0
  27. nlbone/interfaces/api/dependencies/__init__.py +11 -2
  28. nlbone/interfaces/api/dependencies/async_auth.py +61 -0
  29. nlbone/interfaces/api/dependencies/auth.py +5 -3
  30. nlbone/interfaces/api/dependencies/db.py +4 -2
  31. nlbone/interfaces/api/dependencies/uow.py +32 -0
  32. nlbone/interfaces/api/exception_handlers.py +17 -15
  33. nlbone/interfaces/api/exceptions.py +1 -2
  34. nlbone/interfaces/api/middleware/__init__.py +2 -2
  35. nlbone/interfaces/api/middleware/access_log.py +12 -8
  36. nlbone/interfaces/api/middleware/add_request_context.py +55 -52
  37. nlbone/interfaces/api/middleware/authentication.py +4 -1
  38. nlbone/interfaces/api/pagination/__init__.py +4 -5
  39. nlbone/interfaces/api/pagination/offset_base.py +0 -2
  40. nlbone/interfaces/cli/init_db.py +3 -0
  41. nlbone/utils/context.py +14 -4
  42. nlbone/utils/time.py +1 -1
  43. {nlbone-0.3.3.dist-info → nlbone-0.4.1.dist-info}/METADATA +1 -9
  44. nlbone-0.4.1.dist-info/RECORD +72 -0
  45. nlbone/core/application/use_cases/__init__.py +0 -0
  46. nlbone/core/application/use_cases/register_user.py +0 -0
  47. nlbone-0.3.3.dist-info/RECORD +0 -64
  48. {nlbone-0.3.3.dist-info → nlbone-0.4.1.dist-info}/WHEEL +0 -0
  49. {nlbone-0.3.3.dist-info → nlbone-0.4.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,51 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from datetime import datetime, timezone
5
+ from typing import Any, Generic, List, TypeVar
6
+
7
+ TId = TypeVar("TId")
8
+
9
+
10
+ class DomainError(Exception):
11
+ """Base domain exception."""
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class DomainEvent:
16
+ """Immutable domain event."""
17
+
18
+ occurred_at: datetime = datetime.now(timezone.utc)
19
+
20
+ @property
21
+ def name(self):
22
+ return self.__class__.__name__
23
+
24
+
25
+ class ValueObject:
26
+ """Base for value objects (immutable in practice)."""
27
+
28
+ def __eq__(self, other: Any) -> bool:
29
+ return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
30
+
31
+ def __hash__(self) -> int: # allow in sets/dicts
32
+ return hash(tuple(sorted(self.__dict__.items())))
33
+
34
+
35
+ class Entity(Generic[TId]):
36
+ id: TId
37
+
38
+
39
+ class AggregateRoot(Entity[TId]):
40
+ """Aggregate root with domain event collection."""
41
+
42
+ def __init__(self, *args, **kwargs) -> None:
43
+ self._domain_events: List[DomainEvent] = []
44
+
45
+ def _raise(self, event: DomainEvent) -> None:
46
+ self._domain_events.append(event)
47
+
48
+ def pull_events(self) -> List[DomainEvent]:
49
+ events = list(self._domain_events)
50
+ self._domain_events.clear()
51
+ return events
@@ -1 +1,5 @@
1
- from .auth import AuthService
1
+ from .auth import AuthService
2
+ from .event_bus import EventBusPort
3
+ from .files import AsyncFileServicePort, FileServicePort
4
+ from .repo import AsyncRepository, Repository
5
+ from .uow import AsyncUnitOfWork, UnitOfWork
nlbone/core/ports/auth.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from typing import Protocol, runtime_checkable
2
2
 
3
+
3
4
  @runtime_checkable
4
5
  class AuthService(Protocol):
5
6
  def has_access(self, token: str, permissions: list[str]) -> bool: ...
@@ -0,0 +1,10 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Callable, Iterable, Protocol
4
+
5
+ from nlbone.core.domain.base import DomainEvent
6
+
7
+
8
+ class EventBusPort(Protocol):
9
+ def publish(self, events: Iterable[DomainEvent]) -> None: ...
10
+ def subscribe(self, event_name: str, handler: Callable[[DomainEvent], None]) -> None: ...
@@ -1,20 +1,41 @@
1
1
  from __future__ import annotations
2
- from typing import Protocol, runtime_checkable, AsyncIterator, Any
2
+
3
+ from typing import Any, AsyncIterator, Protocol, runtime_checkable
4
+
3
5
 
4
6
  @runtime_checkable
5
7
  class FileServicePort(Protocol):
6
- def upload_file(self, file_bytes: bytes, filename: str, params: dict[str, Any] | None = None, token: str | None = None) -> dict: ...
8
+ def upload_file(
9
+ self, file_bytes: bytes, filename: str, params: dict[str, Any] | None = None, token: str | None = None
10
+ ) -> dict: ...
7
11
  def commit_file(self, file_id: int, client_id: str, token: str | None = None) -> None: ...
8
- def list_files(self, limit: int = 10, offset: int = 0, filters: dict[str, Any] | None = None, sort: list[tuple[str, str]] | None = None, token: str | None = None) -> dict: ...
12
+ def list_files(
13
+ self,
14
+ limit: int = 10,
15
+ offset: int = 0,
16
+ filters: dict[str, Any] | None = None,
17
+ sort: list[tuple[str, str]] | None = None,
18
+ token: str | None = None,
19
+ ) -> dict: ...
9
20
  def get_file(self, file_id: int, token: str | None = None) -> dict: ...
10
21
  def download_file(self, file_id: int, token: str | None = None) -> tuple[bytes, str, str]: ...
11
22
  def delete_file(self, file_id: int, token: str | None = None) -> None: ...
12
23
 
24
+
13
25
  @runtime_checkable
14
26
  class AsyncFileServicePort(Protocol):
15
- async def upload_file(self, file_bytes: bytes, filename: str, params: dict[str, Any] | None = None, token: str | None = None) -> dict: ...
27
+ async def upload_file(
28
+ self, file_bytes: bytes, filename: str, params: dict[str, Any] | None = None, token: str | None = None
29
+ ) -> dict: ...
16
30
  async def commit_file(self, file_id: int, client_id: str, token: str | None = None) -> None: ...
17
- async def list_files(self, limit: int = 10, offset: int = 0, filters: dict[str, Any] | None = None, sort: list[tuple[str, str]] | None = None, token: str | None = None) -> dict: ...
31
+ async def list_files(
32
+ self,
33
+ limit: int = 10,
34
+ offset: int = 0,
35
+ filters: dict[str, Any] | None = None,
36
+ sort: list[tuple[str, str]] | None = None,
37
+ token: str | None = None,
38
+ ) -> dict: ...
18
39
  async def get_file(self, file_id: int, token: str | None = None) -> dict: ...
19
40
  async def download_file(self, file_id: int, token: str | None = None) -> tuple[AsyncIterator[bytes], str, str]: ...
20
41
  async def delete_file(self, file_id: int, token: str | None = None) -> None: ...
nlbone/core/ports/repo.py CHANGED
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Iterable, List, Optional, Protocol, TypeVar
4
+
5
+ T = TypeVar("T")
6
+
7
+
8
+ class Repository(Protocol[T]): # ← نه Protocol, Generic[T]
9
+ def get(self, id) -> Optional[T]: ...
10
+ def add(self, obj: T) -> None: ...
11
+ def remove(self, obj: T) -> None: ...
12
+ def list(self, *, limit: int | None = None, offset: int = 0) -> Iterable[T]: ...
13
+
14
+
15
+ class AsyncRepository(Protocol[T]):
16
+ async def get(self, id) -> Optional[T]: ...
17
+ def add(self, obj: T) -> None: ...
18
+ async def remove(self, obj: T) -> None: ...
19
+ async def list(self, *, limit: int | None = None, offset: int = 0) -> List[T]: ...
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Protocol, runtime_checkable
4
+
5
+
6
+ @runtime_checkable
7
+ class UnitOfWork(Protocol):
8
+ def __enter__(self) -> "UnitOfWork": ...
9
+ def __exit__(self, exc_type, exc, tb) -> None: ...
10
+ def commit(self) -> None: ...
11
+ def rollback(self) -> None: ...
12
+
13
+
14
+ @runtime_checkable
15
+ class AsyncUnitOfWork(Protocol):
16
+ async def __aenter__(self) -> "AsyncUnitOfWork": ...
17
+ async def __aexit__(self, exc_type, exc, tb) -> None: ...
18
+ async def commit(self) -> None: ...
19
+ async def rollback(self) -> None: ...
@@ -1,2 +1,11 @@
1
- from .db import get_session, get_async_session
2
- from .auth import has_access, client_has_access, current_client_id, current_user_id, current_request, user_authenticated
1
+ from .async_auth import client_has_access, current_request, current_user_id, has_access, user_authenticated
2
+ from .auth import ( # noqa: F811
3
+ client_has_access,
4
+ current_client_id,
5
+ current_request,
6
+ current_user_id,
7
+ has_access,
8
+ user_authenticated,
9
+ )
10
+ from .db import get_async_session, get_session
11
+ from .uow import get_async_uow, get_uow
@@ -0,0 +1,61 @@
1
+ import functools
2
+
3
+ from nlbone.adapters.auth import KeycloakAuthService
4
+ from nlbone.interfaces.api.exceptions import ForbiddenException, UnauthorizedException
5
+ from nlbone.utils.context import current_request
6
+
7
+
8
+ async def current_user_id() -> int:
9
+ user_id = current_request().state.user_id
10
+ if user_id is not None:
11
+ return int(user_id)
12
+ raise UnauthorizedException()
13
+
14
+
15
+ async def current_client_id() -> str:
16
+ request = current_request()
17
+ if client_id := KeycloakAuthService().get_client_id(request.state.token):
18
+ return str(client_id)
19
+ raise UnauthorizedException()
20
+
21
+
22
+ def client_has_access(*, permissions=None):
23
+ def decorator(func):
24
+ @functools.wraps(func)
25
+ async def wrapper(*args, **kwargs):
26
+ request = current_request()
27
+ if not KeycloakAuthService().client_has_access(request.state.token, permissions=permissions):
28
+ raise ForbiddenException(f"Forbidden {permissions}")
29
+
30
+ return await func(*args, **kwargs)
31
+
32
+ return wrapper
33
+
34
+ return decorator
35
+
36
+
37
+ def user_authenticated(func):
38
+ @functools.wraps(func)
39
+ async def wrapper(*args, **kwargs):
40
+ if not await current_user_id():
41
+ raise UnauthorizedException()
42
+ return await func(*args, **kwargs)
43
+
44
+ return wrapper
45
+
46
+
47
+ def has_access(*, permissions=None):
48
+ def decorator(func):
49
+ @functools.wraps(func)
50
+ async def wrapper(*args, **kwargs):
51
+ request = current_request()
52
+ if not await current_user_id():
53
+ raise UnauthorizedException()
54
+ if not KeycloakAuthService().has_access(request.state.token, permissions=permissions):
55
+ raise ForbiddenException(f"Forbidden {permissions}")
56
+
57
+ return await func(*args, **kwargs)
58
+
59
+ return wrapper
60
+
61
+ return decorator
@@ -30,15 +30,16 @@ def client_has_access(*, permissions=None):
30
30
  return func(*args, **kwargs)
31
31
 
32
32
  return wrapper
33
+
33
34
  return decorator
34
35
 
35
36
 
36
37
  def user_authenticated(func):
37
38
  @functools.wraps(func)
38
- async def wrapper(*args, **kwargs):
39
+ def wrapper(*args, **kwargs):
39
40
  if not current_user_id():
40
41
  raise UnauthorizedException()
41
- return await func(*args, **kwargs)
42
+ return func(*args, **kwargs)
42
43
 
43
44
  return wrapper
44
45
 
@@ -56,4 +57,5 @@ def has_access(*, permissions=None):
56
57
  return func(*args, **kwargs)
57
58
 
58
59
  return wrapper
59
- return decorator
60
+
61
+ return decorator
@@ -1,8 +1,9 @@
1
1
  from __future__ import annotations
2
+
2
3
  from typing import AsyncGenerator, Generator
3
4
 
4
- from sqlalchemy.orm import Session
5
5
  from sqlalchemy.ext.asyncio import AsyncSession
6
+ from sqlalchemy.orm import Session
6
7
 
7
8
  from nlbone.adapters.db.sqlalchemy.engine import async_session, sync_session
8
9
 
@@ -11,6 +12,7 @@ async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
11
12
  async with async_session() as s:
12
13
  yield s
13
14
 
15
+
14
16
  def get_session() -> Generator[Session, None, None]:
15
17
  with sync_session() as s:
16
- yield s
18
+ yield s
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterator
4
+ from typing import AsyncIterator
5
+
6
+ from fastapi import Request
7
+
8
+ from nlbone.adapters.db.sqlalchemy import AsyncSqlAlchemyUnitOfWork
9
+ from nlbone.core.ports.uow import AsyncUnitOfWork, UnitOfWork
10
+
11
+
12
+ def get_uow(request: Request) -> Iterator[UnitOfWork]:
13
+ """
14
+ Uses DI container mounted at app.state.container to create a UoW per request.
15
+ Assumes container.uow is a provider returning SqlAlchemyUnitOfWork(session_factory).
16
+ """
17
+ container = getattr(request.app.state, "container", None)
18
+ if container is None or not hasattr(container, "uow"):
19
+ raise RuntimeError("Container with 'uow' provider not configured on app.state.container")
20
+
21
+ uow = container.uow()
22
+ with uow as _uow:
23
+ yield _uow
24
+
25
+
26
+ async def get_async_uow(request: Request) -> AsyncIterator[AsyncUnitOfWork]:
27
+ container = getattr(request.app.state, "container", None)
28
+ if container is None or not hasattr(container, "async_uow"):
29
+ raise RuntimeError("Container.async_uow provider not configured")
30
+ uow: AsyncSqlAlchemyUnitOfWork = container.async_uow()
31
+ async with uow as _uow:
32
+ yield _uow
@@ -1,32 +1,33 @@
1
1
  from __future__ import annotations
2
- from typing import Any, Optional, Mapping
2
+
3
+ from typing import Any, Mapping, Optional
3
4
  from uuid import uuid4
4
5
 
5
6
  from fastapi import FastAPI, Request
6
- from fastapi.responses import JSONResponse
7
+ from fastapi import HTTPException as FastAPIHTTPException
7
8
  from fastapi.exceptions import RequestValidationError
9
+ from fastapi.responses import JSONResponse
8
10
  from pydantic import ValidationError
9
- from fastapi import HTTPException as FastAPIHTTPException
10
11
  from starlette.exceptions import HTTPException as StarletteHTTPException
11
12
 
12
13
  from .exceptions import BaseHttpException
13
14
 
14
-
15
15
  # ---- Helpers ---------------------------------------------------------------
16
16
 
17
+
17
18
  def _ensure_trace_id(request: Request) -> str:
18
19
  rid = request.headers.get("X-Request-Id") or request.headers.get("X-Trace-Id")
19
20
  return rid or str(uuid4())
20
21
 
21
22
 
22
23
  def _json_response(
23
- request: Request,
24
- status_code: int,
25
- *,
26
- detail: Any,
27
- headers: Optional[Mapping[str, str]] = None,
28
- trace_id: Optional[str] = None,
29
- extra: Optional[Mapping[str, Any]] = None,
24
+ request: Request,
25
+ status_code: int,
26
+ *,
27
+ detail: Any,
28
+ headers: Optional[Mapping[str, str]] = None,
29
+ trace_id: Optional[str] = None,
30
+ extra: Optional[Mapping[str, Any]] = None,
30
31
  ) -> JSONResponse:
31
32
  payload: dict[str, Any] = {"detail": detail}
32
33
  if extra:
@@ -44,11 +45,12 @@ def _json_response(
44
45
 
45
46
  # ---- Public Installer ------------------------------------------------------
46
47
 
48
+
47
49
  def install_exception_handlers(
48
- app: FastAPI,
49
- *,
50
- logger: Any = None,
51
- expose_server_errors: bool = False,
50
+ app: FastAPI,
51
+ *,
52
+ logger: Any = None,
53
+ expose_server_errors: bool = False,
52
54
  ) -> None:
53
55
  @app.exception_handler(BaseHttpException)
54
56
  async def _handle_base_http_exception(request: Request, exc: BaseHttpException):
@@ -1,4 +1,5 @@
1
1
  from typing import Any, Iterable
2
+
2
3
  from fastapi import HTTPException, status
3
4
 
4
5
 
@@ -77,5 +78,3 @@ class UnprocessableEntityException(BaseHttpException):
77
78
  class LogicalValidationException(UnprocessableEntityException):
78
79
  def __init__(self, detail: str, loc: Iterable[Any] | None = None, type_: str = "logical_error"):
79
80
  super().__init__(detail=detail, loc=loc, type_=type_)
80
-
81
-
@@ -1,3 +1,3 @@
1
- from .authentication import AuthenticationMiddleware
1
+ from .access_log import AccessLogMiddleware
2
2
  from .add_request_context import AddRequestContextMiddleware
3
- from .access_log import AccessLogMiddleware
3
+ from .authentication import AuthenticationMiddleware
@@ -1,5 +1,6 @@
1
1
  import time
2
2
  from typing import Callable
3
+
3
4
  from fastapi import Request
4
5
  from starlette.middleware.base import BaseHTTPMiddleware
5
6
 
@@ -7,6 +8,7 @@ from nlbone.config.logging import get_logger
7
8
 
8
9
  logger = get_logger(__name__)
9
10
 
11
+
10
12
  class AccessLogMiddleware(BaseHTTPMiddleware):
11
13
  async def dispatch(self, request: Request, call_next: Callable):
12
14
  start = time.perf_counter()
@@ -20,11 +22,13 @@ class AccessLogMiddleware(BaseHTTPMiddleware):
20
22
  raise
21
23
  finally:
22
24
  dur_ms = int((time.perf_counter() - start) * 1000)
23
- logger.info({
24
- "event": "access",
25
- "method": request.method,
26
- "path": request.url.path,
27
- "status": status_code,
28
- "duration_ms": dur_ms,
29
- "query": request.url.query,
30
- })
25
+ logger.info(
26
+ {
27
+ "event": "access",
28
+ "method": request.method,
29
+ "path": request.url.path,
30
+ "status": status_code,
31
+ "duration_ms": dur_ms,
32
+ "query": request.url.query,
33
+ }
34
+ )
@@ -1,52 +1,55 @@
1
- from __future__ import annotations
2
- from typing import Optional
3
- from uuid import uuid4
4
-
5
- from starlette.datastructures import Headers
6
- from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
7
- from starlette.requests import Request
8
-
9
- from nlbone import config
10
- from nlbone.utils.context import request_ctx, request_id_ctx, bind_context, reset_context
11
-
12
-
13
- def mock_request(user=None, token: Optional[str] = None):
14
- request = Request({
15
- "type": "http",
16
- "headers": Headers({"User-Agent": "Testing-Agent"}).raw,
17
- "client": {"host": "192.168.1.1", "port": 80},
18
- "method": "GET",
19
- "path": "/__test__",
20
- })
21
- request.state.user = user
22
- request.state.token = token
23
- return request
24
-
25
-
26
- def current_request() -> Optional[Request]:
27
- req = request_ctx.get()
28
- if config.ENV == 'test' and req is None:
29
- return mock_request()
30
- return req
31
-
32
-
33
- def current_request_id() -> Optional[str]:
34
- return request_id_ctx.get()
35
-
36
-
37
- class AddRequestContextMiddleware(BaseHTTPMiddleware):
38
- async def dispatch(self, request: Request, call_next: RequestResponseEndpoint):
39
- incoming_req_id = request.headers.get("X-Request-ID")
40
- req_id = incoming_req_id or str(uuid4())
41
-
42
- user_id = getattr(getattr(request, "state", None), "user_id", None) or request.headers.get("X-User-Id")
43
- ip = request.client.host if request.client else None
44
- ua = request.headers.get("user-agent")
45
-
46
- tokens = bind_context(request=request, request_id=req_id, user_id=user_id, ip=ip, user_agent=ua)
47
- try:
48
- response = await call_next(request)
49
- response.headers.setdefault("X-Request-ID", req_id)
50
- return response
51
- finally:
52
- reset_context(tokens)
1
+ from __future__ import annotations
2
+
3
+ from typing import Optional
4
+ from uuid import uuid4
5
+
6
+ from starlette.datastructures import Headers
7
+ from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
8
+ from starlette.requests import Request
9
+
10
+ from nlbone.config.settings import get_settings
11
+ from nlbone.utils.context import bind_context, request_ctx, request_id_ctx, reset_context
12
+
13
+
14
+ def mock_request(user=None, token: Optional[str] = None):
15
+ request = Request(
16
+ {
17
+ "type": "http",
18
+ "headers": Headers({"User-Agent": "Testing-Agent"}).raw,
19
+ "client": {"host": "192.168.1.1", "port": 80},
20
+ "method": "GET",
21
+ "path": "/__test__",
22
+ }
23
+ )
24
+ request.state.user = user
25
+ request.state.token = token
26
+ return request
27
+
28
+
29
+ def current_request() -> Optional[Request]:
30
+ req = request_ctx.get()
31
+ if get_settings().ENV == "local" and req is None:
32
+ return mock_request()
33
+ return req
34
+
35
+
36
+ def current_request_id() -> Optional[str]:
37
+ return request_id_ctx.get()
38
+
39
+
40
+ class AddRequestContextMiddleware(BaseHTTPMiddleware):
41
+ async def dispatch(self, request: Request, call_next: RequestResponseEndpoint):
42
+ incoming_req_id = request.headers.get("X-Request-ID")
43
+ req_id = incoming_req_id or str(uuid4())
44
+
45
+ user_id = getattr(getattr(request, "state", None), "user_id", None) or request.headers.get("X-User-Id")
46
+ ip = request.client.host if request.client else None
47
+ ua = request.headers.get("user-agent")
48
+
49
+ tokens = bind_context(request=request, request_id=req_id, user_id=user_id, ip=ip, user_agent=ua)
50
+ try:
51
+ response = await call_next(request)
52
+ response.headers.setdefault("X-Request-ID", req_id)
53
+ return response
54
+ finally:
55
+ reset_context(tokens)
@@ -1,9 +1,11 @@
1
- from typing import Optional, Callable, Union
1
+ from typing import Callable, Optional, Union
2
+
2
3
  from fastapi import Request
3
4
  from starlette.middleware.base import BaseHTTPMiddleware
4
5
 
5
6
  try:
6
7
  from dependency_injector import providers
8
+
7
9
  ProviderType = providers.Provider # type: ignore
8
10
  except Exception:
9
11
  ProviderType = object
@@ -14,6 +16,7 @@ from nlbone.core.ports.auth import AuthService
14
16
  def _to_factory(auth: Union[AuthService, Callable[[], AuthService], ProviderType]):
15
17
  try:
16
18
  from dependency_injector import providers as _p # type: ignore
19
+
17
20
  if isinstance(auth, _p.Provider):
18
21
  return auth
19
22
  except Exception:
@@ -1,13 +1,12 @@
1
1
  from typing import Generic, List, Optional, TypeVar
2
- from pydantic import BaseModel
2
+
3
3
  from fastapi import Depends
4
+ from pydantic import BaseModel
4
5
 
5
- from .offset_base import PaginateResponse, PaginateRequest
6
+ from .offset_base import PaginateRequest, PaginateResponse
6
7
 
7
8
 
8
- def get_pagination(
9
- req: PaginateRequest = Depends(PaginateRequest)
10
- ) -> PaginateRequest:
9
+ def get_pagination(req: PaginateRequest = Depends(PaginateRequest)) -> PaginateRequest:
11
10
  return req
12
11
 
13
12
 
@@ -3,7 +3,6 @@ from math import ceil
3
3
  from typing import Any, Optional
4
4
 
5
5
  from fastapi import Query
6
- from sqlalchemy import asc, desc
7
6
 
8
7
 
9
8
  class PaginateRequest:
@@ -76,7 +75,6 @@ class PaginateRequest:
76
75
  return filters_dict
77
76
 
78
77
 
79
-
80
78
  class PaginateResponse:
81
79
  """
82
80
  Lightweight response shaper. If total_count is None → returns just items.
@@ -1,8 +1,10 @@
1
1
  import argparse
2
+
2
3
  import anyio
3
4
 
4
5
  from nlbone.adapters.db.sqlalchemy.schema import init_db_async, init_db_sync
5
6
 
7
+
6
8
  def main() -> None:
7
9
  parser = argparse.ArgumentParser(description="Initialize database schema (create_all).")
8
10
  parser.add_argument("--async", dest="use_async", action="store_true", help="Use AsyncEngine")
@@ -13,5 +15,6 @@ def main() -> None:
13
15
  else:
14
16
  init_db_sync()
15
17
 
18
+
16
19
  if __name__ == "__main__":
17
20
  main()