nlbone 0.1.34__tar.gz → 0.1.36__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nlbone-0.1.34 → nlbone-0.1.36}/PKG-INFO +2 -1
- {nlbone-0.1.34 → nlbone-0.1.36}/pyproject.toml +3 -2
- nlbone-0.1.36/src/nlbone/container.py +31 -0
- nlbone-0.1.36/src/nlbone/interfaces/api/middleware/__init__.py +3 -0
- nlbone-0.1.36/src/nlbone/interfaces/api/middleware/access_log.py +30 -0
- nlbone-0.1.36/src/nlbone/interfaces/api/middleware/add_request_context.py +52 -0
- nlbone-0.1.36/src/nlbone/interfaces/api/middleware/authentication.py +56 -0
- nlbone-0.1.36/src/nlbone/utils/context.py +42 -0
- nlbone-0.1.36/src/nlbone/utils/time.py +5 -0
- nlbone-0.1.34/src/nlbone/container.py +0 -13
- nlbone-0.1.34/src/nlbone/utils/time.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/.gitignore +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/LICENSE +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/README.md +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/__init__.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/adapters/__init__.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/adapters/auth/__init__.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/adapters/auth/keycloak.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/adapters/db/__init__.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/adapters/db/memory.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/adapters/db/postgres.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/adapters/db/query_builder.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/adapters/db/sqlalchemy/__init__.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/adapters/db/sqlalchemy/base.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/adapters/db/sqlalchemy/engine.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/adapters/db/sqlalchemy/query/__init__.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/adapters/db/sqlalchemy/query/builder.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/adapters/db/sqlalchemy/query/coercion.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/adapters/db/sqlalchemy/query/filters.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/adapters/db/sqlalchemy/query/ordering.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/adapters/db/sqlalchemy/query/types.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/adapters/db/sqlalchemy/schema.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/adapters/http_clients/__init__.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/adapters/http_clients/email_gateway.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/adapters/http_clients/uploadchi.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/adapters/http_clients/uploadchi_async.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/adapters/messaging/__init__.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/adapters/messaging/redis.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/config/__init__.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/config/logging.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/config/settings.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/core/__init__.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/core/application/__init__.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/core/application/services/__init__.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/core/application/services.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/core/application/use_cases/__init__.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/core/application/use_cases/register_user.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/core/domain/__init__.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/core/domain/events.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/core/domain/models.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/core/ports/__init__.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/core/ports/auth.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/core/ports/files.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/core/ports/messaging.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/core/ports/repo.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/interfaces/__init__.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/interfaces/api/__init__.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/interfaces/api/dependencies/__init__.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/interfaces/api/dependencies/db.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/interfaces/api/exceptions.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/interfaces/api/pagination/__init__.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/interfaces/api/pagination/offset_base.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/interfaces/api/routers.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/interfaces/api/schemas.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/interfaces/cli/__init__.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/interfaces/cli/init_db.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/interfaces/cli/main.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/interfaces/jobs/__init__.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/interfaces/jobs/sync_tokens.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/types.py +0 -0
- {nlbone-0.1.34 → nlbone-0.1.36}/src/nlbone/utils/__init__.py +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nlbone
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.36
|
|
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
|
|
7
7
|
License-File: LICENSE
|
|
8
8
|
Requires-Python: >=3.10
|
|
9
9
|
Requires-Dist: anyio>=4.0
|
|
10
|
+
Requires-Dist: dependency-injector>=4.48.1
|
|
10
11
|
Requires-Dist: fastapi>=0.116
|
|
11
12
|
Requires-Dist: httpx>=0.27
|
|
12
13
|
Requires-Dist: psycopg>=3.2.9
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "nlbone"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.36"
|
|
8
8
|
description = "Backbone package for interfaces and infrastructure in Python projects"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -21,7 +21,8 @@ dependencies = [
|
|
|
21
21
|
"starlette>=0.47",
|
|
22
22
|
"uvicorn>=0.35",
|
|
23
23
|
"sqlalchemy>=2.0",
|
|
24
|
-
"psycopg>=3.2.9"
|
|
24
|
+
"psycopg>=3.2.9",
|
|
25
|
+
"dependency-injector>=4.48.1"
|
|
25
26
|
]
|
|
26
27
|
|
|
27
28
|
[project.optional-dependencies]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Any, Mapping, Optional
|
|
3
|
+
|
|
4
|
+
from dependency_injector import containers, providers
|
|
5
|
+
|
|
6
|
+
from nlbone.adapters.http_clients.uploadchi import UploadchiClient
|
|
7
|
+
from nlbone.adapters.http_clients.uploadchi_async import UploadchiAsyncClient
|
|
8
|
+
from nlbone.adapters.auth.keycloak import KeycloakAuthService
|
|
9
|
+
from nlbone.core.ports.files import FileServicePort, AsyncFileServicePort
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Container(containers.DeclarativeContainer):
|
|
13
|
+
config = providers.Configuration(strict=False)
|
|
14
|
+
|
|
15
|
+
auth: providers.Singleton[KeycloakAuthService] = providers.Singleton(KeycloakAuthService, settings=config)
|
|
16
|
+
file_service: providers.Singleton[FileServicePort] = providers.Singleton(UploadchiClient)
|
|
17
|
+
afiles_service: providers.Singleton[AsyncFileServicePort] = providers.Singleton(UploadchiAsyncClient)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def create_container(settings: Optional[Any] = None) -> Container:
|
|
21
|
+
c = Container()
|
|
22
|
+
if settings is not None:
|
|
23
|
+
if hasattr(settings, "model_dump"):
|
|
24
|
+
c.config.from_dict(settings.model_dump()) # Pydantic v2
|
|
25
|
+
elif hasattr(settings, "dict"):
|
|
26
|
+
c.config.from_dict(settings.dict()) # Pydantic v1
|
|
27
|
+
elif isinstance(settings, Mapping):
|
|
28
|
+
c.config.from_dict(dict(settings))
|
|
29
|
+
else:
|
|
30
|
+
c.config.override(settings)
|
|
31
|
+
return c
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from typing import Callable
|
|
3
|
+
from fastapi import Request
|
|
4
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
5
|
+
|
|
6
|
+
from nlbone.config.logging import get_logger
|
|
7
|
+
|
|
8
|
+
logger = get_logger(__name__)
|
|
9
|
+
|
|
10
|
+
class AccessLogMiddleware(BaseHTTPMiddleware):
|
|
11
|
+
async def dispatch(self, request: Request, call_next: Callable):
|
|
12
|
+
start = time.perf_counter()
|
|
13
|
+
status_code = None
|
|
14
|
+
try:
|
|
15
|
+
response = await call_next(request)
|
|
16
|
+
status_code = getattr(response, "status_code", None)
|
|
17
|
+
return response
|
|
18
|
+
except Exception:
|
|
19
|
+
status_code = 500
|
|
20
|
+
raise
|
|
21
|
+
finally:
|
|
22
|
+
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
|
+
})
|
|
@@ -0,0 +1,52 @@
|
|
|
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)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from typing import Optional, Callable, Union
|
|
2
|
+
from fastapi import Request
|
|
3
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
from dependency_injector import providers
|
|
7
|
+
ProviderType = providers.Provider # type: ignore
|
|
8
|
+
except Exception:
|
|
9
|
+
ProviderType = object
|
|
10
|
+
|
|
11
|
+
from nlbone.core.ports.auth import AuthService
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _to_factory(auth: Union[AuthService, Callable[[], AuthService], ProviderType]):
|
|
15
|
+
try:
|
|
16
|
+
from dependency_injector import providers as _p # type: ignore
|
|
17
|
+
if isinstance(auth, _p.Provider):
|
|
18
|
+
return auth
|
|
19
|
+
except Exception:
|
|
20
|
+
pass
|
|
21
|
+
if callable(auth) and not hasattr(auth, "verify_token"):
|
|
22
|
+
return auth
|
|
23
|
+
return lambda: auth
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AuthenticationMiddleware(BaseHTTPMiddleware):
|
|
27
|
+
def __init__(self, app, auth: Union[AuthService, Callable[[], AuthService], ProviderType]):
|
|
28
|
+
super().__init__(app)
|
|
29
|
+
self._get_auth = _to_factory(auth)
|
|
30
|
+
|
|
31
|
+
async def dispatch(self, request: Request, call_next):
|
|
32
|
+
request.state.client_id = request.headers.get("X-Client-Id")
|
|
33
|
+
request.state.user_id = None
|
|
34
|
+
request.state.token = None
|
|
35
|
+
|
|
36
|
+
token: Optional[str] = None
|
|
37
|
+
authz = request.headers.get("Authorization")
|
|
38
|
+
if authz:
|
|
39
|
+
try:
|
|
40
|
+
scheme, token = authz.split(" ", 1)
|
|
41
|
+
if scheme.lower() != "bearer":
|
|
42
|
+
token = None
|
|
43
|
+
except ValueError:
|
|
44
|
+
token = None
|
|
45
|
+
|
|
46
|
+
if token:
|
|
47
|
+
request.state.token = token
|
|
48
|
+
try:
|
|
49
|
+
service: AuthService = self._get_auth()
|
|
50
|
+
data = service.verify_token(token)
|
|
51
|
+
if data:
|
|
52
|
+
request.state.user_id = data.get("user_id")
|
|
53
|
+
except Exception:
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
return await call_next(request)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from contextvars import ContextVar
|
|
2
|
+
from typing import Any, Optional
|
|
3
|
+
|
|
4
|
+
request_ctx: ContextVar[Any | None] = ContextVar("request", default=None)
|
|
5
|
+
request_id_ctx: ContextVar[str | None] = ContextVar("request_id", default=None)
|
|
6
|
+
user_id_ctx: ContextVar[str | None] = ContextVar("user_id", default=None)
|
|
7
|
+
ip_ctx: ContextVar[str | None] = ContextVar("ip", default=None)
|
|
8
|
+
user_agent_ctx: ContextVar[str | None] = ContextVar("user_agent", default=None)
|
|
9
|
+
|
|
10
|
+
def bind_context(*, request: Any | None = None, request_id: str | None = None,
|
|
11
|
+
user_id: str | None = None, ip: str | None = None,
|
|
12
|
+
user_agent: str | None = None) -> dict[ContextVar, Any]:
|
|
13
|
+
tokens: dict[ContextVar, Any] = {}
|
|
14
|
+
if request is not None:
|
|
15
|
+
tokens[request_ctx] = request_ctx.set(request)
|
|
16
|
+
if request_id is not None:
|
|
17
|
+
tokens[request_id_ctx] = request_id_ctx.set(request_id)
|
|
18
|
+
if user_id is not None:
|
|
19
|
+
tokens[user_id_ctx] = user_id_ctx.set(user_id)
|
|
20
|
+
if ip is not None:
|
|
21
|
+
tokens[ip_ctx] = ip_ctx.set(ip)
|
|
22
|
+
if user_agent is not None:
|
|
23
|
+
tokens[user_agent_ctx] = user_agent_ctx.set(user_agent)
|
|
24
|
+
return tokens
|
|
25
|
+
|
|
26
|
+
def reset_context(tokens: dict[ContextVar, Any]):
|
|
27
|
+
for var, token in tokens.items():
|
|
28
|
+
var.reset(token)
|
|
29
|
+
|
|
30
|
+
def current_request():
|
|
31
|
+
return request_ctx.get()
|
|
32
|
+
|
|
33
|
+
def current_request_id() -> Optional[str]:
|
|
34
|
+
return request_id_ctx.get()
|
|
35
|
+
|
|
36
|
+
def current_context_dict() -> dict[str, Any]:
|
|
37
|
+
return {
|
|
38
|
+
"request_id": request_id_ctx.get(),
|
|
39
|
+
"user_id": user_id_ctx.get(),
|
|
40
|
+
"ip": ip_ctx.get(),
|
|
41
|
+
"user_agent": user_agent_ctx.get(),
|
|
42
|
+
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
from nlbone.adapters.http_clients.uploadchi import UploadchiClient
|
|
2
|
-
from nlbone.adapters.http_clients.uploadchi_async import UploadchiAsyncClient
|
|
3
|
-
from nlbone.config.settings import Settings
|
|
4
|
-
from nlbone.adapters.auth.keycloak import KeycloakAuthService
|
|
5
|
-
from nlbone.core.ports.files import FileServicePort, AsyncFileServicePort
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class Container:
|
|
9
|
-
def __init__(self, settings: Settings | None = None):
|
|
10
|
-
self.settings = settings or Settings()
|
|
11
|
-
self.auth: KeycloakAuthService = KeycloakAuthService(self.settings)
|
|
12
|
-
self.file_service: FileServicePort = UploadchiClient()
|
|
13
|
-
self.afiles_service: AsyncFileServicePort = UploadchiAsyncClient()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|