nlbone 0.7.32__py3-none-any.whl → 0.8.0__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.
@@ -1,9 +1,11 @@
1
+ import logging
1
2
  from typing import Dict, Optional
2
3
 
4
+ from nlbone.adapters.i18n.loaders import BaseLoader
5
+
3
6
  from nlbone.core.ports.translation import TranslationPort
4
- from nlbone.utils.context import get_locale
5
7
 
6
- from .loaders import BaseLoader
8
+ logger = logging.getLogger(__name__)
7
9
 
8
10
 
9
11
  class I18nAdapter(TranslationPort):
@@ -17,19 +19,22 @@ class I18nAdapter(TranslationPort):
17
19
  self._translations = self.loader.load()
18
20
 
19
21
  def translate(self, key: str, locale: Optional[str] = None, **kwargs) -> str:
20
- self._ensure_loaded()
21
-
22
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
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
@@ -1,19 +1,23 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Any, Mapping, Optional
3
+ from typing import Any, Mapping, Optional, List
4
4
  from uuid import uuid4
5
5
 
6
6
  from fastapi import FastAPI, Request
7
7
  from fastapi import HTTPException as FastAPIHTTPException
8
8
  from fastapi.exceptions import RequestValidationError
9
9
  from fastapi.responses import JSONResponse
10
- from pydantic import ValidationError
10
+ from pydantic import BaseModel, ValidationError
11
11
  from starlette.exceptions import HTTPException as StarletteHTTPException
12
-
13
- from .exceptions import BaseHttpException
12
+ from starlette.status import HTTP_500_INTERNAL_SERVER_ERROR, HTTP_422_UNPROCESSABLE_ENTITY
14
13
 
15
14
  from nlbone.adapters.i18n import translator as _
16
- # ---- Helpers ---------------------------------------------------------------
15
+ from .exceptions import BaseHttpException, ErrorDetail, UnprocessableEntityException
16
+
17
+
18
+ class ErrorResponse(BaseModel):
19
+ message: str
20
+ errors: List[ErrorDetail]
17
21
 
18
22
 
19
23
  def _ensure_trace_id(request: Request) -> str:
@@ -22,93 +26,141 @@ def _ensure_trace_id(request: Request) -> str:
22
26
 
23
27
 
24
28
  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,
29
+ request: Request,
30
+ status_code: int,
31
+ content: Any,
32
+ trace_id: Optional[str] = None,
33
+ headers: Optional[Mapping[str, str]] = None,
32
34
  ) -> JSONResponse:
33
- payload: dict[str, Any] = {"detail": detail}
34
- if extra:
35
- payload.update(extra)
36
35
  tid = trace_id or _ensure_trace_id(request)
37
36
 
38
- payload.setdefault("trace_id", tid)
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
39
43
 
40
44
  base_headers = {"X-Trace-Id": tid}
41
45
  if headers:
42
46
  base_headers.update(headers)
43
47
 
44
- return JSONResponse(status_code=status_code, content=payload, headers=base_headers)
45
-
46
-
47
- # ---- Public Installer ------------------------------------------------------
48
+ return JSONResponse(
49
+ status_code=status_code,
50
+ content=payload,
51
+ headers=base_headers
52
+ )
48
53
 
49
54
 
50
55
  def install_exception_handlers(
51
- app: FastAPI,
52
- *,
53
- logger: Any = None,
54
- expose_server_errors: bool = False,
56
+ app: FastAPI,
57
+ *,
58
+ logger: Any = None,
59
+ expose_server_errors: bool = False,
55
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
+
56
79
  @app.exception_handler(BaseHttpException)
57
80
  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))
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)
75
101
 
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
102
  @app.exception_handler(RequestValidationError)
88
103
  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)
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)
96
111
 
97
112
  @app.exception_handler(ValidationError)
98
113
  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)
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)
106
141
 
107
142
  @app.exception_handler(Exception)
108
143
  async def _handle_unexpected_exception(request: Request, exc: Exception):
109
144
  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)
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
+ )
@@ -1,108 +1,153 @@
1
- from typing import Any, Iterable, Dict, List
1
+ from http import HTTPStatus
2
+ from typing import Any, Dict, List, Optional, Union
2
3
 
3
- from fastapi import HTTPException, status
4
+ from fastapi import HTTPException
5
+ from pydantic import BaseModel, Field
4
6
 
5
7
 
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
- }
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
12
13
 
13
14
 
14
- def _errors(loc: Iterable[Any] | None, detail: str, type_: str) -> list[dict]:
15
- return [_error_entry(loc, detail, type_)]
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
+ ]
16
39
 
40
+ self.errors = errors
41
+ super().__init__(status_code=status_code, detail=self.message)
17
42
 
18
- class BaseHttpException(HTTPException):
19
- pass
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
20
48
 
