nlbone 0.1.33__tar.gz → 0.1.35__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.33 → nlbone-0.1.35}/PKG-INFO +2 -1
- {nlbone-0.1.33 → nlbone-0.1.35}/pyproject.toml +3 -2
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/adapters/http_clients/uploadchi.py +34 -12
- nlbone-0.1.35/src/nlbone/container.py +31 -0
- nlbone-0.1.35/src/nlbone/interfaces/api/middleware/access_log.py +30 -0
- nlbone-0.1.35/src/nlbone/interfaces/api/middleware/add_request_context.py +52 -0
- nlbone-0.1.35/src/nlbone/interfaces/api/middleware/authentication.py +56 -0
- nlbone-0.1.35/src/nlbone/utils/context.py +42 -0
- nlbone-0.1.35/src/nlbone/utils/time.py +5 -0
- nlbone-0.1.33/src/nlbone/container.py +0 -13
- {nlbone-0.1.33 → nlbone-0.1.35}/.gitignore +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/LICENSE +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/README.md +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/__init__.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/adapters/__init__.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/adapters/auth/__init__.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/adapters/auth/keycloak.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/adapters/db/__init__.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/adapters/db/memory.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/adapters/db/postgres.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/adapters/db/query_builder.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/adapters/db/sqlalchemy/__init__.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/adapters/db/sqlalchemy/base.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/adapters/db/sqlalchemy/engine.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/adapters/db/sqlalchemy/query/__init__.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/adapters/db/sqlalchemy/query/builder.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/adapters/db/sqlalchemy/query/coercion.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/adapters/db/sqlalchemy/query/filters.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/adapters/db/sqlalchemy/query/ordering.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/adapters/db/sqlalchemy/query/types.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/adapters/db/sqlalchemy/schema.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/adapters/http_clients/__init__.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/adapters/http_clients/email_gateway.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/adapters/http_clients/uploadchi_async.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/adapters/messaging/__init__.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/adapters/messaging/redis.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/config/__init__.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/config/logging.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/config/settings.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/core/__init__.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/core/application/__init__.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/core/application/services/__init__.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/core/application/services.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/core/application/use_cases/__init__.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/core/application/use_cases/register_user.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/core/domain/__init__.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/core/domain/events.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/core/domain/models.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/core/ports/__init__.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/core/ports/auth.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/core/ports/files.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/core/ports/messaging.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/core/ports/repo.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/interfaces/__init__.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/interfaces/api/__init__.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/interfaces/api/dependencies/__init__.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/interfaces/api/dependencies/db.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/interfaces/api/exceptions.py +0 -0
- {nlbone-0.1.33/src/nlbone/interfaces/cli → nlbone-0.1.35/src/nlbone/interfaces/api/middleware}/__init__.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/interfaces/api/pagination/__init__.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/interfaces/api/pagination/offset_base.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/interfaces/api/routers.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/interfaces/api/schemas.py +0 -0
- {nlbone-0.1.33/src/nlbone/interfaces/jobs → nlbone-0.1.35/src/nlbone/interfaces/cli}/__init__.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/interfaces/cli/init_db.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/interfaces/cli/main.py +0 -0
- {nlbone-0.1.33/src/nlbone/utils → nlbone-0.1.35/src/nlbone/interfaces/jobs}/__init__.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/interfaces/jobs/sync_tokens.py +0 -0
- {nlbone-0.1.33 → nlbone-0.1.35}/src/nlbone/types.py +0 -0
- /nlbone-0.1.33/src/nlbone/utils/time.py → /nlbone-0.1.35/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.35
|
|
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.35"
|
|
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]
|
|
@@ -1,27 +1,35 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
import json
|
|
3
3
|
from typing import Any, Optional
|
|
4
|
+
from urllib.parse import urlparse, urlunparse
|
|
5
|
+
|
|
4
6
|
import httpx
|
|
7
|
+
import requests
|
|
5
8
|
|
|
6
9
|
from nlbone.core.ports.files import FileServicePort
|
|
7
10
|
from nlbone.config.settings import get_settings
|
|
8
11
|
|
|
12
|
+
|
|
9
13
|
class UploadchiError(RuntimeError):
|
|
10
14
|
def __init__(self, status: int, detail: Any | None = None):
|
|
11
15
|
super().__init__(f"Uploadchi HTTP {status}: {detail}")
|
|
12
16
|
self.status = status
|
|
13
17
|
self.detail = detail
|
|
14
18
|
|
|
19
|
+
|
|
15
20
|
def _resolve_token(explicit: str | None) -> str | None:
|
|
16
21
|
if explicit is not None:
|
|
17
22
|
return explicit
|
|
18
23
|
s = get_settings()
|
|
19
24
|
return s.UPLOADCHI_TOKEN.get_secret_value() if s.UPLOADCHI_TOKEN else None
|
|
20
25
|
|
|
26
|
+
|
|
21
27
|
def _auth_headers(token: str | None) -> dict[str, str]:
|
|
22
28
|
return {"Authorization": f"Bearer {token}"} if token else {}
|
|
23
29
|
|
|
24
|
-
|
|
30
|
+
|
|
31
|
+
def _build_list_query(limit: int, offset: int, filters: dict[str, Any] | None, sort: list[tuple[str, str]] | None) -> \
|
|
32
|
+
dict[str, Any]:
|
|
25
33
|
q: dict[str, Any] = {"limit": limit, "offset": offset}
|
|
26
34
|
if filters:
|
|
27
35
|
q["filters"] = json.dumps(filters)
|
|
@@ -29,6 +37,7 @@ def _build_list_query(limit: int, offset: int, filters: dict[str, Any] | None, s
|
|
|
29
37
|
q["sort"] = ",".join([f"{f}:{o}" for f, o in sort])
|
|
30
38
|
return q
|
|
31
39
|
|
|
40
|
+
|
|
32
41
|
def _filename_from_cd(cd: str | None, fallback: str) -> str:
|
|
33
42
|
if not cd:
|
|
34
43
|
return fallback
|
|
@@ -36,49 +45,62 @@ def _filename_from_cd(cd: str | None, fallback: str) -> str:
|
|
|
36
45
|
return cd.split("filename=", 1)[1].strip("\"'")
|
|
37
46
|
return fallback
|
|
38
47
|
|
|
48
|
+
|
|
49
|
+
def _normalize_https_base(url: str) -> str:
|
|
50
|
+
p = urlparse(url.strip())
|
|
51
|
+
p = p._replace(scheme="https") # enforce https
|
|
52
|
+
if p.path.endswith("/"):
|
|
53
|
+
p = p._replace(path=p.path.rstrip("/"))
|
|
54
|
+
return str(urlunparse(p))
|
|
55
|
+
|
|
56
|
+
|
|
39
57
|
class UploadchiClient(FileServicePort):
|
|
40
|
-
def __init__(self, base_url: Optional[str] = None, timeout_seconds: Optional[float] = None,
|
|
58
|
+
def __init__(self, base_url: Optional[str] = None, timeout_seconds: Optional[float] = None,
|
|
59
|
+
client: httpx.Client | None = None) -> None:
|
|
41
60
|
s = get_settings()
|
|
42
|
-
self._base_url = base_url or str(s.UPLOADCHI_BASE_URL)
|
|
61
|
+
self._base_url = _normalize_https_base(base_url or str(s.UPLOADCHI_BASE_URL))
|
|
43
62
|
self._timeout = timeout_seconds or float(s.HTTP_TIMEOUT_SECONDS)
|
|
44
|
-
self._client = client or
|
|
63
|
+
self._client = client or requests.session()
|
|
45
64
|
|
|
46
65
|
def close(self) -> None:
|
|
47
66
|
self._client.close()
|
|
48
67
|
|
|
49
|
-
def upload_file(self, file_bytes: bytes, filename: str, params: dict[str, Any] | None = None,
|
|
68
|
+
def upload_file(self, file_bytes: bytes, filename: str, params: dict[str, Any] | None = None,
|
|
69
|
+
token: str | None = None) -> dict:
|
|
50
70
|
tok = _resolve_token(token)
|
|
51
71
|
files = {"file": (filename, file_bytes)}
|
|
52
72
|
data = (params or {}).copy()
|
|
53
|
-
r = self._client.post(
|
|
73
|
+
r = self._client.post(self._base_url, files=files, data=data, headers=_auth_headers(tok))
|
|
54
74
|
if r.status_code >= 400:
|
|
55
75
|
raise UploadchiError(r.status_code, r.text)
|
|
56
76
|
return r.json()
|
|
57
77
|
|
|
58
78
|
def commit_file(self, file_id: int, client_id: str, token: str | None = None) -> None:
|
|
59
79
|
tok = _resolve_token(token)
|
|
60
|
-
r = self._client.post(f"/{file_id}/commit", headers=_auth_headers(tok),
|
|
80
|
+
r = self._client.post(f"{self._base_url}/{file_id}/commit", headers=_auth_headers(tok),
|
|
81
|
+
params={"client_id": client_id} if client_id else None)
|
|
61
82
|
if r.status_code not in (204, 200):
|
|
62
83
|
raise UploadchiError(r.status_code, r.text)
|
|
63
84
|
|
|
64
|
-
def list_files(self, limit: int = 10, offset: int = 0, filters: dict[str, Any] | None = None,
|
|
85
|
+
def list_files(self, limit: int = 10, offset: int = 0, filters: dict[str, Any] | None = None,
|
|
86
|
+
sort: list[tuple[str, str]] | None = None, token: str | None = None) -> dict:
|
|
65
87
|
tok = _resolve_token(token)
|
|
66
88
|
q = _build_list_query(limit, offset, filters, sort)
|
|
67
|
-
r = self._client.get(
|
|
89
|
+
r = self._client.get(self._base_url, params=q, headers=_auth_headers(tok))
|
|
68
90
|
if r.status_code >= 400:
|
|
69
91
|
raise UploadchiError(r.status_code, r.text)
|
|
70
92
|
return r.json()
|
|
71
93
|
|
|
72
94
|
def get_file(self, file_id: int, token: str | None = None) -> dict:
|
|
73
95
|
tok = _resolve_token(token)
|
|
74
|
-
r = self._client.get(f"/{file_id}", headers=_auth_headers(tok))
|
|
96
|
+
r = self._client.get(f"{self._base_url}/{file_id}", headers=_auth_headers(tok))
|
|
75
97
|
if r.status_code >= 400:
|
|
76
98
|
raise UploadchiError(r.status_code, r.text)
|
|
77
99
|
return r.json()
|
|
78
100
|
|
|
79
101
|
def download_file(self, file_id: int, token: str | None = None) -> tuple[bytes, str, str]:
|
|
80
102
|
tok = _resolve_token(token)
|
|
81
|
-
r = self._client.get(f"/{file_id}/download", headers=_auth_headers(tok))
|
|
103
|
+
r = self._client.get(f"{self._base_url}/{file_id}/download", headers=_auth_headers(tok))
|
|
82
104
|
if r.status_code >= 400:
|
|
83
105
|
raise UploadchiError(r.status_code, r.text)
|
|
84
106
|
filename = _filename_from_cd(r.headers.get("content-disposition"), fallback=f"file-{file_id}")
|
|
@@ -87,6 +109,6 @@ class UploadchiClient(FileServicePort):
|
|
|
87
109
|
|
|
88
110
|
def delete_file(self, file_id: int, token: str | None = None) -> None:
|
|
89
111
|
tok = _resolve_token(token)
|
|
90
|
-
r = self._client.delete(f"/{file_id}", headers=_auth_headers(tok))
|
|
112
|
+
r = self._client.delete(f"{self._base_url}/{file_id}", headers=_auth_headers(tok))
|
|
91
113
|
if r.status_code not in (204, 200):
|
|
92
114
|
raise UploadchiError(r.status_code, r.text)
|
|
@@ -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
|
{nlbone-0.1.33/src/nlbone/interfaces/jobs → nlbone-0.1.35/src/nlbone/interfaces/cli}/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|