nlbone 0.7.31__tar.gz → 0.8.0__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.7.31 → nlbone-0.8.0}/PKG-INFO +1 -1
- {nlbone-0.7.31 → nlbone-0.8.0}/pyproject.toml +1 -1
- nlbone-0.8.0/src/nlbone/adapters/i18n/engine.py +40 -0
- nlbone-0.8.0/src/nlbone/adapters/i18n/locales/fa-IR.json +5 -0
- nlbone-0.8.0/src/nlbone/interfaces/api/exception_handlers.py +166 -0
- nlbone-0.8.0/src/nlbone/interfaces/api/exceptions.py +153 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/api/schemas.py +1 -0
- nlbone-0.8.0/src/nlbone/utils/flatten_dict.py +9 -0
- nlbone-0.7.31/src/nlbone/adapters/i18n/engine.py +0 -35
- nlbone-0.7.31/src/nlbone/adapters/i18n/locales/fa-IR.json +0 -10
- nlbone-0.7.31/src/nlbone/interfaces/api/exception_handlers.py +0 -114
- nlbone-0.7.31/src/nlbone/interfaces/api/exceptions.py +0 -108
- {nlbone-0.7.31 → nlbone-0.8.0}/.gitignore +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/LICENSE +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/README.md +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/auth/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/auth/auth_service.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/auth/keycloak.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/auth/token_provider.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/cache/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/cache/async_redis.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/cache/memory.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/cache/pubsub_listener.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/cache/redis.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/db/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/db/postgres/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/db/postgres/audit.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/db/postgres/base.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/db/postgres/engine.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/db/postgres/query_builder.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/db/postgres/repository.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/db/postgres/schema.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/db/postgres/types.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/db/postgres/uow.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/db/redis/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/db/redis/client.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/http_clients/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/http_clients/pricing/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/http_clients/pricing/pricing_service.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/http_clients/uploadchi/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/http_clients/uploadchi/uploadchi.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/http_clients/uploadchi/uploadchi_async.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/i18n/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/i18n/loaders.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/messaging/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/messaging/event_bus.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/messaging/rabbitmq.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/outbox/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/outbox/outbox_consumer.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/outbox/outbox_repo.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/percolation/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/percolation/connection.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/repositories/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/snowflake.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/ticketing/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/adapters/ticketing/client.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/config/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/config/logging.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/config/settings.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/container.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/core/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/core/application/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/core/application/base_worker.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/core/application/bus.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/core/application/di.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/core/application/registry.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/core/application/services/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/core/application/use_case.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/core/domain/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/core/domain/base.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/core/domain/models.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/core/ports/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/core/ports/auth.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/core/ports/cache.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/core/ports/event_bus.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/core/ports/files.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/core/ports/outbox.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/core/ports/repository.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/core/ports/translation.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/core/ports/uow.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/api/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/api/additional_filed/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/api/additional_filed/assembler.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/api/additional_filed/default_field_rules/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/api/additional_filed/default_field_rules/image_field_rules.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/api/additional_filed/field_registry.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/api/additional_filed/resolver.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/api/dependencies/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/api/dependencies/async_auth.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/api/dependencies/auth.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/api/dependencies/client_credential.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/api/dependencies/db.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/api/dependencies/uow.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/api/middleware/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/api/middleware/access_log.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/api/middleware/add_request_context.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/api/middleware/authentication.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/api/pagination/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/api/pagination/offset_base.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/api/routers.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/api/schema/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/api/schema/adaptive_schema.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/api/schema/base_response_model.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/cli/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/cli/crypto.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/cli/init_db.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/cli/main.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/cli/ticket.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/jobs/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/jobs/dispatch_outbox.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/interfaces/jobs/sync_tokens.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/types.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/utils/__init__.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/utils/cache.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/utils/cache_keys.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/utils/cache_registry.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/utils/context.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/utils/crypto.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/utils/flatten_sqlalchemy_result.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/utils/http.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/utils/normalize_mobile.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/utils/redactor.py +0 -0
- {nlbone-0.7.31 → nlbone-0.8.0}/src/nlbone/utils/time.py +0 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Dict, Optional
|
|
3
|
+
|
|
4
|
+
from nlbone.adapters.i18n.loaders import BaseLoader
|
|
5
|
+
|
|
6
|
+
from nlbone.core.ports.translation import TranslationPort
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class I18nAdapter(TranslationPort):
|
|
12
|
+
def __init__(self, loader: BaseLoader, default_locale: str = "fa-IR"):
|
|
13
|
+
self.default_locale = default_locale
|
|
14
|
+
self.loader = loader
|
|
15
|
+
self._translations: Optional[Dict[str, Dict[str, str]]] = None
|
|
16
|
+
|
|
17
|
+
def _ensure_loaded(self):
|
|
18
|
+
if self._translations is None:
|
|
19
|
+
self._translations = self.loader.load()
|
|
20
|
+
|
|
21
|
+
def translate(self, key: str, locale: Optional[str] = None, **kwargs) -> str:
|
|
22
|
+
target_locale = locale or self.default_locale
|
|
23
|
+
try:
|
|
24
|
+
self._ensure_loaded()
|
|
25
|
+
|
|
26
|
+
locale_data = self._translations.get(target_locale, {})
|
|
27
|
+
text = locale_data.get(key)
|
|
28
|
+
|
|
29
|
+
if text is None:
|
|
30
|
+
text = self._translations.get(self.default_locale, {}).get(key, key)
|
|
31
|
+
|
|
32
|
+
if kwargs:
|
|
33
|
+
try:
|
|
34
|
+
return text.format(**kwargs)
|
|
35
|
+
except KeyError:
|
|
36
|
+
pass
|
|
37
|
+
return text
|
|
38
|
+
except:
|
|
39
|
+
logger.exception("Failed to translate key '{}' to locale '{}'".format(key, target_locale))
|
|
40
|
+
return key
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Mapping, Optional, List
|
|
4
|
+
from uuid import uuid4
|
|
5
|
+
|
|
6
|
+
from fastapi import FastAPI, Request
|
|
7
|
+
from fastapi import HTTPException as FastAPIHTTPException
|
|
8
|
+
from fastapi.exceptions import RequestValidationError
|
|
9
|
+
from fastapi.responses import JSONResponse
|
|
10
|
+
from pydantic import BaseModel, ValidationError
|
|
11
|
+
from starlette.exceptions import HTTPException as StarletteHTTPException
|
|
12
|
+
from starlette.status import HTTP_500_INTERNAL_SERVER_ERROR, HTTP_422_UNPROCESSABLE_ENTITY
|
|
13
|
+
|
|
14
|
+
from nlbone.adapters.i18n import translator as _
|
|
15
|
+
from .exceptions import BaseHttpException, ErrorDetail, UnprocessableEntityException
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ErrorResponse(BaseModel):
|
|
19
|
+
message: str
|
|
20
|
+
errors: List[ErrorDetail]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _ensure_trace_id(request: Request) -> str:
|
|
24
|
+
rid = request.headers.get("X-Request-Id") or request.headers.get("X-Trace-Id")
|
|
25
|
+
return rid or str(uuid4())
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _json_response(
|
|
29
|
+
request: Request,
|
|
30
|
+
status_code: int,
|
|
31
|
+
content: Any,
|
|
32
|
+
trace_id: Optional[str] = None,
|
|
33
|
+
headers: Optional[Mapping[str, str]] = None,
|
|
34
|
+
) -> JSONResponse:
|
|
35
|
+
tid = trace_id or _ensure_trace_id(request)
|
|
36
|
+
|
|
37
|
+
if isinstance(content, dict):
|
|
38
|
+
payload = content
|
|
39
|
+
else:
|
|
40
|
+
payload = content.model_dump(exclude_none=True) if hasattr(content, "model_dump") else content
|
|
41
|
+
|
|
42
|
+
payload["trace_id"] = tid
|
|
43
|
+
|
|
44
|
+
base_headers = {"X-Trace-Id": tid}
|
|
45
|
+
if headers:
|
|
46
|
+
base_headers.update(headers)
|
|
47
|
+
|
|
48
|
+
return JSONResponse(
|
|
49
|
+
status_code=status_code,
|
|
50
|
+
content=payload,
|
|
51
|
+
headers=base_headers
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def install_exception_handlers(
|
|
56
|
+
app: FastAPI,
|
|
57
|
+
*,
|
|
58
|
+
logger: Any = None,
|
|
59
|
+
expose_server_errors: bool = False,
|
|
60
|
+
) -> None:
|
|
61
|
+
async def _log_exception(
|
|
62
|
+
request: Request,
|
|
63
|
+
exc: Exception,
|
|
64
|
+
level: str = "warning",
|
|
65
|
+
extra: Optional[dict] = None
|
|
66
|
+
):
|
|
67
|
+
if not logger:
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
log_payload = {
|
|
71
|
+
"path": request.url.path,
|
|
72
|
+
"method": request.method,
|
|
73
|
+
**extra
|
|
74
|
+
} if extra else {"path": request.url.path}
|
|
75
|
+
|
|
76
|
+
log_method = getattr(logger, level, logger.warning)
|
|
77
|
+
log_method(str(exc), extra=log_payload)
|
|
78
|
+
|
|
79
|
+
@app.exception_handler(BaseHttpException)
|
|
80
|
+
async def _handle_base_http_exception(request: Request, exc: BaseHttpException):
|
|
81
|
+
await _log_exception(request, exc, extra={"status": exc.status_code, "detail": exc.message})
|
|
82
|
+
|
|
83
|
+
locale = getattr(request.state, "locale", None)
|
|
84
|
+
|
|
85
|
+
main_message = _(exc.message, locale=locale)
|
|
86
|
+
|
|
87
|
+
translated_errors = []
|
|
88
|
+
if exc.errors:
|
|
89
|
+
for err in exc.errors:
|
|
90
|
+
err_copy = err.model_copy()
|
|
91
|
+
if err_copy.message:
|
|
92
|
+
err_copy.message = _(err_copy.message, locale=locale)
|
|
93
|
+
translated_errors.append(err_copy)
|
|
94
|
+
|
|
95
|
+
response_model = ErrorResponse(
|
|
96
|
+
message=main_message,
|
|
97
|
+
errors=translated_errors
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
return _json_response(request, exc.status_code, content=response_model)
|
|
101
|
+
|
|
102
|
+
@app.exception_handler(RequestValidationError)
|
|
103
|
+
async def _handle_request_validation_error(request: Request, exc: RequestValidationError):
|
|
104
|
+
await _log_exception(request, exc, level="info", extra={"errors": exc.errors()})
|
|
105
|
+
|
|
106
|
+
normalized_exception = UnprocessableEntityException(
|
|
107
|
+
detail="Validation Error",
|
|
108
|
+
validation_errors=exc.errors()
|
|
109
|
+
)
|
|
110
|
+
return await _handle_base_http_exception(request, normalized_exception)
|
|
111
|
+
|
|
112
|
+
@app.exception_handler(ValidationError)
|
|
113
|
+
async def _handle_pydantic_validation_error(request: Request, exc: ValidationError):
|
|
114
|
+
await _log_exception(request, exc, level="info", extra={"errors": exc.errors()})
|
|
115
|
+
|
|
116
|
+
normalized_exception = UnprocessableEntityException(
|
|
117
|
+
detail="Validation Error",
|
|
118
|
+
validation_errors=exc.errors()
|
|
119
|
+
)
|
|
120
|
+
return await _handle_base_http_exception(request, normalized_exception)
|
|
121
|
+
|
|
122
|
+
@app.exception_handler(StarletteHTTPException)
|
|
123
|
+
async def _handle_starlette_http_exception(request: Request, exc: StarletteHTTPException):
|
|
124
|
+
await _log_exception(request, exc, extra={"status": exc.status_code})
|
|
125
|
+
|
|
126
|
+
locale = getattr(request.state, "locale", None)
|
|
127
|
+
message_str = str(exc.detail) if exc.detail else "HTTP Error"
|
|
128
|
+
translated_message = _(message_str, locale=locale)
|
|
129
|
+
|
|
130
|
+
error_detail = ErrorDetail(
|
|
131
|
+
code=exc.status_code,
|
|
132
|
+
message=translated_message
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
response_model = ErrorResponse(
|
|
136
|
+
message=translated_message,
|
|
137
|
+
errors=[error_detail]
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
return _json_response(request, exc.status_code, content=response_model)
|
|
141
|
+
|
|
142
|
+
@app.exception_handler(Exception)
|
|
143
|
+
async def _handle_unexpected_exception(request: Request, exc: Exception):
|
|
144
|
+
tid = _ensure_trace_id(request)
|
|
145
|
+
await _log_exception(request, exc, level="exception", extra={"trace_id": tid})
|
|
146
|
+
|
|
147
|
+
raw_detail = str(exc) if expose_server_errors else "Internal Server Error"
|
|
148
|
+
locale = getattr(request.state, "locale", None)
|
|
149
|
+
translated_detail = _(raw_detail, locale=locale)
|
|
150
|
+
|
|
151
|
+
error_detail = ErrorDetail(
|
|
152
|
+
code=HTTP_500_INTERNAL_SERVER_ERROR,
|
|
153
|
+
message=translated_detail
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
response_model = ErrorResponse(
|
|
157
|
+
message=translated_detail,
|
|
158
|
+
errors=[error_detail]
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
return _json_response(
|
|
162
|
+
request,
|
|
163
|
+
HTTP_500_INTERNAL_SERVER_ERROR,
|
|
164
|
+
content=response_model,
|
|
165
|
+
trace_id=tid
|
|
166
|
+
)
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
from http import HTTPStatus
|
|
2
|
+
from typing import Any, Dict, List, Optional, Union
|
|
3
|
+
|
|
4
|
+
from fastapi import HTTPException
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ErrorDetail(BaseModel):
|
|
9
|
+
code: int | str = 0
|
|
10
|
+
message: Optional[str] = None
|
|
11
|
+
fields: Optional[Union[List[Union[str, int]], str]] = None
|
|
12
|
+
data: Optional[Union[List[Dict], Dict]] = None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BaseHttpException(HTTPException):
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
status_code: int,
|
|
19
|
+
detail: Optional[str] = None,
|
|
20
|
+
errors: Optional[List[ErrorDetail]] = None,
|
|
21
|
+
fields: Optional[Union[List[Union[str, int]], str]] = None,
|
|
22
|
+
data: Optional[Dict] = None,
|
|
23
|
+
code: Optional[str] = None,
|
|
24
|
+
):
|
|
25
|
+
if errors is None:
|
|
26
|
+
message = detail or HTTPStatus(status_code).phrase
|
|
27
|
+
|
|
28
|
+
if not code:
|
|
29
|
+
code = HTTPStatus(status_code).phrase.lower().replace(" ", "_")
|
|
30
|
+
|
|
31
|
+
errors = [
|
|
32
|
+
ErrorDetail(
|
|
33
|
+
code=code,
|
|
34
|
+
message=message,
|
|
35
|
+
fields=fields,
|
|
36
|
+
data=data
|
|
37
|
+
)
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
self.errors = errors
|
|
41
|
+
super().__init__(status_code=status_code, detail=self.message)
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def message(self) -> str:
|
|
45
|
+
if self.errors and self.errors[0].message:
|
|
46
|
+
return self.errors[0].message
|
|
47
|
+
return HTTPStatus(self.status_code).phrase
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class UnprocessableEntityException(BaseHttpException):
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
detail: str = None,
|
|
54
|
+
fields: Optional[Union[List[Union[str, int]], str]] = None,
|
|
55
|
+
validation_errors: Optional[List[Dict[str, Any]]] = None
|
|
56
|
+
):
|
|
57
|
+
errors = None
|
|
58
|
+
|
|
59
|
+
if validation_errors:
|
|
60
|
+
errors = []
|
|
61
|
+
for error in validation_errors:
|
|
62
|
+
err_type = error.get("type")
|
|
63
|
+
loc = error.get("loc")
|
|
64
|
+
msg = error.get("msg")
|
|
65
|
+
inp = error.get("input")
|
|
66
|
+
|
|
67
|
+
if err_type == "json_invalid":
|
|
68
|
+
loc = ["body"]
|
|
69
|
+
msg = "Invalid JSON format. Please check your syntax (e.g., convert '۱' to '1')."
|
|
70
|
+
|
|
71
|
+
elif isinstance(loc, tuple):
|
|
72
|
+
loc = list(loc)
|
|
73
|
+
|
|
74
|
+
error_data = {}
|
|
75
|
+
if inp is not None and not (isinstance(inp, dict) and not inp):
|
|
76
|
+
error_data["input"] = inp
|
|
77
|
+
|
|
78
|
+
errors.append(
|
|
79
|
+
ErrorDetail(
|
|
80
|
+
code=err_type or "validation_error",
|
|
81
|
+
message=msg,
|
|
82
|
+
fields=loc,
|
|
83
|
+
data=error_data if error_data else None
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
super().__init__(
|
|
88
|
+
status_code=HTTPStatus.UNPROCESSABLE_ENTITY,
|
|
89
|
+
detail=detail,
|
|
90
|
+
fields=fields,
|
|
91
|
+
errors=errors
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class BadRequestException(BaseHttpException):
|
|
96
|
+
def __init__(self, detail: str = None, code: int = 0):
|
|
97
|
+
super().__init__(status_code=HTTPStatus.BAD_REQUEST, detail=detail, code=code)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class UnauthorizedException(BaseHttpException):
|
|
101
|
+
def __init__(self, detail: str = None):
|
|
102
|
+
super().__init__(status_code=HTTPStatus.UNAUTHORIZED, detail=detail)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class ForbiddenException(BaseHttpException):
|
|
106
|
+
def __init__(self, detail: str = None):
|
|
107
|
+
super().__init__(status_code=HTTPStatus.FORBIDDEN, detail=detail)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class NotFoundException(BaseHttpException):
|
|
111
|
+
def __init__(self, detail: str = None):
|
|
112
|
+
super().__init__(status_code=HTTPStatus.NOT_FOUND, detail=detail)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class ConflictException(BaseHttpException):
|
|
116
|
+
def __init__(self, detail: str = None, data: Optional[Union[List[Dict], Dict]] = None):
|
|
117
|
+
super().__init__(status_code=HTTPStatus.CONFLICT, detail=detail, data=data)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class NotSupportedException(BaseHttpException):
|
|
121
|
+
def __init__(self, detail: str = None):
|
|
122
|
+
super().__init__(status_code=HTTPStatus.NOT_IMPLEMENTED, detail=detail)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class InternalServerException(BaseHttpException):
|
|
126
|
+
def __init__(self, detail: str = None):
|
|
127
|
+
super().__init__(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=detail)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class LogicalValidationException(UnprocessableEntityException):
|
|
131
|
+
def __init__(self, detail: str = None,
|
|
132
|
+
fields: Optional[Union[List[str], str]] = None,
|
|
133
|
+
validation_errors: Optional[List[Dict[str, Any]]] = None):
|
|
134
|
+
super().__init__(
|
|
135
|
+
detail=detail,
|
|
136
|
+
fields=fields,
|
|
137
|
+
validation_errors=validation_errors
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class GoneException(BaseHttpException):
|
|
142
|
+
def __init__(self, detail: str = None):
|
|
143
|
+
super().__init__(status_code=HTTPStatus.GONE, detail=detail)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class TooManyRequestsException(BaseHttpException):
|
|
147
|
+
def __init__(self, detail: str = None):
|
|
148
|
+
super().__init__(status_code=HTTPStatus.TOO_MANY_REQUESTS, detail=detail)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class NotAcceptableException(BaseHttpException):
|
|
152
|
+
def __init__(self, detail: str = None):
|
|
153
|
+
super().__init__(status_code=HTTPStatus.NOT_ACCEPTABLE, detail=detail)
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
from typing import Dict, Optional
|
|
2
|
-
|
|
3
|
-
from nlbone.core.ports.translation import TranslationPort
|
|
4
|
-
from nlbone.utils.context import get_locale
|
|
5
|
-
|
|
6
|
-
from .loaders import BaseLoader
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class I18nAdapter(TranslationPort):
|
|
10
|
-
def __init__(self, loader: BaseLoader, default_locale: str = "fa-IR"):
|
|
11
|
-
self.default_locale = default_locale
|
|
12
|
-
self.loader = loader
|
|
13
|
-
self._translations: Optional[Dict[str, Dict[str, str]]] = None
|
|
14
|
-
|
|
15
|
-
def _ensure_loaded(self):
|
|
16
|
-
if self._translations is None:
|
|
17
|
-
self._translations = self.loader.load()
|
|
18
|
-
|
|
19
|
-
def translate(self, key: str, locale: Optional[str] = None, **kwargs) -> str:
|
|
20
|
-
self._ensure_loaded()
|
|
21
|
-
|
|
22
|
-
target_locale = locale or self.default_locale
|
|
23
|
-
|
|
24
|
-
locale_data = self._translations.get(target_locale, {})
|
|
25
|
-
text = locale_data.get(key)
|
|
26
|
-
|
|
27
|
-
if text is None:
|
|
28
|
-
text = self._translations.get(self.default_locale, {}).get(key, key)
|
|
29
|
-
|
|
30
|
-
if kwargs:
|
|
31
|
-
try:
|
|
32
|
-
return text.format(**kwargs)
|
|
33
|
-
except KeyError:
|
|
34
|
-
pass
|
|
35
|
-
return text
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import Any, Mapping, Optional
|
|
4
|
-
from uuid import uuid4
|
|
5
|
-
|
|
6
|
-
from fastapi import FastAPI, Request
|
|
7
|
-
from fastapi import HTTPException as FastAPIHTTPException
|
|
8
|
-
from fastapi.exceptions import RequestValidationError
|
|
9
|
-
from fastapi.responses import JSONResponse
|
|
10
|
-
from pydantic import ValidationError
|
|
11
|
-
from starlette.exceptions import HTTPException as StarletteHTTPException
|
|
12
|
-
|
|
13
|
-
from .exceptions import BaseHttpException
|
|
14
|
-
|
|
15
|
-
from nlbone.adapters.i18n import translator as _
|
|
16
|
-
# ---- Helpers ---------------------------------------------------------------
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def _ensure_trace_id(request: Request) -> str:
|
|
20
|
-
rid = request.headers.get("X-Request-Id") or request.headers.get("X-Trace-Id")
|
|
21
|
-
return rid or str(uuid4())
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def _json_response(
|
|
25
|
-
request: Request,
|
|
26
|
-
status_code: int,
|
|
27
|
-
*,
|
|
28
|
-
detail: Any,
|
|
29
|
-
headers: Optional[Mapping[str, str]] = None,
|
|
30
|
-
trace_id: Optional[str] = None,
|
|
31
|
-
extra: Optional[Mapping[str, Any]] = None,
|
|
32
|
-
) -> JSONResponse:
|
|
33
|
-
payload: dict[str, Any] = {"detail": detail}
|
|
34
|
-
if extra:
|
|
35
|
-
payload.update(extra)
|
|
36
|
-
tid = trace_id or _ensure_trace_id(request)
|
|
37
|
-
|
|
38
|
-
payload.setdefault("trace_id", tid)
|
|
39
|
-
|
|
40
|
-
base_headers = {"X-Trace-Id": tid}
|
|
41
|
-
if headers:
|
|
42
|
-
base_headers.update(headers)
|
|
43
|
-
|
|
44
|
-
return JSONResponse(status_code=status_code, content=payload, headers=base_headers)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
# ---- Public Installer ------------------------------------------------------
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def install_exception_handlers(
|
|
51
|
-
app: FastAPI,
|
|
52
|
-
*,
|
|
53
|
-
logger: Any = None,
|
|
54
|
-
expose_server_errors: bool = False,
|
|
55
|
-
) -> None:
|
|
56
|
-
@app.exception_handler(BaseHttpException)
|
|
57
|
-
async def _handle_base_http_exception(request: Request, exc: BaseHttpException):
|
|
58
|
-
if logger:
|
|
59
|
-
logger.warning(
|
|
60
|
-
"http_error",
|
|
61
|
-
extra={"status": exc.status_code, "detail": exc.detail, "path": request.url.path},
|
|
62
|
-
)
|
|
63
|
-
locale = locale = getattr(request.state, "locale", None)
|
|
64
|
-
return _json_response(request, exc.status_code, detail=_(exc.detail, locale=locale))
|
|
65
|
-
|
|
66
|
-
@app.exception_handler(FastAPIHTTPException)
|
|
67
|
-
async def _handle_fastapi_http_exception(request: Request, exc: FastAPIHTTPException):
|
|
68
|
-
if logger:
|
|
69
|
-
logger.warning(
|
|
70
|
-
"fastapi_http_error",
|
|
71
|
-
extra={"status": exc.status_code, "detail": exc.detail, "path": request.url.path},
|
|
72
|
-
)
|
|
73
|
-
locale = locale = getattr(request.state, "locale", None)
|
|
74
|
-
return _json_response(request, exc.status_code, detail=_(exc.detail, locale=locale))
|
|
75
|
-
|
|
76
|
-
@app.exception_handler(StarletteHTTPException)
|
|
77
|
-
async def _handle_starlette_http_exception(request: Request, exc: StarletteHTTPException):
|
|
78
|
-
if logger:
|
|
79
|
-
logger.warning(
|
|
80
|
-
"starlette_http_error",
|
|
81
|
-
extra={"status": exc.status_code, "detail": exc.detail, "path": request.url.path},
|
|
82
|
-
)
|
|
83
|
-
locale = locale = getattr(request.state, "locale", None)
|
|
84
|
-
return _json_response(request, exc.status_code, detail=_(exc.detail, locale=locale))
|
|
85
|
-
|
|
86
|
-
# 3) خطاهای اعتبارسنجی FastAPI (request body/query/path)
|
|
87
|
-
@app.exception_handler(RequestValidationError)
|
|
88
|
-
async def _handle_request_validation_error(request: Request, exc: RequestValidationError):
|
|
89
|
-
errors = exc.errors()
|
|
90
|
-
if logger:
|
|
91
|
-
logger.info(
|
|
92
|
-
"request_validation_error",
|
|
93
|
-
extra={"errors": errors, "path": request.url.path},
|
|
94
|
-
)
|
|
95
|
-
return _json_response(request, 422, detail=errors)
|
|
96
|
-
|
|
97
|
-
@app.exception_handler(ValidationError)
|
|
98
|
-
async def _handle_pydantic_validation_error(request: Request, exc: ValidationError):
|
|
99
|
-
errors = exc.errors()
|
|
100
|
-
if logger:
|
|
101
|
-
logger.info(
|
|
102
|
-
"pydantic_validation_error",
|
|
103
|
-
extra={"errors": errors, "path": request.url.path},
|
|
104
|
-
)
|
|
105
|
-
return _json_response(request, 422, detail=errors)
|
|
106
|
-
|
|
107
|
-
@app.exception_handler(Exception)
|
|
108
|
-
async def _handle_unexpected_exception(request: Request, exc: Exception):
|
|
109
|
-
tid = _ensure_trace_id(request)
|
|
110
|
-
if logger:
|
|
111
|
-
logger.exception("unhandled_exception", extra={"trace_id": tid, "path": request.url.path})
|
|
112
|
-
detail = str(exc) if expose_server_errors else "internal server error"
|
|
113
|
-
locale = locale = getattr(request.state, "locale", None)
|
|
114
|
-
return _json_response(request, 500, detail=_(detail, locale=locale), trace_id=tid)
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
from typing import Any, Iterable, Dict, List
|
|
2
|
-
|
|
3
|
-
from fastapi import HTTPException, status
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def _error_entry(loc: Iterable[Any] | None, detail: str, type_: str) -> dict:
|
|
7
|
-
return {
|
|
8
|
-
"loc": list(loc) if loc else [],
|
|
9
|
-
"msg": detail,
|
|
10
|
-
"type": type_,
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def _errors(loc: Iterable[Any] | None, detail: str, type_: str) -> list[dict]:
|
|
15
|
-
return [_error_entry(loc, detail, type_)]
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class BaseHttpException(HTTPException):
|
|
19
|
-
pass
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class BadRequestException(BaseHttpException):
|
|
23
|
-
def __init__(self, detail: str):
|
|
24
|
-
super().__init__(
|
|
25
|
-
status_code=status.HTTP_400_BAD_REQUEST,
|
|
26
|
-
detail=detail,
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class UnauthorizedException(BaseHttpException):
|
|
31
|
-
def __init__(self, detail: str = "unauthorized"):
|
|
32
|
-
super().__init__(
|
|
33
|
-
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
34
|
-
detail=detail,
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class ForbiddenException(BaseHttpException):
|
|
39
|
-
def __init__(self, detail: str = "forbidden"):
|
|
40
|
-
super().__init__(
|
|
41
|
-
status_code=status.HTTP_403_FORBIDDEN,
|
|
42
|
-
detail=detail,
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class NotFoundException(BaseHttpException):
|
|
47
|
-
def __init__(self, detail: str = "not found"):
|
|
48
|
-
super().__init__(
|
|
49
|
-
status_code=status.HTTP_404_NOT_FOUND,
|
|
50
|
-
detail=detail,
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
class ConflictException(BaseHttpException):
|
|
55
|
-
def __init__(self, detail: str = "conflict"):
|
|
56
|
-
super().__init__(
|
|
57
|
-
status_code=status.HTTP_409_CONFLICT,
|
|
58
|
-
detail=detail,
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
class NotSupportedException(BaseHttpException):
|
|
63
|
-
def __init__(self, detail: str = "NotSupported"):
|
|
64
|
-
super().__init__(
|
|
65
|
-
status_code=status.HTTP_501_NOT_IMPLEMENTED,
|
|
66
|
-
detail=detail,
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
class InternalServerException(BaseHttpException):
|
|
71
|
-
def __init__(self, detail: str = "internal_server_error"):
|
|
72
|
-
super().__init__(
|
|
73
|
-
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
74
|
-
detail=detail,
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
class UnprocessableEntityException(BaseHttpException):
|
|
79
|
-
def __init__(self, detail: str, loc: Iterable[Any] | None = None, type_: str = "unprocessable_entity"):
|
|
80
|
-
super().__init__(
|
|
81
|
-
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
82
|
-
detail=_errors(loc, detail, type_),
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
class LogicalValidationException(HTTPException):
|
|
87
|
-
def __init__(self, detail: str = None, loc: Iterable[Any] | None = None, type_: str = "logical_error",
|
|
88
|
-
errors: List[Dict[str, Any]] = None):
|
|
89
|
-
self.errors = errors
|
|
90
|
-
if errors:
|
|
91
|
-
super().__init__(detail=errors, status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,)
|
|
92
|
-
else:
|
|
93
|
-
super().__init__(detail=_errors(loc, detail, type_), status_code=status.HTTP_422_UNPROCESSABLE_ENTITY)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
class GoneException(BaseHttpException):
|
|
97
|
-
def __init__(self, detail: str = "Gone"):
|
|
98
|
-
super().__init__(
|
|
99
|
-
status_code=status.HTTP_410_GONE,
|
|
100
|
-
detail=detail,
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
class TooManyRequestsException(BaseHttpException):
|
|
104
|
-
def __init__(self, detail: str = "TooManyRequests"):
|
|
105
|
-
super().__init__(
|
|
106
|
-
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
|
107
|
-
detail=detail,
|
|
108
|
-
)
|
|
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.7.31 → nlbone-0.8.0}/src/nlbone/adapters/http_clients/uploadchi/uploadchi_async.py
RENAMED
|
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
|
|
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
|