21
49
 
22
- class BadRequestException(BaseHttpException):
23
- def __init__(self, detail: str):
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
+
24
87
  super().__init__(
25
- status_code=status.HTTP_400_BAD_REQUEST,
88
+ status_code=HTTPStatus.UNPROCESSABLE_ENTITY,
26
89
  detail=detail,
90
+ fields=fields,
91
+ errors=errors
27
92
  )
28
93
 
29
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
+
30
100
  class UnauthorizedException(BaseHttpException):
31
- def __init__(self, detail: str = "unauthorized"):
32
- super().__init__(
33
- status_code=status.HTTP_401_UNAUTHORIZED,
34
- detail=detail,
35
- )
101
+ def __init__(self, detail: str = None):
102
+ super().__init__(status_code=HTTPStatus.UNAUTHORIZED, detail=detail)
36
103
 
37
104
 
38
105
  class ForbiddenException(BaseHttpException):
39
- def __init__(self, detail: str = "forbidden"):
40
- super().__init__(
41
- status_code=status.HTTP_403_FORBIDDEN,
42
- detail=detail,
43
- )
106
+ def __init__(self, detail: str = None):
107
+ super().__init__(status_code=HTTPStatus.FORBIDDEN, detail=detail)
44
108
 
45
109
 
46
110
  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
- )
111
+ def __init__(self, detail: str = None):
112
+ super().__init__(status_code=HTTPStatus.NOT_FOUND, detail=detail)
52
113
 
53
114
 
54
115
  class ConflictException(BaseHttpException):
55
- def __init__(self, detail: str = "conflict"):
56
- super().__init__(
57
- status_code=status.HTTP_409_CONFLICT,
58
- detail=detail,
59
- )
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)
60
118
 
61
119
 
62
120
  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
- )
121
+ def __init__(self, detail: str = None):
122
+ super().__init__(status_code=HTTPStatus.NOT_IMPLEMENTED, detail=detail)
68
123
 
69
124
 
70
125
  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
- )
126
+ def __init__(self, detail: str = None):
127
+ super().__init__(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=detail)
76
128
 
77
129
 
78
- class UnprocessableEntityException(BaseHttpException):
79
- def __init__(self, detail: str, loc: Iterable[Any] | None = None, type_: str = "unprocessable_entity"):
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):
80
134
  super().__init__(
81
- status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
82
- detail=_errors(loc, detail, type_),
135
+ detail=detail,
136
+ fields=fields,
137
+ validation_errors=validation_errors
83
138
  )
84
139
 
85
140
 
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
141
  class GoneException(BaseHttpException):
97
- def __init__(self, detail: str = "Gone"):
98
- super().__init__(
99
- status_code=status.HTTP_410_GONE,
100
- detail=detail,
101
- )
142
+ def __init__(self, detail: str = None):
143
+ super().__init__(status_code=HTTPStatus.GONE, detail=detail)
144
+
102
145
 
103
146
  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
- )
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)
@@ -0,0 +1,9 @@
1
+ def flatten_dict(d, parent_key='', sep='.'):
2
+ items = {}
3
+ for k, v in d.items():
4
+ new_key = f"{parent_key}{sep}{k}" if parent_key else k
5
+ if isinstance(v, dict):
6
+ items.update(flatten_dict(v, new_key, sep))
7
+ else:
8
+ items[new_key] = v
9
+ return items
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nlbone
3
- Version: 0.7.32
3
+ Version: 0.8.0
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
@@ -31,7 +31,7 @@ nlbone/adapters/http_clients/uploadchi/__init__.py,sha256=uBzEOuVtY22teWW2b36Pit
31
31
  nlbone/adapters/http_clients/uploadchi/uploadchi.py,sha256=erpjOees25FW0nuK1PkYS-oU0h8MeRV9Rhs1cf3gaEs,4881
32
32
  nlbone/adapters/http_clients/uploadchi/uploadchi_async.py,sha256=PQbVNeaYde5CmgT3vcnQoI1PGeSs9AxHlPFuB8biOmU,4717
33
33
  nlbone/adapters/i18n/__init__.py,sha256=fS97TR7HEc7fiDC2ufQKoFOxXDNkGA4njAFIB3EmhLk,426
34
- nlbone/adapters/i18n/engine.py,sha256=55DbbVPPHG64o640BzWDsjaxRfu6GykZzVBqME03iDE,1078
34
+ nlbone/adapters/i18n/engine.py,sha256=yH_b614oJQ2PgM8qpQ5_Prroi4Jjqb-xPguHCCJm0_0,1305
35
35
  nlbone/adapters/i18n/loaders.py,sha256=7td0Cmn0ehjDO2S2qeH31zwl8UyWI7quzedP9PnPhWk,2381
