pytonapi 2.2.0__tar.gz → 2.2.1__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.
- {pytonapi-2.2.0/pytonapi.egg-info → pytonapi-2.2.1}/PKG-INFO +2 -1
- {pytonapi-2.2.0 → pytonapi-2.2.1}/README.md +1 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/__meta__.py +1 -1
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/client.py +12 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/webhook/dispatcher.py +42 -32
- {pytonapi-2.2.0 → pytonapi-2.2.1/pytonapi.egg-info}/PKG-INFO +2 -1
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi.egg-info/SOURCES.txt +1 -0
- pytonapi-2.2.1/tests/test_client.py +32 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/LICENSE +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pyproject.toml +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/__init__.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/cli.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/exceptions.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/py.typed +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/__init__.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/client.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/limiter.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/mixin.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/__init__.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/_enums.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/accounts.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/blockchain.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/connect.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/dns.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/emulation.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/events.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/extra_currency.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/gasless.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/jettons.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/lite_server.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/multisig.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/nft.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/purchases.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/rates.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/staking.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/storage.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/traces.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/utilities.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/wallet.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/__init__.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/_base.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/accounts.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/blockchain.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/connect.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/dns.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/emulation.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/events.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/extra_currency.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/gasless.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/jettons.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/lite_server.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/multisig.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/nft.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/purchases.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/rates.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/staking.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/storage.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/traces.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/utilities.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/wallet.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/rotator.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/streaming/__init__.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/streaming/base.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/streaming/models.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/streaming/sse.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/streaming/ws.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/types.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/utils.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/webhook/__init__.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/webhook/client.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/webhook/models.py +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi.egg-info/dependency_links.txt +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi.egg-info/entry_points.txt +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi.egg-info/requires.txt +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi.egg-info/top_level.txt +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/setup.cfg +0 -0
- {pytonapi-2.2.0 → pytonapi-2.2.1}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytonapi
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.1
|
|
4
4
|
Summary: Python SDK for TONAPI — REST API, streaming, and webhooks for TON blockchain.
|
|
5
5
|
Author: nessshon
|
|
6
6
|
Maintainer: nessshon
|
|
@@ -51,6 +51,7 @@ Dynamic: license-file
|
|
|
51
51
|

|
|
52
52
|

|
|
53
53
|

