python-mytnb 0.2.0__tar.gz → 0.3.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.
- {python_mytnb-0.2.0 → python_mytnb-0.3.0}/PKG-INFO +7 -7
- {python_mytnb-0.2.0 → python_mytnb-0.3.0}/README.md +5 -5
- {python_mytnb-0.2.0 → python_mytnb-0.3.0}/pyproject.toml +2 -2
- {python_mytnb-0.2.0 → python_mytnb-0.3.0}/src/mytnb/__init__.py +4 -0
- {python_mytnb-0.2.0 → python_mytnb-0.3.0}/src/mytnb/client/client.py +7 -7
- {python_mytnb-0.2.0 → python_mytnb-0.3.0}/src/mytnb/client/legacy.py +4 -4
- {python_mytnb-0.2.0 → python_mytnb-0.3.0}/src/mytnb/models.py +36 -0
- {python_mytnb-0.2.0 → python_mytnb-0.3.0}/tests/test_client.py +1 -1
- {python_mytnb-0.2.0 → python_mytnb-0.3.0}/tests/test_models.py +90 -0
- {python_mytnb-0.2.0 → python_mytnb-0.3.0}/uv.lock +36 -12
- {python_mytnb-0.2.0 → python_mytnb-0.3.0}/.github/workflows/ci.yml +0 -0
- {python_mytnb-0.2.0 → python_mytnb-0.3.0}/.github/workflows/publish.yml +0 -0
- {python_mytnb-0.2.0 → python_mytnb-0.3.0}/.gitignore +0 -0
- {python_mytnb-0.2.0 → python_mytnb-0.3.0}/LICENSE +0 -0
- {python_mytnb-0.2.0 → python_mytnb-0.3.0}/src/mytnb/__main__.py +0 -0
- {python_mytnb-0.2.0 → python_mytnb-0.3.0}/src/mytnb/auth.py +0 -0
- {python_mytnb-0.2.0 → python_mytnb-0.3.0}/src/mytnb/cli.py +0 -0
- {python_mytnb-0.2.0 → python_mytnb-0.3.0}/src/mytnb/client/__init__.py +0 -0
- {python_mytnb-0.2.0 → python_mytnb-0.3.0}/src/mytnb/client/auth.py +0 -0
- {python_mytnb-0.2.0 → python_mytnb-0.3.0}/src/mytnb/client/config.py +0 -0
- {python_mytnb-0.2.0 → python_mytnb-0.3.0}/src/mytnb/client/rest.py +0 -0
- {python_mytnb-0.2.0 → python_mytnb-0.3.0}/src/mytnb/crypto.py +0 -0
- {python_mytnb-0.2.0 → python_mytnb-0.3.0}/src/mytnb/exceptions.py +0 -0
- {python_mytnb-0.2.0 → python_mytnb-0.3.0}/tests/test_auth.py +0 -0
- {python_mytnb-0.2.0 → python_mytnb-0.3.0}/tests/test_cli.py +0 -0
- {python_mytnb-0.2.0 → python_mytnb-0.3.0}/tests/test_crypto.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-mytnb
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Python library to interface with the myTNB API (Tenaga Nasional Berhad)
|
|
5
5
|
Project-URL: Repository, https://github.com/danieyal/python-mytnb
|
|
6
6
|
License-Expression: MIT
|
|
@@ -18,10 +18,10 @@ Classifier: Topic :: Software Development :: Libraries
|
|
|
18
18
|
Requires-Python: >=3.10
|
|
19
19
|
Requires-Dist: click>=8.3.3
|
|
20
20
|
Requires-Dist: cryptography>=42.0
|
|
21
|
+
Requires-Dist: curl-cffi>=0.7
|
|
21
22
|
Requires-Dist: httpx>=0.27
|
|
22
23
|
Requires-Dist: pydantic>=2.0
|
|
23
24
|
Requires-Dist: rich>=15.0.0
|
|
24
|
-
Requires-Dist: tls-client>=1.0
|
|
25
25
|
Provides-Extra: dev
|
|
26
26
|
Requires-Dist: pylint>=3.0; extra == 'dev'
|
|
27
27
|
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
|
|
@@ -119,11 +119,11 @@ mytnb init-config # Generate a starter config file
|
|
|
119
119
|
|
|
120
120
|
myTNB uses two API backends, both handled transparently by this library:
|
|
121
121
|
|
|
122
|
-
| Backend | Domain
|
|
123
|
-
| ----------- |
|
|
124
|
-
| REST | `api.mytnb.com.my`
|
|
125
|
-
| AWS Gateway | `api.mytnb.com.my/core/api
|
|
126
|
-
| Legacy ASMX | `mytnbapp.tnb.com.my`
|
|
122
|
+
| Backend | Domain | Auth | Used for |
|
|
123
|
+
| ----------- | --------------------------- | ------------------------------------------- | ----------------------------------- |
|
|
124
|
+
| REST | `api.mytnb.com.my` | JWT + API key | Bill eligibility, eligibility icons |
|
|
125
|
+
| AWS Gateway | `api.mytnb.com.my/core/api` | Encrypted payloads (AES-256-CBC + RSA-OAEP) | Account listing (auto-discovery) |
|
|
126
|
+
| Legacy ASMX | `mytnbapp.tnb.com.my` | Encrypted payloads (AES-256-CBC + RSA-OAEP) | Usage data, billing, services |
|
|
127
127
|
|
|
128
128
|
Request encryption for the ASMX API is automatic — just pass plaintext parameters.
|
|
129
129
|
|
|
@@ -88,11 +88,11 @@ mytnb init-config # Generate a starter config file
|
|
|
88
88
|
|
|
89
89
|
myTNB uses two API backends, both handled transparently by this library:
|
|
90
90
|
|
|
91
|
-
| Backend | Domain
|
|
92
|
-
| ----------- |
|
|
93
|
-
| REST | `api.mytnb.com.my`
|
|
94
|
-
| AWS Gateway | `api.mytnb.com.my/core/api
|
|
95
|
-
| Legacy ASMX | `mytnbapp.tnb.com.my`
|
|
91
|
+
| Backend | Domain | Auth | Used for |
|
|
92
|
+
| ----------- | --------------------------- | ------------------------------------------- | ----------------------------------- |
|
|
93
|
+
| REST | `api.mytnb.com.my` | JWT + API key | Bill eligibility, eligibility icons |
|
|
94
|
+
| AWS Gateway | `api.mytnb.com.my/core/api` | Encrypted payloads (AES-256-CBC + RSA-OAEP) | Account listing (auto-discovery) |
|
|
95
|
+
| Legacy ASMX | `mytnbapp.tnb.com.my` | Encrypted payloads (AES-256-CBC + RSA-OAEP) | Usage data, billing, services |
|
|
96
96
|
|
|
97
97
|
Request encryption for the ASMX API is automatic — just pass plaintext parameters.
|
|
98
98
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-mytnb"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Python library to interface with the myTNB API (Tenaga Nasional Berhad)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -25,7 +25,7 @@ dependencies = [
|
|
|
25
25
|
"httpx>=0.27",
|
|
26
26
|
"pydantic>=2.0",
|
|
27
27
|
"cryptography>=42.0",
|
|
28
|
-
"
|
|
28
|
+
"curl_cffi>=0.7",
|
|
29
29
|
"click>=8.3.3",
|
|
30
30
|
"rich>=15.0.0",
|
|
31
31
|
]
|
|
@@ -10,6 +10,8 @@ from mytnb.models import (
|
|
|
10
10
|
DailyUsage,
|
|
11
11
|
Metric,
|
|
12
12
|
TariffBlock,
|
|
13
|
+
TariffBlockLegendGroup,
|
|
14
|
+
TariffBlockLegendItem,
|
|
13
15
|
UsageMetric,
|
|
14
16
|
)
|
|
15
17
|
|
|
@@ -24,5 +26,7 @@ __all__ = [
|
|
|
24
26
|
"DailyUsage",
|
|
25
27
|
"Metric",
|
|
26
28
|
"TariffBlock",
|
|
29
|
+
"TariffBlockLegendGroup",
|
|
30
|
+
"TariffBlockLegendItem",
|
|
27
31
|
"UsageMetric",
|
|
28
32
|
]
|
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
from typing import Any, Optional
|
|
6
6
|
|
|
7
7
|
import httpx
|
|
8
|
-
import
|
|
8
|
+
from curl_cffi import requests as curl_requests
|
|
9
9
|
|
|
10
10
|
from mytnb.auth import Credentials
|
|
11
11
|
from mytnb.client.auth import login
|
|
@@ -40,7 +40,7 @@ class MyTNBClient:
|
|
|
40
40
|
self._timeout = timeout
|
|
41
41
|
self._use_staging_key = use_staging_key
|
|
42
42
|
self._http_client: Optional[httpx.AsyncClient] = None
|
|
43
|
-
self._tls_session: Optional[
|
|
43
|
+
self._tls_session: Optional[curl_requests.Session] = None
|
|
44
44
|
self._rest: Optional[_RestTransport] = None
|
|
45
45
|
self._legacy: Optional[_LegacyTransport] = None
|
|
46
46
|
|
|
@@ -59,14 +59,14 @@ class MyTNBClient:
|
|
|
59
59
|
return self._http_client
|
|
60
60
|
|
|
61
61
|
@property
|
|
62
|
-
def _legacy_client(self) ->
|
|
63
|
-
"""Get or create a
|
|
62
|
+
def _legacy_client(self) -> curl_requests.Session:
|
|
63
|
+
"""Get or create a curl_cffi session for legacy ASMX requests.
|
|
64
64
|
|
|
65
|
-
Uses
|
|
65
|
+
Uses a TLS fingerprint to bypass CloudFront WAF.
|
|
66
66
|
"""
|
|
67
67
|
if self._tls_session is None:
|
|
68
|
-
self._tls_session =
|
|
69
|
-
|
|
68
|
+
self._tls_session = curl_requests.Session(
|
|
69
|
+
impersonate="chrome131",
|
|
70
70
|
)
|
|
71
71
|
return self._tls_session
|
|
72
72
|
|
|
@@ -7,7 +7,7 @@ import json
|
|
|
7
7
|
import logging
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
|
-
import
|
|
10
|
+
from curl_cffi import requests as curl_requests
|
|
11
11
|
|
|
12
12
|
from mytnb.auth import Credentials
|
|
13
13
|
from mytnb.client.config import (
|
|
@@ -30,7 +30,7 @@ class _LegacyTransport:
|
|
|
30
30
|
|
|
31
31
|
def __init__(
|
|
32
32
|
self,
|
|
33
|
-
session:
|
|
33
|
+
session: curl_requests.Session,
|
|
34
34
|
credentials: Credentials,
|
|
35
35
|
timeout: float,
|
|
36
36
|
use_staging_key: bool,
|
|
@@ -85,7 +85,7 @@ class _LegacyTransport:
|
|
|
85
85
|
"""Make a POST request to the legacy ASMX API.
|
|
86
86
|
|
|
87
87
|
Automatically encrypts the data using AES-256-CBC + RSA-OAEP.
|
|
88
|
-
Uses
|
|
88
|
+
Uses curl_cffi with a TLS fingerprint to bypass CloudFront WAF.
|
|
89
89
|
"""
|
|
90
90
|
url = f"{LEGACY_BASE_URL}/{endpoint}"
|
|
91
91
|
req_headers = self.headers()
|
|
@@ -99,7 +99,7 @@ class _LegacyTransport:
|
|
|
99
99
|
url,
|
|
100
100
|
headers=req_headers,
|
|
101
101
|
json=body,
|
|
102
|
-
|
|
102
|
+
timeout=int(self._timeout),
|
|
103
103
|
)
|
|
104
104
|
logger.debug("Legacy POST %s → %s", endpoint, response.status_code)
|
|
105
105
|
|
|
@@ -150,6 +150,26 @@ class ByMonthData(BaseModel):
|
|
|
150
150
|
model_config = {"populate_by_name": True}
|
|
151
151
|
|
|
152
152
|
|
|
153
|
+
class TariffBlockLegendItem(BaseModel):
|
|
154
|
+
"""A single tariff rate block in the legend (e.g. BLK1: RM 0.218/kWh)."""
|
|
155
|
+
|
|
156
|
+
block_id: str = Field(alias="BlockId")
|
|
157
|
+
block_range: str = Field(default="", alias="BlockRange")
|
|
158
|
+
block_price: str = Field(default="", alias="BlockPrice")
|
|
159
|
+
|
|
160
|
+
model_config = {"populate_by_name": True, "extra": "ignore"}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class TariffBlockLegendGroup(BaseModel):
|
|
164
|
+
"""A monthly group of RP4 tariff legend items."""
|
|
165
|
+
|
|
166
|
+
items: list[TariffBlockLegendItem] = Field(
|
|
167
|
+
default_factory=list, alias="TariffBlocksLegend"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
model_config = {"populate_by_name": True, "extra": "ignore"}
|
|
171
|
+
|
|
172
|
+
|
|
153
173
|
class AccountUsage(BaseModel):
|
|
154
174
|
"""Full account usage response from GetAccountUsageSmart."""
|
|
155
175
|
|
|
@@ -161,6 +181,8 @@ class AccountUsage(BaseModel):
|
|
|
161
181
|
start_date: Optional[str] = None
|
|
162
182
|
end_date: Optional[str] = None
|
|
163
183
|
date_range: Optional[str] = None
|
|
184
|
+
tariff_blocks_legend: list[TariffBlockLegendItem] = Field(default_factory=list)
|
|
185
|
+
tariff_blocks_legend_rp4: list[TariffBlockLegendGroup] = Field(default_factory=list)
|
|
164
186
|
|
|
165
187
|
@classmethod
|
|
166
188
|
def from_api_response(cls, data: dict) -> "AccountUsage":
|
|
@@ -178,6 +200,18 @@ class AccountUsage(BaseModel):
|
|
|
178
200
|
by_day_raw = data.get("ByDay", [])
|
|
179
201
|
by_day = [DailyUsageWeek.model_validate(w) for w in by_day_raw]
|
|
180
202
|
|
|
203
|
+
# Tariff block legends (residential)
|
|
204
|
+
legend_raw = data.get("TariffBlocksLegend", [])
|
|
205
|
+
tariff_blocks_legend = [
|
|
206
|
+
TariffBlockLegendItem.model_validate(item) for item in legend_raw
|
|
207
|
+
]
|
|
208
|
+
|
|
209
|
+
# Tariff block legends by month (RP4 commercial)
|
|
210
|
+
rp4_raw = data.get("TariffBlocksLegendByMonthListRP4", [])
|
|
211
|
+
tariff_blocks_legend_rp4 = [
|
|
212
|
+
TariffBlockLegendGroup.model_validate(group) for group in rp4_raw
|
|
213
|
+
]
|
|
214
|
+
|
|
181
215
|
return cls(
|
|
182
216
|
usage_metrics=usage_metrics,
|
|
183
217
|
cost_metrics=cost_metrics,
|
|
@@ -187,6 +221,8 @@ class AccountUsage(BaseModel):
|
|
|
187
221
|
start_date=data.get("StartDate"),
|
|
188
222
|
end_date=data.get("EndDate"),
|
|
189
223
|
date_range=data.get("DateRange"),
|
|
224
|
+
tariff_blocks_legend=tariff_blocks_legend,
|
|
225
|
+
tariff_blocks_legend_rp4=tariff_blocks_legend_rp4,
|
|
190
226
|
)
|
|
191
227
|
|
|
192
228
|
@property
|
|
@@ -38,7 +38,7 @@ def _mock_response(data: dict, status_code: int = 200) -> httpx.Response:
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
def _mock_tls_response(data: dict, status_code: int = 200) -> MagicMock:
|
|
41
|
-
"""Create a mock
|
|
41
|
+
"""Create a mock curl_cffi response."""
|
|
42
42
|
resp = MagicMock()
|
|
43
43
|
resp.status_code = status_code
|
|
44
44
|
resp.json.return_value = data
|
|
@@ -12,6 +12,8 @@ from mytnb.models import (
|
|
|
12
12
|
MonthlyTariffBlock,
|
|
13
13
|
SMRAccount,
|
|
14
14
|
TariffBlock,
|
|
15
|
+
TariffBlockLegendGroup,
|
|
16
|
+
TariffBlockLegendItem,
|
|
15
17
|
)
|
|
16
18
|
|
|
17
19
|
# ── Sample API response data ─────────────────────────────────────────────
|
|
@@ -115,6 +117,36 @@ FULL_API_RESPONSE = {
|
|
|
115
117
|
"StartDate": "2026-05-01",
|
|
116
118
|
"EndDate": "2026-05-15",
|
|
117
119
|
"DateRange": "1 May - 15 May 2026",
|
|
120
|
+
"TariffBlocksLegend": [
|
|
121
|
+
{
|
|
122
|
+
"BlockId": "BLK1",
|
|
123
|
+
"RGB": {"R": 102, "G": 196, "B": 183},
|
|
124
|
+
"BlockRange": "1 - 200 kWh",
|
|
125
|
+
"BlockPrice": "RM 0.218 / kWh",
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"BlockId": "BLK2",
|
|
129
|
+
"RGB": {"R": 158, "G": 214, "B": 182},
|
|
130
|
+
"BlockRange": "201 - 300 kWh",
|
|
131
|
+
"BlockPrice": "RM 0.334 / kWh",
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
"TariffBlocksLegendByMonthListRP4": [
|
|
135
|
+
{
|
|
136
|
+
"TariffBlocksLegend": [
|
|
137
|
+
{
|
|
138
|
+
"BlockId": "EnergyCharge",
|
|
139
|
+
"BlockRange": "0",
|
|
140
|
+
"BlockPrice": "RM 0.2703",
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"BlockId": "CustomerCharge",
|
|
144
|
+
"BlockRange": "0",
|
|
145
|
+
"BlockPrice": "RM 10.00",
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
},
|
|
149
|
+
],
|
|
118
150
|
}
|
|
119
151
|
|
|
120
152
|
|
|
@@ -227,6 +259,20 @@ class TestAccountUsage:
|
|
|
227
259
|
assert len(usage.by_day) == 1
|
|
228
260
|
assert len(usage.by_day[0].days) == 1
|
|
229
261
|
|
|
262
|
+
def test_tariff_blocks_legend(self):
|
|
263
|
+
usage = AccountUsage.from_api_response(FULL_API_RESPONSE)
|
|
264
|
+
assert len(usage.tariff_blocks_legend) == 2
|
|
265
|
+
assert usage.tariff_blocks_legend[0].block_id == "BLK1"
|
|
266
|
+
assert usage.tariff_blocks_legend[0].block_range == "1 - 200 kWh"
|
|
267
|
+
assert usage.tariff_blocks_legend[0].block_price == "RM 0.218 / kWh"
|
|
268
|
+
|
|
269
|
+
def test_tariff_blocks_legend_rp4(self):
|
|
270
|
+
usage = AccountUsage.from_api_response(FULL_API_RESPONSE)
|
|
271
|
+
assert len(usage.tariff_blocks_legend_rp4) == 1
|
|
272
|
+
assert len(usage.tariff_blocks_legend_rp4[0].items) == 2
|
|
273
|
+
assert usage.tariff_blocks_legend_rp4[0].items[0].block_id == "EnergyCharge"
|
|
274
|
+
assert usage.tariff_blocks_legend_rp4[0].items[0].block_price == "RM 0.2703"
|
|
275
|
+
|
|
230
276
|
def test_empty_response(self):
|
|
231
277
|
usage = AccountUsage.from_api_response({})
|
|
232
278
|
assert usage.current_usage_kwh is None
|
|
@@ -421,3 +467,47 @@ class TestCustomerAccount:
|
|
|
421
467
|
assert acc.account_desc == ""
|
|
422
468
|
assert acc.owner_name == ""
|
|
423
469
|
assert acc.account_st_address == ""
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
class TestTariffBlockLegendItem:
|
|
473
|
+
"""Tests for TariffBlockLegendItem (residential tariff rate block)."""
|
|
474
|
+
|
|
475
|
+
def test_parse(self):
|
|
476
|
+
item = TariffBlockLegendItem.model_validate({
|
|
477
|
+
"BlockId": "BLK1",
|
|
478
|
+
"RGB": {"R": 102, "G": 196, "B": 183},
|
|
479
|
+
"BlockRange": "1 - 200 kWh",
|
|
480
|
+
"BlockPrice": "RM 0.218 / kWh",
|
|
481
|
+
})
|
|
482
|
+
assert item.block_id == "BLK1"
|
|
483
|
+
assert item.block_range == "1 - 200 kWh"
|
|
484
|
+
assert item.block_price == "RM 0.218 / kWh"
|
|
485
|
+
|
|
486
|
+
def test_ignores_extra_fields(self):
|
|
487
|
+
item = TariffBlockLegendItem.model_validate({
|
|
488
|
+
"BlockId": "BLK1",
|
|
489
|
+
"RGB": {"R": 0, "G": 0, "B": 0},
|
|
490
|
+
"BlockRange": "",
|
|
491
|
+
"BlockPrice": "",
|
|
492
|
+
"Month": "Jan",
|
|
493
|
+
})
|
|
494
|
+
assert item.block_id == "BLK1"
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
class TestTariffBlockLegendGroup:
|
|
498
|
+
"""Tests for TariffBlockLegendGroup (RP4 monthly tariff breakdown)."""
|
|
499
|
+
|
|
500
|
+
def test_parse(self):
|
|
501
|
+
group = TariffBlockLegendGroup.model_validate({
|
|
502
|
+
"TariffBlocksLegend": [
|
|
503
|
+
{"BlockId": "EnergyCharge", "BlockRange": "0", "BlockPrice": "RM 0.2703"},
|
|
504
|
+
{"BlockId": "CustomerCharge", "BlockRange": "0", "BlockPrice": "RM 10.00"},
|
|
505
|
+
],
|
|
506
|
+
})
|
|
507
|
+
assert len(group.items) == 2
|
|
508
|
+
assert group.items[0].block_id == "EnergyCharge"
|
|
509
|
+
assert group.items[1].block_price == "RM 10.00"
|
|
510
|
+
|
|
511
|
+
def test_empty(self):
|
|
512
|
+
group = TariffBlockLegendGroup.model_validate({"TariffBlocksLegend": []})
|
|
513
|
+
assert group.items == []
|
|
@@ -223,6 +223,39 @@ wheels = [
|
|
|
223
223
|
{ url = "https://files.pythonhosted.org/packages/a2/ca/7e8365deec19afb2b2c7be7c1c0aa8f99633b54e90c570999acda93260fc/cryptography-48.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:db63bf618e5dea46c07de12e900fe1cdd2541e6dc9dbae772a70b7d4d4765f6a", size = 3739536, upload-time = "2026-05-04T22:59:29.61Z" },
|
|
224
224
|
]
|
|
225
225
|
|
|
226
|
+
[[package]]
|
|
227
|
+
name = "curl-cffi"
|
|
228
|
+
version = "0.15.0"
|
|
229
|
+
source = { registry = "https://pypi.org/simple" }
|
|
230
|
+
dependencies = [
|
|
231
|
+
{ name = "certifi" },
|
|
232
|
+
{ name = "cffi" },
|
|
233
|
+
{ name = "rich" },
|
|
234
|
+
]
|
|
235
|
+
sdist = { url = "https://files.pythonhosted.org/packages/48/5b/89fcfebd3e5e85134147ac99e9f2b2271165fd4d71984fc65da5f17819b7/curl_cffi-0.15.0.tar.gz", hash = "sha256:ea0c67652bf6893d34ee0f82c944f37e488f6147e9421bef1771cc6545b02ded", size = 196437, upload-time = "2026-04-03T11:12:31.525Z" }
|
|
236
|
+
wheels = [
|
|
237
|
+
{ url = "https://files.pythonhosted.org/packages/5e/42/54ddd442c795f30ce5dd4e49f87ce77505958d3777cd96a91567a3975d2a/curl_cffi-0.15.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:bda66404010e9ed743b1b83c20c86f24fe21a9a6873e17479d6e67e29d8ded28", size = 2795267, upload-time = "2026-04-03T11:11:46.48Z" },
|
|
238
|
+
{ url = "https://files.pythonhosted.org/packages/83/2d/3915e238579b3c5a92cead5c79130c3b8d20caaba7616cc4d894650e1d6b/curl_cffi-0.15.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:a25620d9bf989c9c029a7d1642999c4c265abb0bad811deb2f77b0b5b2b12e5b", size = 2573544, upload-time = "2026-04-03T11:11:47.951Z" },
|
|
239
|
+
{ url = "https://files.pythonhosted.org/packages/2a/b3/9d2f1057749a1b07ba1989db3c1503ce8bed998310bae9aea2c43aa64f20/curl_cffi-0.15.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:582e570aa2586b96ed47cf4a17586b9a3c462cbe43f780487c3dc245c6ef1527", size = 10515369, upload-time = "2026-04-03T11:11:50.126Z" },
|
|
240
|
+
{ url = "https://files.pythonhosted.org/packages/b5/1d/6d10dded5ce3fd8157e558ebd97d09e551b77a62cdc1c31e93d0a633cee5/curl_cffi-0.15.0-cp310-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:838e48212447d9c81364b04707a5c861daf08f8320f9ecb3406a8919d1d5c3b3", size = 10160045, upload-time = "2026-04-03T11:11:52.664Z" },
|
|
241
|
+
{ url = "https://files.pythonhosted.org/packages/5c/12/c70b835487ace3b9ba1502631912e3440082b8ae3a162f60b59cb0b6444d/curl_cffi-0.15.0-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b6c847d86283b07ae69bb72c82eb8a59242277142aa35b89850f89e792a02fc", size = 11090433, upload-time = "2026-04-03T11:11:55.049Z" },
|
|
242
|
+
{ url = "https://files.pythonhosted.org/packages/ea/0d/78edcc4f71934225db99df68197a107386d59080742fc7bf6bb4d007924f/curl_cffi-0.15.0-cp310-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9e5e69eee735f659287e2c84444319d68a1fa68dd37abf228943a4074864283a", size = 10479178, upload-time = "2026-04-03T11:11:57.685Z" },
|
|
243
|
+
{ url = "https://files.pythonhosted.org/packages/5b/84/1e101c1acb1ea2f0b4992f5c3024f596d8e21db0d53540b9d583f673c4e7/curl_cffi-0.15.0-cp310-abi3-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aa1323950224db24f4c510d010b3affa02196ca853fb424191fa917a513d3f4b", size = 10317051, upload-time = "2026-04-03T11:12:00.295Z" },
|
|
244
|
+
{ url = "https://files.pythonhosted.org/packages/28/42/8ef236b22a6c23d096c85a1dc507efe37bfdfc7a2f8a4b34efb590197369/curl_cffi-0.15.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:41f80170ba844009273b2660da1964ec31e99e5719d16b3422ada87177e32e13", size = 11299660, upload-time = "2026-04-03T11:12:02.791Z" },
|
|
245
|
+
{ url = "https://files.pythonhosted.org/packages/1d/01/56aeb055d962da87a1be0d74c6c644e251c7e88129b5471dc44ac724e678/curl_cffi-0.15.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1977e1e12cfb5c11352cbb74acef1bed24eb7d226dab61ca57c168c21acd4d61", size = 11945049, upload-time = "2026-04-03T11:12:05.912Z" },
|
|
246
|
+
{ url = "https://files.pythonhosted.org/packages/d8/8c/2abf99a38d6340d66cf0557e0c750ef3f8883dfc5d450087e01c85861343/curl_cffi-0.15.0-cp310-abi3-win_amd64.whl", hash = "sha256:5a0c1896a0d5a5ac1eb89cd24b008d2b718dd1df6fd2f75451b59ca66e49e572", size = 1661649, upload-time = "2026-04-03T11:12:07.948Z" },
|
|
247
|
+
{ url = "https://files.pythonhosted.org/packages/3d/39/dfd54f2240d3a9b96d77bacc62b97813b35e2aa8ecf5cd5013c683f1ba96/curl_cffi-0.15.0-cp310-abi3-win_arm64.whl", hash = "sha256:a6d57f8389273a3a1f94370473c74897467bcc36af0a17336989780c507fa43d", size = 1410741, upload-time = "2026-04-03T11:12:10.073Z" },
|
|
248
|
+
{ url = "https://files.pythonhosted.org/packages/19/6a/c24df8a4fc22fa84070dcd94abeba43c15e08cc09e35869565c0bad196fd/curl_cffi-0.15.0-cp313-abi3-android_24_arm64_v8a.whl", hash = "sha256:4682dc38d4336e0eb0b185374db90a760efde63cbea994b4e63f3521d44c4c92", size = 7190427, upload-time = "2026-04-03T11:12:12.142Z" },
|
|
249
|
+
{ url = "https://files.pythonhosted.org/packages/11/56/132225cb3491d07cc6adcce5fe395e059bde87c68cff1ef87a31c88c7819/curl_cffi-0.15.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:967ad7355bd8e9586f8c2d02eaa99953747549e7ea4a9b25cd53353e6b67fe6d", size = 2795723, upload-time = "2026-04-03T11:12:13.668Z" },
|
|
250
|
+
{ url = "https://files.pythonhosted.org/packages/07/8f/f4f83cd303bef7e8f1749512e5dd157e7e5d08b0a36c8211f9640a2757bf/curl_cffi-0.15.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7e63539d0d839d0a8c5eacf86229bc68c57803547f35e0db7ee0986328b478c3", size = 2573739, upload-time = "2026-04-03T11:12:15.08Z" },
|
|
251
|
+
{ url = "https://files.pythonhosted.org/packages/e8/5c/643d65c7fc9acd742876aa55c2d7823c438cb7665810acd2e66c9976c4d9/curl_cffi-0.15.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:08c799b89740b9bc49c09fbc3d5907f13ac1f845ca52620507ef9466d4639dd5", size = 10521046, upload-time = "2026-04-03T11:12:17.034Z" },
|
|
252
|
+
{ url = "https://files.pythonhosted.org/packages/7f/0b/9b8037113c93f4c5323096163471fa7c35c7676c3f608eeaf1287cd99d58/curl_cffi-0.15.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b7a92767a888ee90147e18964b396d8435ff42737030d6fb00824ffd6094805", size = 11096115, upload-time = "2026-04-03T11:12:19.694Z" },
|
|
253
|
+
{ url = "https://files.pythonhosted.org/packages/5f/96/fff2fcbd924ef4042e0d67379f751a8a4e3186a91e75e35a4cf218b306ee/curl_cffi-0.15.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:829cc357061ecb99cc2d406301f609a039e05665322f5c025ec67c38b0dc49ce", size = 11305346, upload-time = "2026-04-03T11:12:22.151Z" },
|
|
254
|
+
{ url = "https://files.pythonhosted.org/packages/53/1b/304b253a45ab28691c8c5e8cca1e6cbb9cf8e46dfceae4648dd536f75e73/curl_cffi-0.15.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:408d6f14e346841cd889c2e0962832bb235ba3b6749ebf609f347f747da5e60f", size = 11949834, upload-time = "2026-04-03T11:12:24.986Z" },
|
|
255
|
+
{ url = "https://files.pythonhosted.org/packages/5a/ff/4723d92f08259c707a974aba27a08d0a822b9555e35ca581bf18d055a364/curl_cffi-0.15.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b624c7ce087bfda967a013ed0a64702a525444e5b6e97d23534d567ccc6525aa", size = 1702771, upload-time = "2026-04-03T11:12:28.201Z" },
|
|
256
|
+
{ url = "https://files.pythonhosted.org/packages/59/8c/36bbe06d66fa2b765e4a07199f643a59a9cd1a754207a96335402a9520f4/curl_cffi-0.15.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0b6c0543b993996670e9e4b78e305a2d60809d5681903ffb5568e21a387434d3", size = 1466312, upload-time = "2026-04-03T11:12:30.054Z" },
|
|
257
|
+
]
|
|
258
|
+
|
|
226
259
|
[[package]]
|
|
227
260
|
name = "dill"
|
|
228
261
|
version = "0.4.1"
|
|
@@ -567,15 +600,15 @@ wheels = [
|
|
|
567
600
|
|
|
568
601
|
[[package]]
|
|
569
602
|
name = "python-mytnb"
|
|
570
|
-
version = "0.
|
|
603
|
+
version = "0.3.0"
|
|
571
604
|
source = { editable = "." }
|
|
572
605
|
dependencies = [
|
|
573
606
|
{ name = "click" },
|
|
574
607
|
{ name = "cryptography" },
|
|
608
|
+
{ name = "curl-cffi" },
|
|
575
609
|
{ name = "httpx" },
|
|
576
610
|
{ name = "pydantic" },
|
|
577
611
|
{ name = "rich" },
|
|
578
|
-
{ name = "tls-client" },
|
|
579
612
|
]
|
|
580
613
|
|
|
581
614
|
[package.optional-dependencies]
|
|
@@ -590,6 +623,7 @@ dev = [
|
|
|
590
623
|
requires-dist = [
|
|
591
624
|
{ name = "click", specifier = ">=8.3.3" },
|
|
592
625
|
{ name = "cryptography", specifier = ">=42.0" },
|
|
626
|
+
{ name = "curl-cffi", specifier = ">=0.7" },
|
|
593
627
|
{ name = "httpx", specifier = ">=0.27" },
|
|
594
628
|
{ name = "pydantic", specifier = ">=2.0" },
|
|
595
629
|
{ name = "pylint", marker = "extra == 'dev'", specifier = ">=3.0" },
|
|
@@ -597,7 +631,6 @@ requires-dist = [
|
|
|
597
631
|
{ name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.21" },
|
|
598
632
|
{ name = "rich", specifier = ">=15.0.0" },
|
|
599
633
|
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.6" },
|
|
600
|
-
{ name = "tls-client", specifier = ">=1.0" },
|
|
601
634
|
]
|
|
602
635
|
provides-extras = ["dev"]
|
|
603
636
|
|
|
@@ -639,15 +672,6 @@ wheels = [
|
|
|
639
672
|
{ url = "https://files.pythonhosted.org/packages/2d/c7/c53e8dbff9c9dc4b7928773421ae294a5d28fcb8dcda1a089579d3a7e510/ruff-0.15.17-py3-none-win_arm64.whl", hash = "sha256:f3be1fbb34bcdfd146240d8fb92a709d4c2c8191348580a3c044ec60fa0b4456", size = 11355275, upload-time = "2026-06-11T17:54:43.635Z" },
|
|
640
673
|
]
|
|
641
674
|
|
|
642
|
-
[[package]]
|
|
643
|
-
name = "tls-client"
|
|
644
|
-
version = "1.0.1"
|
|
645
|
-
source = { registry = "https://pypi.org/simple" }
|
|
646
|
-
sdist = { url = "https://files.pythonhosted.org/packages/3c/a6/6ec27c66a836a11a085e841f825d2f5bd289092f3bcd2f645558f587c89f/tls_client-1.0.1.tar.gz", hash = "sha256:dad797f3412bb713606e0765d489f547ffb580c5ffdb74aed47a183ce8505ff5", size = 16414, upload-time = "2024-02-02T18:55:55.767Z" }
|
|
647
|
-
wheels = [
|
|
648
|
-
{ url = "https://files.pythonhosted.org/packages/75/cd/5c735818692927e07980357445569adb6ee204c3332d19c516bae01c6cfa/tls_client-1.0.1-py3-none-any.whl", hash = "sha256:2f8915c0642c2226c9e33120072a2af082812f6310d32f4ea4da322db7d3bb1c", size = 41287556, upload-time = "2024-02-02T18:55:52.226Z" },
|
|
649
|
-
]
|
|
650
|
-
|
|
651
675
|
[[package]]
|
|
652
676
|
name = "tomli"
|
|
653
677
|
version = "2.4.1"
|
|
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
|