nlbone 0.5.0__py3-none-any.whl → 0.6.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.
- nlbone/adapters/http_clients/__init__.py +2 -0
- nlbone/adapters/http_clients/pricing/__init__.py +1 -0
- nlbone/adapters/http_clients/pricing/pricing_service.py +96 -0
- nlbone/adapters/http_clients/uploadchi/__init__.py +2 -0
- nlbone/adapters/http_clients/{uploadchi.py → uploadchi/uploadchi.py} +10 -34
- nlbone/adapters/http_clients/{uploadchi_async.py → uploadchi/uploadchi_async.py} +11 -10
- nlbone/config/settings.py +20 -12
- nlbone/container.py +3 -1
- nlbone/utils/http.py +29 -0
- {nlbone-0.5.0.dist-info → nlbone-0.6.0.dist-info}/METADATA +1 -1
- {nlbone-0.5.0.dist-info → nlbone-0.6.0.dist-info}/RECORD +14 -11
- nlbone/adapters/http_clients/email_gateway.py +0 -0
- {nlbone-0.5.0.dist-info → nlbone-0.6.0.dist-info}/WHEEL +0 -0
- {nlbone-0.5.0.dist-info → nlbone-0.6.0.dist-info}/entry_points.txt +0 -0
- {nlbone-0.5.0.dist-info → nlbone-0.6.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .pricing_service import PricingService, CalculatePriceIn, CalculatePriceOut
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Optional, Literal, List
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
import requests
|
|
6
|
+
from pydantic import BaseModel, Field, NonNegativeInt, RootModel
|
|
7
|
+
|
|
8
|
+
from nlbone.adapters.auth.token_provider import ClientTokenProvider
|
|
9
|
+
from nlbone.config.settings import get_settings
|
|
10
|
+
from nlbone.utils.http import normalize_https_base, auth_headers
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PricingError(Exception):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CalculatePriceIn(BaseModel):
|
|
18
|
+
params: dict[str, str]
|
|
19
|
+
product_id: NonNegativeInt | None = None
|
|
20
|
+
product_title: str | None = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class DiscountType(str, Enum):
|
|
24
|
+
percent = "percent"
|
|
25
|
+
amount = "amount"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Product(BaseModel):
|
|
29
|
+
id: Optional[int] = Field(None, description="Nullable product id")
|
|
30
|
+
service_product_id: NonNegativeInt
|
|
31
|
+
title: Optional[str] = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Pricing(BaseModel):
|
|
35
|
+
source: Literal["formula", "static"]
|
|
36
|
+
price: float
|
|
37
|
+
discount: Optional[float] = None
|
|
38
|
+
discount_type: Optional[DiscountType] = None
|
|
39
|
+
params: dict
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Formula(BaseModel):
|
|
43
|
+
id: int
|
|
44
|
+
title: str
|
|
45
|
+
key: str
|
|
46
|
+
status: str
|
|
47
|
+
description: str
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class PricingRule(BaseModel):
|
|
51
|
+
product: Product
|
|
52
|
+
segment_name: str | None
|
|
53
|
+
formula: Optional[Formula] = None
|
|
54
|
+
specificity: int
|
|
55
|
+
matched_fields: list
|
|
56
|
+
pricing: Pricing
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class CalculatePriceOut(RootModel[List[PricingRule]]):
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class PricingService:
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
token_provider: ClientTokenProvider,
|
|
67
|
+
base_url: Optional[str] = None,
|
|
68
|
+
timeout_seconds: Optional[float] = None,
|
|
69
|
+
client: httpx.Client | None = None,
|
|
70
|
+
) -> None:
|
|
71
|
+
s = get_settings()
|
|
72
|
+
self._base_url = normalize_https_base(base_url or str(s.PRICING_SERVICE_URL), enforce_https=False)
|
|
73
|
+
self._timeout = timeout_seconds or float(s.HTTP_TIMEOUT_SECONDS)
|
|
74
|
+
self._client = client or requests.session()
|
|
75
|
+
self._token_provider = token_provider
|
|
76
|
+
|
|
77
|
+
def calculate(self, items: list[CalculatePriceIn]) -> CalculatePriceOut:
|
|
78
|
+
body = {
|
|
79
|
+
"items": [i.model_dump() for i in items]
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
r = self._client.post(
|
|
83
|
+
f"{self._base_url}/priced",
|
|
84
|
+
headers=auth_headers(self._token_provider.get_access_token()),
|
|
85
|
+
json=body,
|
|
86
|
+
timeout=self._timeout,
|
|
87
|
+
verify=False
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if r.status_code not in (200, 204):
|
|
91
|
+
raise PricingError(r.status_code, r.text)
|
|
92
|
+
|
|
93
|
+
if r.status_code == 204 or not r.content:
|
|
94
|
+
return CalculatePriceOut.model_validate(root=[])
|
|
95
|
+
|
|
96
|
+
return CalculatePriceOut.model_validate(r.json())
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
3
|
from typing import Any, Optional
|
|
5
|
-
from urllib.parse import urlparse, urlunparse
|
|
6
4
|
|
|
7
5
|
import httpx
|
|
8
6
|
import requests
|
|
@@ -10,6 +8,7 @@ import requests
|
|
|
10
8
|
from nlbone.adapters.auth.token_provider import ClientTokenProvider
|
|
11
9
|
from nlbone.config.settings import get_settings
|
|
12
10
|
from nlbone.core.ports.files import FileServicePort
|
|
11
|
+
from nlbone.utils.http import auth_headers, build_list_query, normalize_https_base
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
class UploadchiError(RuntimeError):
|
|
@@ -26,21 +25,6 @@ def _resolve_token(explicit: str | None) -> str | None:
|
|
|
26
25
|
return s.UPLOADCHI_TOKEN.get_secret_value() if s.UPLOADCHI_TOKEN else None
|
|
27
26
|
|
|
28
27
|
|
|
29
|
-
def _auth_headers(token: str | None) -> dict[str, str]:
|
|
30
|
-
return {"Authorization": f"Bearer {token}"} if token else {}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def _build_list_query(
|
|
34
|
-
limit: int, offset: int, filters: dict[str, Any] | None, sort: list[tuple[str, str]] | None
|
|
35
|
-
) -> dict[str, Any]:
|
|
36
|
-
q: dict[str, Any] = {"limit": limit, "offset": offset}
|
|
37
|
-
if filters:
|
|
38
|
-
q["filters"] = json.dumps(filters)
|
|
39
|
-
if sort:
|
|
40
|
-
q["sort"] = ",".join([f"{f}:{o}" for f, o in sort])
|
|
41
|
-
return q
|
|
42
|
-
|
|
43
|
-
|
|
44
28
|
def _filename_from_cd(cd: str | None, fallback: str) -> str:
|
|
45
29
|
if not cd:
|
|
46
30
|
return fallback
|
|
@@ -49,14 +33,6 @@ def _filename_from_cd(cd: str | None, fallback: str) -> str:
|
|
|
49
33
|
return fallback
|
|
50
34
|
|
|
51
35
|
|
|
52
|
-
def _normalize_https_base(url: str) -> str:
|
|
53
|
-
p = urlparse(url.strip())
|
|
54
|
-
p = p._replace(scheme="https") # enforce https
|
|
55
|
-
if p.path.endswith("/"):
|
|
56
|
-
p = p._replace(path=p.path.rstrip("/"))
|
|
57
|
-
return str(urlunparse(p))
|
|
58
|
-
|
|
59
|
-
|
|
60
36
|
class UploadchiClient(FileServicePort):
|
|
61
37
|
def __init__(
|
|
62
38
|
self,
|
|
@@ -66,7 +42,7 @@ class UploadchiClient(FileServicePort):
|
|
|
66
42
|
client: httpx.Client | None = None,
|
|
67
43
|
) -> None:
|
|
68
44
|
s = get_settings()
|
|
69
|
-
self._base_url =
|
|
45
|
+
self._base_url = normalize_https_base(base_url or str(s.UPLOADCHI_BASE_URL))
|
|
70
46
|
self._timeout = timeout_seconds or float(s.HTTP_TIMEOUT_SECONDS)
|
|
71
47
|
self._client = client or requests.session()
|
|
72
48
|
self._token_provider = token_provider
|
|
@@ -80,7 +56,7 @@ class UploadchiClient(FileServicePort):
|
|
|
80
56
|
tok = _resolve_token(token)
|
|
81
57
|
files = {"file": (filename, file_bytes)}
|
|
82
58
|
data = (params or {}).copy()
|
|
83
|
-
r = self._client.post(self._base_url, files=files, data=data, headers=
|
|
59
|
+
r = self._client.post(self._base_url, files=files, data=data, headers=auth_headers(tok))
|
|
84
60
|
if r.status_code >= 400:
|
|
85
61
|
raise UploadchiError(r.status_code, r.text)
|
|
86
62
|
return r.json()
|
|
@@ -91,7 +67,7 @@ class UploadchiClient(FileServicePort):
|
|
|
91
67
|
tok = _resolve_token(token)
|
|
92
68
|
r = self._client.post(
|
|
93
69
|
f"{self._base_url}/{file_id}/commit",
|
|
94
|
-
headers=
|
|
70
|
+
headers=auth_headers(tok or self._token_provider.get_access_token()),
|
|
95
71
|
)
|
|
96
72
|
if r.status_code not in (204, 200):
|
|
97
73
|
raise UploadchiError(r.status_code, r.text)
|
|
@@ -102,7 +78,7 @@ class UploadchiClient(FileServicePort):
|
|
|
102
78
|
tok = _resolve_token(token)
|
|
103
79
|
r = self._client.post(
|
|
104
80
|
f"{self._base_url}/{file_id}/rollback",
|
|
105
|
-
headers=
|
|
81
|
+
headers=auth_headers(tok or self._token_provider.get_access_token()),
|
|
106
82
|
)
|
|
107
83
|
if r.status_code not in (204, 200):
|
|
108
84
|
raise UploadchiError(r.status_code, r.text)
|
|
@@ -116,22 +92,22 @@ class UploadchiClient(FileServicePort):
|
|
|
116
92
|
token: str | None = None,
|
|
117
93
|
) -> dict:
|
|
118
94
|
tok = _resolve_token(token)
|
|
119
|
-
q =
|
|
120
|
-
r = self._client.get(self._base_url, params=q, headers=
|
|
95
|
+
q = build_list_query(limit, offset, filters, sort)
|
|
96
|
+
r = self._client.get(self._base_url, params=q, headers=auth_headers(tok))
|
|
121
97
|
if r.status_code >= 400:
|
|
122
98
|
raise UploadchiError(r.status_code, r.text)
|
|
123
99
|
return r.json()
|
|
124
100
|
|
|
125
101
|
def get_file(self, file_id: str, token: str | None = None) -> dict:
|
|
126
102
|
tok = _resolve_token(token)
|
|
127
|
-
r = self._client.get(f"{self._base_url}/{file_id}", headers=
|
|
103
|
+
r = self._client.get(f"{self._base_url}/{file_id}", headers=auth_headers(tok))
|
|
128
104
|
if r.status_code >= 400:
|
|
129
105
|
raise UploadchiError(r.status_code, r.text)
|
|
130
106
|
return r.json()
|
|
131
107
|
|
|
132
108
|
def download_file(self, file_id: str, token: str | None = None) -> tuple[bytes, str, str]:
|
|
133
109
|
tok = _resolve_token(token)
|
|
134
|
-
r = self._client.get(f"{self._base_url}/{file_id}/download", headers=
|
|
110
|
+
r = self._client.get(f"{self._base_url}/{file_id}/download", headers=auth_headers(tok))
|
|
135
111
|
if r.status_code >= 400:
|
|
136
112
|
raise UploadchiError(r.status_code, r.text)
|
|
137
113
|
filename = _filename_from_cd(r.headers.get("content-disposition"), fallback=f"file-{file_id}")
|
|
@@ -140,6 +116,6 @@ class UploadchiClient(FileServicePort):
|
|
|
140
116
|
|
|
141
117
|
def delete_file(self, file_id: str, token: str | None = None) -> None:
|
|
142
118
|
tok = _resolve_token(token)
|
|
143
|
-
r = self._client.delete(f"{self._base_url}/{file_id}", headers=
|
|
119
|
+
r = self._client.delete(f"{self._base_url}/{file_id}", headers=auth_headers(tok))
|
|
144
120
|
if r.status_code not in (204, 200):
|
|
145
121
|
raise UploadchiError(r.status_code, r.text)
|
|
@@ -7,8 +7,9 @@ import httpx
|
|
|
7
7
|
from nlbone.config.settings import get_settings
|
|
8
8
|
from nlbone.core.ports.files import AsyncFileServicePort
|
|
9
9
|
|
|
10
|
-
from .uploadchi import UploadchiError,
|
|
11
|
-
from
|
|
10
|
+
from nlbone.adapters.http_clients.uploadchi.uploadchi import UploadchiError, _filename_from_cd, _resolve_token
|
|
11
|
+
from nlbone.adapters.auth.token_provider import ClientTokenProvider
|
|
12
|
+
from nlbone.utils.http import auth_headers, build_list_query
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
class UploadchiAsyncClient(AsyncFileServicePort):
|
|
@@ -36,7 +37,7 @@ class UploadchiAsyncClient(AsyncFileServicePort):
|
|
|
36
37
|
tok = _resolve_token(token)
|
|
37
38
|
files = {"file": (filename, file_bytes)}
|
|
38
39
|
data = (params or {}).copy()
|
|
39
|
-
r = await self._client.post("", files=files, data=data, headers=
|
|
40
|
+
r = await self._client.post("", files=files, data=data, headers=auth_headers(tok))
|
|
40
41
|
if r.status_code >= 400:
|
|
41
42
|
raise UploadchiError(r.status_code, await r.aread())
|
|
42
43
|
return r.json()
|
|
@@ -46,7 +47,7 @@ class UploadchiAsyncClient(AsyncFileServicePort):
|
|
|
46
47
|
raise UploadchiError(detail="token_provider is not provided", status=400)
|
|
47
48
|
tok = _resolve_token(token)
|
|
48
49
|
r = await self._client.post(
|
|
49
|
-
f"/{file_id}/commit", headers=
|
|
50
|
+
f"/{file_id}/commit", headers=auth_headers(tok or self._token_provider.get_access_token())
|
|
50
51
|
)
|
|
51
52
|
if r.status_code not in (204, 200):
|
|
52
53
|
raise UploadchiError(r.status_code, await r.aread())
|
|
@@ -56,7 +57,7 @@ class UploadchiAsyncClient(AsyncFileServicePort):
|
|
|
56
57
|
raise UploadchiError(detail="token_provider is not provided", status=400)
|
|
57
58
|
tok = _resolve_token(token)
|
|
58
59
|
r = await self._client.post(
|
|
59
|
-
f"/{file_id}/rollback", headers=
|
|
60
|
+
f"/{file_id}/rollback", headers=auth_headers(tok or self._token_provider.get_access_token())
|
|
60
61
|
)
|
|
61
62
|
if r.status_code not in (204, 200):
|
|
62
63
|
raise UploadchiError(r.status_code, await r.aread())
|
|
@@ -70,22 +71,22 @@ class UploadchiAsyncClient(AsyncFileServicePort):
|
|
|
70
71
|
token: str | None = None,
|
|
71
72
|
) -> dict:
|
|
72
73
|
tok = _resolve_token(token)
|
|
73
|
-
q =
|
|
74
|
-
r = await self._client.get("", params=q, headers=
|
|
74
|
+
q = build_list_query(limit, offset, filters, sort)
|
|
75
|
+
r = await self._client.get("", params=q, headers=auth_headers(tok))
|
|
75
76
|
if r.status_code >= 400:
|
|
76
77
|
raise UploadchiError(r.status_code, await r.aread())
|
|
77
78
|
return r.json()
|
|
78
79
|
|
|
79
80
|
async def get_file(self, file_id: str, token: str | None = None) -> dict:
|
|
80
81
|
tok = _resolve_token(token)
|
|
81
|
-
r = await self._client.get(f"/{file_id}", headers=
|
|
82
|
+
r = await self._client.get(f"/{file_id}", headers=auth_headers(tok))
|
|
82
83
|
if r.status_code >= 400:
|
|
83
84
|
raise UploadchiError(r.status_code, await r.aread())
|
|
84
85
|
return r.json()
|
|
85
86
|
|
|
86
87
|
async def download_file(self, file_id: str, token: str | None = None) -> tuple[AsyncIterator[bytes], str, str]:
|
|
87
88
|
tok = _resolve_token(token)
|
|
88
|
-
r = await self._client.get(f"/{file_id}/download", headers=
|
|
89
|
+
r = await self._client.get(f"/{file_id}/download", headers=auth_headers(tok), stream=True)
|
|
89
90
|
if r.status_code >= 400:
|
|
90
91
|
body = await r.aread()
|
|
91
92
|
raise UploadchiError(r.status_code, body.decode(errors="ignore"))
|
|
@@ -103,7 +104,7 @@ class UploadchiAsyncClient(AsyncFileServicePort):
|
|
|
103
104
|
|
|
104
105
|
async def delete_file(self, file_id: str, token: str | None = None) -> None:
|
|
105
106
|
tok = _resolve_token(token)
|
|
106
|
-
r = await self._client.delete(f"/{file_id}", headers=
|
|
107
|
+
r = await self._client.delete(f"/{file_id}", headers=auth_headers(tok))
|
|
107
108
|
if r.status_code not in (204, 200):
|
|
108
109
|
body = await r.aread()
|
|
109
110
|
raise UploadchiError(r.status_code, body.decode(errors="ignore"))
|
nlbone/config/settings.py
CHANGED
|
@@ -8,19 +8,22 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def _guess_env_file() -> str | None:
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
try:
|
|
12
|
+
explicit = os.getenv("NLBONE_ENV_FILE")
|
|
13
|
+
if explicit:
|
|
14
|
+
return explicit
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
cwd_env = Path.cwd() / ".env"
|
|
17
|
+
if cwd_env.exists():
|
|
18
|
+
return str(cwd_env)
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
for i in range(0, 8):
|
|
21
|
+
p = Path.cwd().resolve().parents[i]
|
|
22
|
+
f = p / ".env"
|
|
23
|
+
if f.exists():
|
|
24
|
+
return str(f)
|
|
25
|
+
except Exception as e:
|
|
26
|
+
raise Exception("Failed to guess env file path!") from e
|
|
24
27
|
|
|
25
28
|
|
|
26
29
|
def is_production_env() -> bool:
|
|
@@ -82,10 +85,15 @@ class Settings(BaseSettings):
|
|
|
82
85
|
# ---------------------------
|
|
83
86
|
# PERCOLATE
|
|
84
87
|
# ---------------------------
|
|
85
|
-
ELASTIC_PERCOLATE_URL: str =
|
|
88
|
+
ELASTIC_PERCOLATE_URL: str = Field(default="http://localhost:9200")
|
|
86
89
|
ELASTIC_PERCOLATE_USER: str = Field(default="")
|
|
87
90
|
ELASTIC_PERCOLATE_PASS: SecretStr = Field(default="")
|
|
88
91
|
|
|
92
|
+
# ---------------------------
|
|
93
|
+
# Pricing
|
|
94
|
+
# ---------------------------
|
|
95
|
+
PRICING_SERVICE_URL: AnyHttpUrl = Field(default="https://pricing.numberland.ir/v1")
|
|
96
|
+
|
|
89
97
|
model_config = SettingsConfigDict(
|
|
90
98
|
env_prefix="",
|
|
91
99
|
env_file=None,
|
nlbone/container.py
CHANGED
|
@@ -11,8 +11,9 @@ from nlbone.adapters.cache.memory import InMemoryCache
|
|
|
11
11
|
from nlbone.adapters.cache.redis import RedisCache
|
|
12
12
|
from nlbone.adapters.db.postgres import AsyncSqlAlchemyUnitOfWork, SqlAlchemyUnitOfWork
|
|
13
13
|
from nlbone.adapters.db.postgres.engine import get_async_session_factory, get_sync_session_factory
|
|
14
|
+
from nlbone.adapters.http_clients import PricingService
|
|
14
15
|
from nlbone.adapters.http_clients.uploadchi import UploadchiClient
|
|
15
|
-
from nlbone.adapters.http_clients.uploadchi_async import UploadchiAsyncClient
|
|
16
|
+
from nlbone.adapters.http_clients.uploadchi.uploadchi_async import UploadchiAsyncClient
|
|
16
17
|
from nlbone.adapters.messaging import InMemoryEventBus
|
|
17
18
|
from nlbone.core.ports import EventBusPort
|
|
18
19
|
from nlbone.core.ports.cache import CachePort, AsyncCachePort
|
|
@@ -39,6 +40,7 @@ class Container(containers.DeclarativeContainer):
|
|
|
39
40
|
token_provider=token_provider)
|
|
40
41
|
afiles_service: providers.Singleton[AsyncFileServicePort] = providers.Singleton(UploadchiAsyncClient,
|
|
41
42
|
token_provider=token_provider)
|
|
43
|
+
pricing_service: providers.Singleton[PricingService] = providers.Singleton(PricingService, token_provider=token_provider)
|
|
42
44
|
|
|
43
45
|
cache: providers.Singleton[CachePort] = providers.Selector(
|
|
44
46
|
config.CACHE_BACKEND,
|
nlbone/utils/http.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any
|
|
5
|
+
from urllib.parse import urlparse, urlunparse
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def auth_headers(token: str | None) -> dict[str, str]:
|
|
9
|
+
return {"Authorization": f"Bearer {token}"} if token else {}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def build_list_query(
|
|
13
|
+
limit: int, offset: int, filters: dict[str, Any] | None, sort: list[tuple[str, str]] | None
|
|
14
|
+
) -> dict[str, Any]:
|
|
15
|
+
q: dict[str, Any] = {"limit": limit, "offset": offset}
|
|
16
|
+
if filters:
|
|
17
|
+
q["filters"] = json.dumps(filters)
|
|
18
|
+
if sort:
|
|
19
|
+
q["sort"] = ",".join([f"{f}:{o}" for f, o in sort])
|
|
20
|
+
return q
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def normalize_https_base(url: str, enforce_https: bool = True) -> str:
|
|
24
|
+
p = urlparse(url.strip())
|
|
25
|
+
if enforce_https:
|
|
26
|
+
p = p._replace(scheme="https") # enforce https
|
|
27
|
+
if p.path.endswith("/"):
|
|
28
|
+
p = p._replace(path=p.path.rstrip("/"))
|
|
29
|
+
return str(urlunparse(p))
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
nlbone/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
nlbone/container.py,sha256=
|
|
2
|
+
nlbone/container.py,sha256=hOkc8F6Zx4fP5HxRp_uBYQyxhi6QYRAIqUPFEYd18aQ,3361
|
|
3
3
|
nlbone/types.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
nlbone/adapters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
5
|
nlbone/adapters/auth/__init__.py,sha256=hkDHvsFhw_UiOHG9ZSMqjiAhK4wumEforitveSZswVw,42
|
|
@@ -21,10 +21,12 @@ nlbone/adapters/db/postgres/schema.py,sha256=NlE7Rr8uXypsw4oWkdZhZwcIBHQEPIpoHLx
|
|
|
21
21
|
nlbone/adapters/db/postgres/uow.py,sha256=nRxNpY-WoWHpym-XeZ8VHm0MYvtB9wuopOeNdV_ebk8,2088
|
|
22
22
|
nlbone/adapters/db/redis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
23
|
nlbone/adapters/db/redis/client.py,sha256=XAKcmU0lpPvWPMS0fChVQ3iSJfHV1g4bMOCgJaj2bCI,512
|
|
24
|
-
nlbone/adapters/http_clients/__init__.py,sha256=
|
|
25
|
-
nlbone/adapters/http_clients/
|
|
26
|
-
nlbone/adapters/http_clients/
|
|
27
|
-
nlbone/adapters/http_clients/
|
|
24
|
+
nlbone/adapters/http_clients/__init__.py,sha256=Ed75OHxkJ401XNHpfx3pz20e7Z_YFSAoKUct8o-SI9k,150
|
|
25
|
+
nlbone/adapters/http_clients/pricing/__init__.py,sha256=RLSnG9mgaw1Qwt5Qsc4SwRCjNiqWeyDpvFY79PlhkFE,81
|
|
26
|
+
nlbone/adapters/http_clients/pricing/pricing_service.py,sha256=cdYEF_ZY01QbhSXoifotELdp2n8GyzdKIC_nWHP696E,2568
|
|
27
|
+
nlbone/adapters/http_clients/uploadchi/__init__.py,sha256=VjLtgOPvNufoQvNCrwPLwlzJicdfJCZUqBK4VEXNQ3o,104
|
|
28
|
+
nlbone/adapters/http_clients/uploadchi/uploadchi.py,sha256=_D6Gv5nj4TJXcEq_YJWut92vEu69LflLTw6m2dlD7C0,4864
|
|
29
|
+
nlbone/adapters/http_clients/uploadchi/uploadchi_async.py,sha256=EGFI1ZhfMxotHkORyU1cJVGG4tsFAbnITZViIc3J7vI,4766
|
|
28
30
|
nlbone/adapters/messaging/__init__.py,sha256=UDAwu3s-JQmOZjWz2Nu0SgHhnkbeOhKDH_zLD75oWMY,40
|
|
29
31
|
nlbone/adapters/messaging/event_bus.py,sha256=w-NPwDiPMLFPU_enRQCtfQXOALsXfg31u57R8sG_-1U,781
|
|
30
32
|
nlbone/adapters/messaging/redis.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -32,7 +34,7 @@ nlbone/adapters/percolation/__init__.py,sha256=viq5WZqcSLlRBF5JwuyTD_IZaNWfpKzGJ
|
|
|
32
34
|
nlbone/adapters/percolation/connection.py,sha256=xZ-OtQVbyQYH83TUizS0UWI85Iic-AhUjiuyzO0e46s,331
|
|
33
35
|
nlbone/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
36
|
nlbone/config/logging.py,sha256=rGQz9W5ZgUFXBK74TFmTuwx_WMJhD8zPN39zfKVxwnI,4115
|
|
35
|
-
nlbone/config/settings.py,sha256
|
|
37
|
+
nlbone/config/settings.py,sha256=W3NHZP6yjIyyKiGWNkjlUt_RYFKkcIfMBoKih_z_0Bs,3911
|
|
36
38
|
nlbone/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
39
|
nlbone/core/application/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
40
|
nlbone/core/application/base_worker.py,sha256=uHqglsd33jXl_0kmkFlB4KQ5NdI1wArcOeQmdcifPQc,1192
|
|
@@ -79,10 +81,11 @@ nlbone/utils/cache.py,sha256=3sKtWoGBlrEvkg74r4C-Sx8u41OTup_G4s-Q57IaOXg,5921
|
|
|
79
81
|
nlbone/utils/cache_keys.py,sha256=a1yRMUuyRJ2-CswjtgVkLcJAeT2QmThLQ5kQWvpOKL4,1069
|
|
80
82
|
nlbone/utils/cache_registry.py,sha256=0csax1-GmKBcsZmQYWI4Bs0X9_BMo6Jdoac-e9Zusv8,819
|
|
81
83
|
nlbone/utils/context.py,sha256=MmclJ24BG2uvSTg1IK7J-Da9BhVFDQ5ag4Ggs2FF1_w,1600
|
|
84
|
+
nlbone/utils/http.py,sha256=krNfMoD9kTFE_E5yX-0DzgH97H-UgAiKRMXmATlwgIM,876
|
|
82
85
|
nlbone/utils/redactor.py,sha256=JbbPs2Qtnz0zHN85BGPYQNWwBigXMSzmMEmmZZOTs_U,1277
|
|
83
86
|
nlbone/utils/time.py,sha256=6e0A4_hG1rYDCrWoOklEGVJstBf8j9XSSTT7VNV2K9Y,1272
|
|
84
|
-
nlbone-0.
|
|
85
|
-
nlbone-0.
|
|
86
|
-
nlbone-0.
|
|
87
|
-
nlbone-0.
|
|
88
|
-
nlbone-0.
|
|
87
|
+
nlbone-0.6.0.dist-info/METADATA,sha256=402hd-duWpvMKlnSwcgbbYLJQ0OQFgwKtWd9zlfkdB8,2194
|
|
88
|
+
nlbone-0.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
89
|
+
nlbone-0.6.0.dist-info/entry_points.txt,sha256=CpIL45t5nbhl1dGQPhfIIDfqqak3teK0SxPGBBr7YCk,59
|
|
90
|
+
nlbone-0.6.0.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
91
|
+
nlbone-0.6.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|