|
|
54
|
+
[](https://context7.com/nessshon/tonapi)
|
|
54
55
|
|
|
55
56
|
### Python SDK for [TON API](https://tonapi.io)
|
|
56
57
|
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|

|
|
12
12
|

|
|
13
13
|

|
|
14
|
+
[](https://context7.com/nessshon/tonapi)
|
|
14
15
|
|
|
15
16
|
### Python SDK for [TON API](https://tonapi.io)
|
|
16
17
|
|
|
@@ -107,6 +107,18 @@ class BaseClient:
|
|
|
107
107
|
await asyncio.sleep(0)
|
|
108
108
|
self._session = None
|
|
109
109
|
|
|
110
|
+
@property
|
|
111
|
+
def session(self) -> aiohttp.ClientSession:
|
|
112
|
+
"""Active ``aiohttp.ClientSession``.
|
|
113
|
+
|
|
114
|
+
:raises TONAPISessionNotCreatedError: If the session has not been
|
|
115
|
+
created yet — call ``create_session()`` or use the client as an
|
|
116
|
+
async context manager first.
|
|
117
|
+
"""
|
|
118
|
+
if self._session is None or self._session.closed:
|
|
119
|
+
raise TONAPISessionNotCreatedError(self.__class__.__name__)
|
|
120
|
+
return self._session
|
|
121
|
+
|
|
110
122
|
async def __aenter__(self: _Self) -> _Self:
|
|
111
123
|
"""Enter the async context manager."""
|
|
112
124
|
await self.create_session()
|
|
@@ -106,20 +106,21 @@ class TonapiWebhookDispatcher:
|
|
|
106
106
|
for event_type, handlers in self._handlers.items():
|
|
107
107
|
if not handlers:
|
|
108
108
|
continue
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
109
|
+
# One webhook per distinct path (incl. custom ``path=``) so every
|
|
110
|
+
# path gets its token stored — otherwise auth is silently skipped.
|
|
111
|
+
for local_path in sorted({path for _, _, path in handlers}):
|
|
112
|
+
webhook = await self._client.ensure(self._endpoint_for_path(local_path))
|
|
113
|
+
self._tokens[local_path] = webhook.token
|
|
114
|
+
|
|
115
|
+
if event_type is WebhookEventType.ACCOUNT_TX and self._accounts:
|
|
116
|
+
await webhook.sync_accounts(list(self._accounts))
|
|
117
|
+
elif event_type is WebhookEventType.MEMPOOL_MSG:
|
|
118
|
+
await webhook.subscribe_mempool_msg()
|
|
119
|
+
elif event_type is WebhookEventType.OPCODE_MSG and self._opcodes:
|
|
120
|
+
for opcode in self._opcodes:
|
|
121
|
+
await webhook.subscribe_opcode_msg(opcode)
|
|
122
|
+
elif event_type is WebhookEventType.NEW_CONTRACTS:
|
|
123
|
+
await webhook.subscribe_new_contracts()
|
|
123
124
|
|
|
124
125
|
async def teardown(self, cleanup: bool = False) -> None:
|
|
125
126
|
"""Close session and optionally unsubscribe.
|
|
@@ -140,22 +141,22 @@ class TonapiWebhookDispatcher:
|
|
|
140
141
|
for event_type, handlers in self._handlers.items():
|
|
141
142
|
if not handlers:
|
|
142
143
|
continue
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
144
|
+
for local_path in sorted({path for _, _, path in handlers}):
|
|
145
|
+
endpoint = self._endpoint_for_path(local_path)
|
|
146
|
+
info = endpoint_map.get(endpoint)
|
|
147
|
+
if info is None:
|
|
148
|
+
continue
|
|
149
|
+
webhook = TonapiWebhook(self._client, info.id, info.endpoint, info.token)
|
|
150
|
+
|
|
151
|
+
if event_type is WebhookEventType.ACCOUNT_TX and self._accounts:
|
|
152
|
+
await webhook.unsubscribe(list(self._accounts))
|
|
153
|
+
elif event_type is WebhookEventType.MEMPOOL_MSG:
|
|
154
|
+
await webhook.unsubscribe_mempool_msg()
|
|
155
|
+
elif event_type is WebhookEventType.OPCODE_MSG and self._opcodes:
|
|
156
|
+
for opcode in self._opcodes:
|
|
157
|
+
await webhook.unsubscribe_opcode_msg(opcode)
|
|
158
|
+
elif event_type is WebhookEventType.NEW_CONTRACTS:
|
|
159
|
+
await webhook.unsubscribe_new_contracts()
|
|
159
160
|
|
|
160
161
|
await self._client.close_session()
|
|
161
162
|
|
|
@@ -177,9 +178,18 @@ class TonapiWebhookDispatcher:
|
|
|
177
178
|
"""
|
|
178
179
|
return self._path + self.DEFAULT_SUFFIXES[event_type]
|
|
179
180
|
|
|
181
|
+
def _endpoint_for_path(self, path: str) -> str:
|
|
182
|
+
"""Build the absolute webhook endpoint URL for a local path.
|
|
183
|
+
|
|
184
|
+
:param path: Local URL path component.
|
|
185
|
+
:return: Absolute endpoint URL.
|
|
186
|
+
"""
|
|
187
|
+
parsed = urlparse(self._url)
|
|
188
|
+
return parsed._replace(path=path, params="", query="", fragment="").geturl()
|
|
189
|
+
|
|
180
190
|
def _build_path_map(self) -> dict[str, WebhookEventType]:
|
|
181
|
-
"""Build reverse mapping from path to event type."""
|
|
182
|
-
return {
|
|
191
|
+
"""Build reverse mapping from every registered path to its event type."""
|
|
192
|
+
return {path: et for et, handlers in self._handlers.items() for _, _, path in handlers}
|
|
183
193
|
|
|
184
194
|
def account_tx(
|
|
185
195
|
self,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytonapi
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.1
|
|
4
4
|
Summary: Python SDK for TONAPI — REST API, streaming, and webhooks for TON blockchain.
|
|
5
5
|
Author: nessshon
|
|
6
6
|
Maintainer: nessshon
|
|
@@ -51,6 +51,7 @@ Dynamic: license-file
|
|
|
51
51
|

|
|
52
52
|

|
|
53
53
|

|
|
54
|
+
[](https://context7.com/nessshon/tonapi)
|
|
54
55
|
|
|
55
56
|
### Python SDK for [TON API](https://tonapi.io)
|
|
56
57
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from unittest import IsolatedAsyncioTestCase
|
|
2
|
+
|
|
3
|
+
import aiohttp
|
|
4
|
+
|
|
5
|
+
from pytonapi.exceptions import TONAPISessionNotCreatedError
|
|
6
|
+
from pytonapi.rest import TonapiRestClient
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestSessionProperty(IsolatedAsyncioTestCase):
|
|
10
|
+
async def test_raises_before_create_session(self) -> None:
|
|
11
|
+
client = TonapiRestClient()
|
|
12
|
+
with self.assertRaises(TONAPISessionNotCreatedError):
|
|
13
|
+
_ = client.session
|
|
14
|
+
|
|
15
|
+
async def test_returns_active_session(self) -> None:
|
|
16
|
+
client = TonapiRestClient()
|
|
17
|
+
await client.create_session()
|
|
18
|
+
try:
|
|
19
|
+
self.assertIsInstance(client.session, aiohttp.ClientSession)
|
|
20
|
+
finally:
|
|
21
|
+
await client.close_session()
|
|
22
|
+
|
|
23
|
+
async def test_raises_after_close(self) -> None:
|
|
24
|
+
client = TonapiRestClient()
|
|
25
|
+
await client.create_session()
|
|
26
|
+
await client.close_session()
|
|
27
|
+
with self.assertRaises(TONAPISessionNotCreatedError):
|
|
28
|
+
_ = client.session
|
|
29
|
+
|
|
30
|
+
async def test_available_in_context_manager(self) -> None:
|
|
31
|
+
async with TonapiRestClient() as client:
|
|
32
|
+
self.assertIsInstance(client.session, aiohttp.ClientSession)
|
|
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
|