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.
Files changed (77) hide show
  1. {pytonapi-2.2.0/pytonapi.egg-info → pytonapi-2.2.1}/PKG-INFO +2 -1
  2. {pytonapi-2.2.0 → pytonapi-2.2.1}/README.md +1 -0
  3. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/__meta__.py +1 -1
  4. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/client.py +12 -0
  5. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/webhook/dispatcher.py +42 -32
  6. {pytonapi-2.2.0 → pytonapi-2.2.1/pytonapi.egg-info}/PKG-INFO +2 -1
  7. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi.egg-info/SOURCES.txt +1 -0
  8. pytonapi-2.2.1/tests/test_client.py +32 -0
  9. {pytonapi-2.2.0 → pytonapi-2.2.1}/LICENSE +0 -0
  10. {pytonapi-2.2.0 → pytonapi-2.2.1}/pyproject.toml +0 -0
  11. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/__init__.py +0 -0
  12. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/cli.py +0 -0
  13. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/exceptions.py +0 -0
  14. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/py.typed +0 -0
  15. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/__init__.py +0 -0
  16. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/client.py +0 -0
  17. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/limiter.py +0 -0
  18. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/mixin.py +0 -0
  19. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/__init__.py +0 -0
  20. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/_enums.py +0 -0
  21. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/accounts.py +0 -0
  22. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/blockchain.py +0 -0
  23. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/connect.py +0 -0
  24. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/dns.py +0 -0
  25. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/emulation.py +0 -0
  26. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/events.py +0 -0
  27. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/extra_currency.py +0 -0
  28. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/gasless.py +0 -0
  29. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/jettons.py +0 -0
  30. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/lite_server.py +0 -0
  31. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/multisig.py +0 -0
  32. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/nft.py +0 -0
  33. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/purchases.py +0 -0
  34. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/rates.py +0 -0
  35. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/staking.py +0 -0
  36. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/storage.py +0 -0
  37. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/traces.py +0 -0
  38. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/utilities.py +0 -0
  39. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/models/wallet.py +0 -0
  40. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/__init__.py +0 -0
  41. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/_base.py +0 -0
  42. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/accounts.py +0 -0
  43. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/blockchain.py +0 -0
  44. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/connect.py +0 -0
  45. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/dns.py +0 -0
  46. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/emulation.py +0 -0
  47. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/events.py +0 -0
  48. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/extra_currency.py +0 -0
  49. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/gasless.py +0 -0
  50. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/jettons.py +0 -0
  51. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/lite_server.py +0 -0
  52. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/multisig.py +0 -0
  53. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/nft.py +0 -0
  54. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/purchases.py +0 -0
  55. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/rates.py +0 -0
  56. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/staking.py +0 -0
  57. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/storage.py +0 -0
  58. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/traces.py +0 -0
  59. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/utilities.py +0 -0
  60. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/resources/wallet.py +0 -0
  61. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/rest/rotator.py +0 -0
  62. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/streaming/__init__.py +0 -0
  63. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/streaming/base.py +0 -0
  64. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/streaming/models.py +0 -0
  65. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/streaming/sse.py +0 -0
  66. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/streaming/ws.py +0 -0
  67. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/types.py +0 -0
  68. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/utils.py +0 -0
  69. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/webhook/__init__.py +0 -0
  70. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/webhook/client.py +0 -0
  71. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi/webhook/models.py +0 -0
  72. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi.egg-info/dependency_links.txt +0 -0
  73. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi.egg-info/entry_points.txt +0 -0
  74. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi.egg-info/requires.txt +0 -0
  75. {pytonapi-2.2.0 → pytonapi-2.2.1}/pytonapi.egg-info/top_level.txt +0 -0
  76. {pytonapi-2.2.0 → pytonapi-2.2.1}/setup.cfg +0 -0
  77. {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.0
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
  ![Downloads](https://pepy.tech/badge/pytonapi)
52
52
  ![Downloads](https://pepy.tech/badge/pytonapi/month)
53
53
  ![Downloads](https://pepy.tech/badge/pytonapi/week)
54
+ [![Context7](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fcontext7.com%2Fapi%2Fv2%2Flibs%2Fsearch%3FlibraryName%3Dnessshon%2Ftonapi%26query%3Dtonapi&query=%24.results%5B0%5D.benchmarkScore&label=Context7&suffix=%2F100&color=blue)](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
  ![Downloads](https://pepy.tech/badge/pytonapi)
12
12
  ![Downloads](https://pepy.tech/badge/pytonapi/month)
13
13
  ![Downloads](https://pepy.tech/badge/pytonapi/week)
14
+ [![Context7](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fcontext7.com%2Fapi%2Fv2%2Flibs%2Fsearch%3FlibraryName%3Dnessshon%2Ftonapi%26query%3Dtonapi&query=%24.results%5B0%5D.benchmarkScore&label=Context7&suffix=%2F100&color=blue)](https://context7.com/nessshon/tonapi)
14
15
 
15
16
  ### Python SDK for [TON API](https://tonapi.io)
16
17
 
@@ -3,6 +3,6 @@
3
3
  # This source code is licensed under the MIT License found in the
4
4
  # LICENSE file in the root directory of this source tree.
5
5
 
6
- __version__ = "2.2.0"
6
+ __version__ = "2.2.1"
7
7
  __author__ = "nessshon"
8
8
  __url__ = "https://github.com/nessshon/tonapi"
@@ -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
- suffix = self.DEFAULT_SUFFIXES[event_type]
110
- local_path = self._path + suffix
111
- webhook = await self._client.ensure(f"{self._url}{suffix}")
112
- self._tokens[local_path] = webhook.token
113
-
114
- if event_type is WebhookEventType.ACCOUNT_TX and self._accounts:
115
- await webhook.sync_accounts(list(self._accounts))
116
- elif event_type is WebhookEventType.MEMPOOL_MSG:
117
- await webhook.subscribe_mempool_msg()
118
- elif event_type is WebhookEventType.OPCODE_MSG and self._opcodes:
119
- for opcode in self._opcodes:
120
- await webhook.subscribe_opcode_msg(opcode)
121
- elif event_type is WebhookEventType.NEW_CONTRACTS:
122
- await webhook.subscribe_new_contracts()
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
- suffix = self.DEFAULT_SUFFIXES[event_type]
144
- endpoint = f"{self._url}{suffix}"
145
- info = endpoint_map.get(endpoint)
146
- if info is None:
147
- continue
148
- webhook = TonapiWebhook(self._client, info.id, info.endpoint, info.token)
149
-
150
- if event_type is WebhookEventType.ACCOUNT_TX and self._accounts:
151
- await webhook.unsubscribe(list(self._accounts))
152
- elif event_type is WebhookEventType.MEMPOOL_MSG:
153
- await webhook.unsubscribe_mempool_msg()
154
- elif event_type is WebhookEventType.OPCODE_MSG and self._opcodes:
155
- for opcode in self._opcodes:
156
- await webhook.unsubscribe_opcode_msg(opcode)
157
- elif event_type is WebhookEventType.NEW_CONTRACTS:
158
- await webhook.unsubscribe_new_contracts()
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 {handlers[0][2]: et for et, handlers in self._handlers.items() if handlers}
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.0
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
  ![Downloads](https://pepy.tech/badge/pytonapi)
52
52
  ![Downloads](https://pepy.tech/badge/pytonapi/month)
53
53
  ![Downloads](https://pepy.tech/badge/pytonapi/week)
54
+ [![Context7](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fcontext7.com%2Fapi%2Fv2%2Flibs%2Fsearch%3FlibraryName%3Dnessshon%2Ftonapi%26query%3Dtonapi&query=%24.results%5B0%5D.benchmarkScore&label=Context7&suffix=%2F100&color=blue)](https://context7.com/nessshon/tonapi)
54
55
 
55
56
  ### Python SDK for [TON API](https://tonapi.io)
56
57
 
@@ -71,4 +71,5 @@ pytonapi/webhook/__init__.py
71
71
  pytonapi/webhook/client.py
72
72
  pytonapi/webhook/dispatcher.py
73
73
  pytonapi/webhook/models.py
74
+ tests/test_client.py
74
75
  tests/test_utils.py
@@ -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