36
36
  nlbone/adapters/i18n/locales/fa-IR.json,sha256=Sc711S0_HexhSCJ6eVgr2HjoSDmSz4uODZmNn9O9Pec,88
37
37
  nlbone/adapters/messaging/__init__.py,sha256=o6ZiMihm_MhRXfcEpcjHBB3JGQovQbg3pxe0qS6516c,41
@@ -70,8 +70,8 @@ nlbone/core/ports/translation.py,sha256=pnqbxhdRCR7eprm8UI8ZKKx7VDUPntvBtlytrnTG
70
70
  nlbone/core/ports/uow.py,sha256=VhqSc-Ryt9m-rlNMiXTzD3dPGz6mM_JxND8D0UJGRu4,962
71
71
  nlbone/interfaces/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
72
72
  nlbone/interfaces/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
73
- nlbone/interfaces/api/exception_handlers.py,sha256=jH2VAgx63auFSat1H3uQ0Z9JrJ6YYN57dFuqwKzT7EU,4431
74
- nlbone/interfaces/api/exceptions.py,sha256=06S677YplJgODjVaa3fP00dPy9i-bNIlDWg1t5Q8EZc,3194
73
+ nlbone/interfaces/api/exception_handlers.py,sha256=2wJp0HZcsSNVsmjFXma3FQ994iga7m3zm9o1E4MIncw,5582
74
+ nlbone/interfaces/api/exceptions.py,sha256=IggZxV9q6l4jqw-G7SWEmuyXnWgbNXJJT-rmnirRIK4,5057
75
75
  nlbone/interfaces/api/routers.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
76
76
  nlbone/interfaces/api/schemas.py,sha256=34Tz2EeXyf12rFL9iyYWaB2ftuaXUebQQQxSO9ouV94,133
77
77
  nlbone/interfaces/api/additional_filed/__init__.py,sha256=BWemliLSQV9iq1vdUaF733q0FOSipSWBOQk9eYj732Q,318
@@ -109,13 +109,14 @@ nlbone/utils/cache_keys.py,sha256=Y2YSellHTbUOcoaNbl1jaD4r485VU_e4KXsfBWhYTBo,10
109
109
  nlbone/utils/cache_registry.py,sha256=3FWYyhujW8oPBiVUPzk1CqJ3jJfxs9729Sbb1pQ5Fag,707
110
110
  nlbone/utils/context.py,sha256=Wq3QLYsMzo_xUiVAHLgEPQUG6LhgJTmFn8MO5Qa7S8w,1837
111
111
  nlbone/utils/crypto.py,sha256=PX0Tlf2nqXcGbuv16J26MoUPzo2c4xcD4sZBXxhBXgQ,746
112
+ nlbone/utils/flatten_dict.py,sha256=nkx8gAcJkjCKG3I5y9clqtjGMZCWyUWRhgD2R8zGEqM,299
112
113
  nlbone/utils/flatten_sqlalchemy_result.py,sha256=JGwQEauoJVjhzvXrOtqabj3wQ7zp2-OPwhuh44sUdUE,625
113
114
  nlbone/utils/http.py,sha256=0yeI34j5FfelqvX3PJnKknSXji1jl15VYbVIIvrSbXg,997
114
115
  nlbone/utils/normalize_mobile.py,sha256=sGH4tV9gX-6eVKozviNWJhm1DN1J28Nj-ERldCYkS_E,732
115
116
  nlbone/utils/redactor.py,sha256=-V4HrHmHwPi3Kez587Ek1uJlgK35qGSrwBOvcbw8Jas,1279
116
117
  nlbone/utils/time.py,sha256=DjjyQ9GLsfXoT6NK8RDW2rOlJg3e6sF04Jw6PBUrSvg,1268
117
- nlbone-0.7.32.dist-info/METADATA,sha256=m6ntviUXuOCKo5ZV_f08VeicMw2HvO3PG8sNw2qUEyU,2295
118
- nlbone-0.7.32.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
119
- nlbone-0.7.32.dist-info/entry_points.txt,sha256=CpIL45t5nbhl1dGQPhfIIDfqqak3teK0SxPGBBr7YCk,59
120
- nlbone-0.7.32.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
121
- nlbone-0.7.32.dist-info/RECORD,,
118
+ nlbone-0.8.0.dist-info/METADATA,sha256=Joe3SppJPlbtJ5q5vdB1jSKCTAgOodLaLdkI4BWg7K4,2294
119
+ nlbone-0.8.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
120
+ nlbone-0.8.0.dist-info/entry_points.txt,sha256=CpIL45t5nbhl1dGQPhfIIDfqqak3teK0SxPGBBr7YCk,59
121
+ nlbone-0.8.0.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
122
+ nlbone-0.8.0.dist-info/RECORD,,