nlbone 0.7.2__py3-none-any.whl → 0.7.3__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.
- nlbone/adapters/auth/auth_service.py +25 -0
- nlbone/adapters/db/postgres/types.py +21 -0
- nlbone/adapters/snowflake.py +70 -0
- nlbone/config/settings.py +5 -0
- nlbone/core/domain/base.py +37 -1
- nlbone/interfaces/api/middleware/authentication.py +44 -23
- {nlbone-0.7.2.dist-info → nlbone-0.7.3.dist-info}/METADATA +1 -1
- {nlbone-0.7.2.dist-info → nlbone-0.7.3.dist-info}/RECORD +11 -8
- {nlbone-0.7.2.dist-info → nlbone-0.7.3.dist-info}/WHEEL +0 -0
- {nlbone-0.7.2.dist-info → nlbone-0.7.3.dist-info}/entry_points.txt +0 -0
- {nlbone-0.7.2.dist-info → nlbone-0.7.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
|
|
3
|
+
from nlbone.config.settings import get_settings
|
|
4
|
+
from nlbone.core.ports.auth import AuthService as BaseAuthService
|
|
5
|
+
from nlbone.utils.http import normalize_https_base
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AuthService(BaseAuthService):
|
|
9
|
+
def __init__(self):
|
|
10
|
+
s = get_settings()
|
|
11
|
+
self._base_url = normalize_https_base(s.AUTH_SERVICE_URL.unicode_string())
|
|
12
|
+
self._timeout = float(s.HTTP_TIMEOUT_SECONDS)
|
|
13
|
+
self._client = requests.session()
|
|
14
|
+
|
|
15
|
+
def has_access(self, token: str, permissions: list[str]) -> bool: ...
|
|
16
|
+
def verify_token(self, token: str) -> dict | None:
|
|
17
|
+
url = f"{self._base_url}/introspect"
|
|
18
|
+
result = self._client.post(url, data={
|
|
19
|
+
"token": token
|
|
20
|
+
})
|
|
21
|
+
return result.json()
|
|
22
|
+
def get_client_token(self) -> dict | None: ...
|
|
23
|
+
def is_client_token(self, token: str, allowed_clients: set[str] | None = None) -> bool: ...
|
|
24
|
+
def client_has_access(self, token: str, perms: list[str], allowed_clients: set[str] | None = None) -> bool: ...
|
|
25
|
+
def get_permissions(self, token: str) -> list[str]: ...
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from sqlalchemy.types import TypeDecorator, BigInteger
|
|
2
|
+
|
|
3
|
+
class BaseIntegerIdType(TypeDecorator):
|
|
4
|
+
"""Maps BaseId subclasses <-> BIGINT transparently (Infrastructure-only)."""
|
|
5
|
+
impl = BigInteger
|
|
6
|
+
cache_ok = True
|
|
7
|
+
|
|
8
|
+
def __init__(self, id_cls):
|
|
9
|
+
super().__init__()
|
|
10
|
+
self.id_cls = id_cls
|
|
11
|
+
|
|
12
|
+
def process_bind_param(self, value, dialect):
|
|
13
|
+
# Python -> DB
|
|
14
|
+
if value is None:
|
|
15
|
+
return None
|
|
16
|
+
return int(value)
|
|
17
|
+
|
|
18
|
+
def process_result_value(self, value, dialect):
|
|
19
|
+
if value is None:
|
|
20
|
+
return None
|
|
21
|
+
return self.id_cls(int(value))
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
import time
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
|
|
5
|
+
from nlbone.config.settings import get_settings
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Snowflake:
|
|
9
|
+
WORKER_ID_BITS = 5
|
|
10
|
+
DATACENTER_ID_BITS = 5
|
|
11
|
+
SEQUENCE_BITS = 12
|
|
12
|
+
|
|
13
|
+
MAX_WORKER_ID = (1 << WORKER_ID_BITS) - 1 # 31
|
|
14
|
+
MAX_DATACENTER_ID = (1 << DATACENTER_ID_BITS) - 1 # 31
|
|
15
|
+
SEQUENCE_MASK = (1 << SEQUENCE_BITS) - 1 # 4095
|
|
16
|
+
|
|
17
|
+
WORKER_ID_SHIFT = SEQUENCE_BITS
|
|
18
|
+
DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS
|
|
19
|
+
TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS
|
|
20
|
+
|
|
21
|
+
EPOCH = int(datetime(2020, 1, 1, tzinfo=timezone.utc).timestamp() * 1000)
|
|
22
|
+
|
|
23
|
+
def __init__(self, datacenter_id: int, worker_id: int):
|
|
24
|
+
if not (0 <= worker_id <= self.MAX_WORKER_ID):
|
|
25
|
+
raise ValueError(f"worker_id must be 0..{self.MAX_WORKER_ID}")
|
|
26
|
+
if not (0 <= datacenter_id <= self.MAX_DATACENTER_ID):
|
|
27
|
+
raise ValueError(f"datacenter_id must be 0..{self.MAX_DATACENTER_ID}")
|
|
28
|
+
|
|
29
|
+
self.datacenter_id = datacenter_id
|
|
30
|
+
self.worker_id = worker_id
|
|
31
|
+
self.sequence = 0
|
|
32
|
+
self.last_ts = -1
|
|
33
|
+
self._lock = threading.Lock()
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def _timestamp_ms() -> int:
|
|
37
|
+
return int(time.time() * 1000)
|
|
38
|
+
|
|
39
|
+
def _wait_next_ms(self, last_ts: int) -> int:
|
|
40
|
+
ts = self._timestamp_ms()
|
|
41
|
+
while ts <= last_ts:
|
|
42
|
+
ts = self._timestamp_ms()
|
|
43
|
+
return ts
|
|
44
|
+
|
|
45
|
+
def next_id(self) -> int:
|
|
46
|
+
with self._lock:
|
|
47
|
+
ts = self._timestamp_ms()
|
|
48
|
+
|
|
49
|
+
if ts < self.last_ts:
|
|
50
|
+
ts = self._wait_next_ms(self.last_ts)
|
|
51
|
+
|
|
52
|
+
if ts == self.last_ts:
|
|
53
|
+
self.sequence = (self.sequence + 1) & self.SEQUENCE_MASK
|
|
54
|
+
if self.sequence == 0:
|
|
55
|
+
ts = self._wait_next_ms(self.last_ts)
|
|
56
|
+
else:
|
|
57
|
+
self.sequence = 0
|
|
58
|
+
|
|
59
|
+
self.last_ts = ts
|
|
60
|
+
|
|
61
|
+
id64 = ((ts - self.EPOCH) << self.TIMESTAMP_SHIFT) | \
|
|
62
|
+
(self.datacenter_id << self.DATACENTER_ID_SHIFT) | \
|
|
63
|
+
(self.worker_id << self.WORKER_ID_SHIFT) | \
|
|
64
|
+
self.sequence
|
|
65
|
+
return id64
|
|
66
|
+
|
|
67
|
+
setting = get_settings()
|
|
68
|
+
_DC_ID = int(setting.SNOWFLAKE_DATACENTER_ID)
|
|
69
|
+
_WORKER_ID = int(setting.SNOWFLAKE_WORKER_ID)
|
|
70
|
+
SNOWFLAKE = Snowflake(datacenter_id=_DC_ID, worker_id=_WORKER_ID)
|
nlbone/config/settings.py
CHANGED
|
@@ -43,6 +43,9 @@ class Settings(BaseSettings):
|
|
|
43
43
|
LOG_LEVEL: Literal["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"] = Field(default="INFO")
|
|
44
44
|
LOG_JSON: bool = Field(default=True)
|
|
45
45
|
|
|
46
|
+
SNOWFLAKE_WORKER_ID : int = Field(default=1)
|
|
47
|
+
SNOWFLAKE_DATACENTER_ID: int = Field(default=1)
|
|
48
|
+
|
|
46
49
|
AUDIT_DEFAULT_ENABLE: bool = False
|
|
47
50
|
|
|
48
51
|
# ---------------------------
|
|
@@ -58,6 +61,8 @@ class Settings(BaseSettings):
|
|
|
58
61
|
KEYCLOAK_CLIENT_ID: str = Field(default="nlbone")
|
|
59
62
|
KEYCLOAK_CLIENT_SECRET: SecretStr = Field(default=SecretStr("dev-secret"))
|
|
60
63
|
|
|
64
|
+
AUTH_SERVICE_URL: AnyHttpUrl = Field(default="https://auth.numberland.ir")
|
|
65
|
+
|
|
61
66
|
# ---------------------------
|
|
62
67
|
# Database
|
|
63
68
|
# ---------------------------
|
nlbone/core/domain/base.py
CHANGED
|
@@ -3,11 +3,13 @@ from __future__ import annotations
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from datetime import datetime, timezone
|
|
5
5
|
from enum import Enum
|
|
6
|
-
from typing import Any, Generic, List, TypeVar
|
|
6
|
+
from typing import Any, Generic, List, TypeVar, Callable, ClassVar
|
|
7
7
|
from uuid import uuid4
|
|
8
8
|
|
|
9
9
|
from pydantic import BaseModel
|
|
10
10
|
|
|
11
|
+
from nlbone.adapters.snowflake import SNOWFLAKE
|
|
12
|
+
|
|
11
13
|
TId = TypeVar("TId")
|
|
12
14
|
|
|
13
15
|
|
|
@@ -70,3 +72,37 @@ class AggregateRoot(Entity[TId]):
|
|
|
70
72
|
|
|
71
73
|
class BaseEnum(Enum):
|
|
72
74
|
pass
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
ID = TypeVar("ID", bound="BaseId")
|
|
79
|
+
|
|
80
|
+
@dataclass(frozen=True)
|
|
81
|
+
class BaseId(ValueObject):
|
|
82
|
+
value: int
|
|
83
|
+
|
|
84
|
+
_generator: ClassVar[Callable[[], int]] = lambda: SNOWFLAKE.next_id()
|
|
85
|
+
|
|
86
|
+
def __post_init__(self):
|
|
87
|
+
if not isinstance(self.value, int):
|
|
88
|
+
raise TypeError("BaseId.value must be int")
|
|
89
|
+
if self.value <= 0:
|
|
90
|
+
raise ValueError("BaseId must be a positive integer")
|
|
91
|
+
|
|
92
|
+
def __int__(self) -> int:
|
|
93
|
+
return self.value
|
|
94
|
+
|
|
95
|
+
def __str__(self) -> str:
|
|
96
|
+
return str(self.value)
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def set_generator(cls, gen: Callable[[], int]) -> None:
|
|
100
|
+
cls._generator = gen
|
|
101
|
+
|
|
102
|
+
@classmethod
|
|
103
|
+
def new(cls: type[ID]) -> ID:
|
|
104
|
+
return cls(cls._generator())
|
|
105
|
+
|
|
106
|
+
@classmethod
|
|
107
|
+
def from_int(cls: type[ID], v: int) -> ID:
|
|
108
|
+
return cls(v)
|
|
@@ -3,6 +3,8 @@ from typing import Callable, Optional, Union
|
|
|
3
3
|
from fastapi import Request
|
|
4
4
|
from starlette.middleware.base import BaseHTTPMiddleware
|
|
5
5
|
|
|
6
|
+
from nlbone.adapters.auth.auth_service import AuthService
|
|
7
|
+
|
|
6
8
|
try:
|
|
7
9
|
from dependency_injector import providers
|
|
8
10
|
|
|
@@ -10,10 +12,10 @@ try:
|
|
|
10
12
|
except Exception:
|
|
11
13
|
ProviderType = object
|
|
12
14
|
|
|
13
|
-
from nlbone.core.ports.auth import AuthService
|
|
15
|
+
from nlbone.core.ports.auth import AuthService as BaseAuthService
|
|
14
16
|
|
|
15
17
|
|
|
16
|
-
def _to_factory(auth: Union[
|
|
18
|
+
def _to_factory(auth: Union[BaseAuthService, Callable[[], BaseAuthService], ProviderType]):
|
|
17
19
|
try:
|
|
18
20
|
from dependency_injector import providers as _p # type: ignore
|
|
19
21
|
|
|
@@ -26,8 +28,43 @@ def _to_factory(auth: Union[AuthService, Callable[[], AuthService], ProviderType
|
|
|
26
28
|
return lambda: auth
|
|
27
29
|
|
|
28
30
|
|
|
31
|
+
def authenticate_admin_user(request, auth_service):
|
|
32
|
+
token: Optional[str] = None
|
|
33
|
+
authz = request.headers.get("Authorization")
|
|
34
|
+
if authz:
|
|
35
|
+
try:
|
|
36
|
+
scheme, token = authz.split(" ", 1)
|
|
37
|
+
if scheme.lower() != "bearer":
|
|
38
|
+
token = None
|
|
39
|
+
except ValueError:
|
|
40
|
+
token = None
|
|
41
|
+
|
|
42
|
+
if token:
|
|
43
|
+
request.state.token = token
|
|
44
|
+
try:
|
|
45
|
+
service: BaseAuthService = auth_service()
|
|
46
|
+
data = service.verify_token(token)
|
|
47
|
+
if data:
|
|
48
|
+
request.state.user_id = data.get("user_id")
|
|
49
|
+
except Exception:
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
def authenticate_user(request):
|
|
53
|
+
token = request.cookies.get("access_token") or request.cookies.get("j_token")
|
|
54
|
+
|
|
55
|
+
if token:
|
|
56
|
+
request.state.token = token
|
|
57
|
+
try:
|
|
58
|
+
service: BaseAuthService = AuthService()
|
|
59
|
+
data = service.verify_token(token)
|
|
60
|
+
if data:
|
|
61
|
+
request.state.user_id = data.get("sub")
|
|
62
|
+
except Exception:
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
|
|
29
66
|
class AuthenticationMiddleware(BaseHTTPMiddleware):
|
|
30
|
-
def __init__(self, app, auth: Union[
|
|
67
|
+
def __init__(self, app, auth: Union[BaseAuthService, Callable[[], BaseAuthService], ProviderType]):
|
|
31
68
|
super().__init__(app)
|
|
32
69
|
self._get_auth = _to_factory(auth)
|
|
33
70
|
|
|
@@ -35,25 +72,9 @@ class AuthenticationMiddleware(BaseHTTPMiddleware):
|
|
|
35
72
|
request.state.client_id = request.headers.get("X-Client-Id")
|
|
36
73
|
request.state.user_id = None
|
|
37
74
|
request.state.token = None
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
try:
|
|
43
|
-
scheme, token = authz.split(" ", 1)
|
|
44
|
-
if scheme.lower() != "bearer":
|
|
45
|
-
token = None
|
|
46
|
-
except ValueError:
|
|
47
|
-
token = None
|
|
48
|
-
|
|
49
|
-
if token:
|
|
50
|
-
request.state.token = token
|
|
51
|
-
try:
|
|
52
|
-
service: AuthService = self._get_auth()
|
|
53
|
-
data = service.verify_token(token)
|
|
54
|
-
if data:
|
|
55
|
-
request.state.user_id = data.get("user_id")
|
|
56
|
-
except Exception:
|
|
57
|
-
pass
|
|
75
|
+
if request.cookies.get("access_token"):
|
|
76
|
+
authenticate_user(request)
|
|
77
|
+
elif request.headers.get("Authorization"):
|
|
78
|
+
authenticate_admin_user(request, auth_service=self._get_auth)
|
|
58
79
|
|
|
59
80
|
return await call_next(request)
|
|
@@ -2,7 +2,9 @@ nlbone/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
2
2
|
nlbone/container.py,sha256=I3Ev5L-CvfOquR4cyueIiplRriZ-_q_QlH_Xne9lA0k,2698
|
|
3
3
|
nlbone/types.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
nlbone/adapters/__init__.py,sha256=NzUmk4XPyp3GJOw7VSE86xkQMZLtG3MrOoXLeoB551M,41
|
|
5
|
+
nlbone/adapters/snowflake.py,sha256=lmq7vi6HdX9hEuTW6BlTEAy91wmrA4Bx3tvGoagHTW4,2315
|
|
5
6
|
nlbone/adapters/auth/__init__.py,sha256=hkDHvsFhw_UiOHG9ZSMqjiAhK4wumEforitveSZswVw,42
|
|
7
|
+
nlbone/adapters/auth/auth_service.py,sha256=HoRaEOcab5DFmHi4yAudtwJDhaofRyqMVvnq65a3sqg,1062
|
|
6
8
|
nlbone/adapters/auth/keycloak.py,sha256=IhEriaFl5mjIGT6ZUCU9qROd678ARchvWgd4UJ6zH7s,4925
|
|
7
9
|
nlbone/adapters/auth/token_provider.py,sha256=vL2Hk6HXnBbpk40Tq1wpqak5QQ7KEQf3nRquT0N8V4Q,1433
|
|
8
10
|
nlbone/adapters/cache/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -18,6 +20,7 @@ nlbone/adapters/db/postgres/engine.py,sha256=UCegauVB1gvo42ThytYnn5VIcQBwR-5xhcX
|
|
|
18
20
|
nlbone/adapters/db/postgres/query_builder.py,sha256=Qv_2oZ5OxZwtN3Ts-jaAX_8sLBzb1mpGBhlNF7aR6Wk,12543
|
|
19
21
|
nlbone/adapters/db/postgres/repository.py,sha256=n01TAzdKd-UbOhirE6KMosuvRdJG2l1cszwVHjTM-Ks,10345
|
|
20
22
|
nlbone/adapters/db/postgres/schema.py,sha256=NlE7Rr8uXypsw4oWkdZhZwcIBHQEPIpoHLxcUo98i6s,1039
|
|
23
|
+
nlbone/adapters/db/postgres/types.py,sha256=OXra45PL8Umrqj6Pul_8ZlYRGIWQE36jvBXxui_u6hs,600
|
|
21
24
|
nlbone/adapters/db/postgres/uow.py,sha256=2vRp4RBkh9RVniEY6CMXNHt-XXK9W2CDNKR1upT5EJE,3788
|
|
22
25
|
nlbone/adapters/db/redis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
26
|
nlbone/adapters/db/redis/client.py,sha256=5SUnwP2-GrueSFimUbiqDvrQsumvIE2aeozk8l-vOfQ,466
|
|
@@ -40,7 +43,7 @@ nlbone/adapters/ticketing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
|
|
|
40
43
|
nlbone/adapters/ticketing/client.py,sha256=V5_u7cxV67eAG4jj4vUD23VWGiMXIXSAU362pNS6hYU,1289
|
|
41
44
|
nlbone/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
42
45
|
nlbone/config/logging.py,sha256=Ot6Ctf7EQZlW8YNB-uBdleqI6wixn5fH0Eo6QRgNkQk,4358
|
|
43
|
-
nlbone/config/settings.py,sha256=
|
|
46
|
+
nlbone/config/settings.py,sha256=o5kJBHuv-XKFOe_yQtkR0YrEbI2uZ89JpGjzDYhE7o4,4507
|
|
44
47
|
nlbone/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
45
48
|
nlbone/core/application/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
49
|
nlbone/core/application/base_worker.py,sha256=5brIToSd-vi6tw0ukhHnUZGZhOLq1SQ-NRRy-kp6D24,1193
|
|
@@ -50,7 +53,7 @@ nlbone/core/application/registry.py,sha256=dTqV_4bkMsLJ60CesZuEel5xO36cD1qikiaCL
|
|
|
50
53
|
nlbone/core/application/use_case.py,sha256=3GMQZ3CFK5cbLoBNBgohPft6GBq2j9_wr8iKRq_osQA,247
|
|
51
54
|
nlbone/core/application/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
52
55
|
nlbone/core/domain/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
53
|
-
nlbone/core/domain/base.py,sha256=
|
|
56
|
+
nlbone/core/domain/base.py,sha256=EpjTDjSFV3or26_4qWh96JQ2hEIyStymhp16L9cuxJ4,2309
|
|
54
57
|
nlbone/core/domain/models.py,sha256=VEIfzruBXcF5XrDrWEvh9q-Ba9-9x_JG20wZsBF5thc,3270
|
|
55
58
|
nlbone/core/ports/__init__.py,sha256=syJg3fAjQALD5Rjfm9wi9bQpkIvNTWjE9AURBmy587o,132
|
|
56
59
|
nlbone/core/ports/auth.py,sha256=C-GmUqHNx4bAku6KbW_OTpPXCEfurBWWyDi9KxpTi9M,553
|
|
@@ -80,7 +83,7 @@ nlbone/interfaces/api/dependencies/uow.py,sha256=QfLEvLYLNWZJQN1k-0q0hBVtUld3D75
|
|
|
80
83
|
nlbone/interfaces/api/middleware/__init__.py,sha256=zbX2vaEAfxRMIYwO2MVY_2O6bqG5H9o7HqGpX14U3Is,158
|
|
81
84
|
nlbone/interfaces/api/middleware/access_log.py,sha256=vIkxxxfy2HcjqqKb8XCfGCcSrivAC8u6ie75FMq5x-U,1032
|
|
82
85
|
nlbone/interfaces/api/middleware/add_request_context.py,sha256=av-qs0biOYuF9R6RJOo2eYsFqDL9WRYWcjVakFhbt-w,1834
|
|
83
|
-
nlbone/interfaces/api/middleware/authentication.py,sha256=
|
|
86
|
+
nlbone/interfaces/api/middleware/authentication.py,sha256=NmCkN_PXBIMJ8tgKJU4C4JHq_9Ohe_-fwgiuv5uWlIk,2497
|
|
84
87
|
nlbone/interfaces/api/pagination/__init__.py,sha256=pA1uC4rK6eqDI5IkLVxmgO2B6lExnOm8Pje2-hifJZw,431
|
|
85
88
|
nlbone/interfaces/api/pagination/offset_base.py,sha256=65UgQZ70IlWUbP9Usd--IxVYW_V0hQLoyXHVffnLFZ0,3940
|
|
86
89
|
nlbone/interfaces/api/schema/__init__.py,sha256=LAqgynfupeqOQ6u0I5ucrcYnojRMZUg9yW8IjKSQTNI,119
|
|
@@ -104,8 +107,8 @@ nlbone/utils/http.py,sha256=UXUoXgQdTRNT08ho8zl-C5ekfDsD8uf-JiMQ323ooqw,872
|
|
|
104
107
|
nlbone/utils/normalize_mobile.py,sha256=sGH4tV9gX-6eVKozviNWJhm1DN1J28Nj-ERldCYkS_E,732
|
|
105
108
|
nlbone/utils/redactor.py,sha256=-V4HrHmHwPi3Kez587Ek1uJlgK35qGSrwBOvcbw8Jas,1279
|
|
106
109
|
nlbone/utils/time.py,sha256=DjjyQ9GLsfXoT6NK8RDW2rOlJg3e6sF04Jw6PBUrSvg,1268
|
|
107
|
-
nlbone-0.7.
|
|
108
|
-
nlbone-0.7.
|
|
109
|
-
nlbone-0.7.
|
|
110
|
-
nlbone-0.7.
|
|
111
|
-
nlbone-0.7.
|
|
110
|
+
nlbone-0.7.3.dist-info/METADATA,sha256=oE6yopvG_cQ_3S7k4a2WqDnaMnDmH6ZiY4oYUiRzCRA,2294
|
|
111
|
+
nlbone-0.7.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
112
|
+
nlbone-0.7.3.dist-info/entry_points.txt,sha256=CpIL45t5nbhl1dGQPhfIIDfqqak3teK0SxPGBBr7YCk,59
|
|
113
|
+
nlbone-0.7.3.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
114
|
+
nlbone-0.7.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|