pytonapi 0.4.9__tar.gz → 2.0.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.
Files changed (144) hide show
  1. pytonapi-2.0.0/PKG-INFO +97 -0
  2. pytonapi-2.0.0/README.md +68 -0
  3. pytonapi-2.0.0/pyproject.toml +69 -0
  4. pytonapi-2.0.0/pytonapi/__init__.py +13 -0
  5. pytonapi-2.0.0/pytonapi/cli.py +23 -0
  6. pytonapi-2.0.0/pytonapi/client.py +262 -0
  7. pytonapi-2.0.0/pytonapi/exceptions.py +202 -0
  8. pytonapi-2.0.0/pytonapi/rest/__init__.py +3 -0
  9. pytonapi-2.0.0/pytonapi/rest/client.py +97 -0
  10. pytonapi-2.0.0/pytonapi/rest/limiter.py +33 -0
  11. pytonapi-2.0.0/pytonapi/rest/mixin.py +111 -0
  12. pytonapi-2.0.0/pytonapi/rest/models/__init__.py +513 -0
  13. pytonapi-2.0.0/pytonapi/rest/models/_enums.py +76 -0
  14. pytonapi-2.0.0/pytonapi/rest/models/accounts.py +486 -0
  15. pytonapi-2.0.0/pytonapi/rest/models/blockchain.py +453 -0
  16. pytonapi-0.4.9/pytonapi/schema/tonconnect.py → pytonapi-2.0.0/pytonapi/rest/models/connect.py +3 -3
  17. pytonapi-2.0.0/pytonapi/rest/models/dns.py +67 -0
  18. pytonapi-2.0.0/pytonapi/rest/models/emulation.py +21 -0
  19. pytonapi-2.0.0/pytonapi/rest/models/events.py +30 -0
  20. {pytonapi-0.4.9/pytonapi/schema → pytonapi-2.0.0/pytonapi/rest/models}/extra_currency.py +4 -0
  21. pytonapi-2.0.0/pytonapi/rest/models/gasless.py +40 -0
  22. pytonapi-2.0.0/pytonapi/rest/models/jettons.py +55 -0
  23. pytonapi-2.0.0/pytonapi/rest/models/lite_server.py +19 -0
  24. pytonapi-2.0.0/pytonapi/rest/models/multisig.py +49 -0
  25. pytonapi-2.0.0/pytonapi/rest/models/nft.py +75 -0
  26. pytonapi-2.0.0/pytonapi/rest/models/purchases.py +43 -0
  27. pytonapi-2.0.0/pytonapi/rest/models/rates.py +22 -0
  28. pytonapi-2.0.0/pytonapi/rest/models/staking.py +51 -0
  29. {pytonapi-0.4.9/pytonapi/schema → pytonapi-2.0.0/pytonapi/rest/models}/storage.py +4 -8
  30. pytonapi-2.0.0/pytonapi/rest/models/traces.py +17 -0
  31. pytonapi-2.0.0/pytonapi/rest/models/utilities.py +11 -0
  32. pytonapi-2.0.0/pytonapi/rest/models/wallet.py +67 -0
  33. pytonapi-2.0.0/pytonapi/rest/resources/__init__.py +43 -0
  34. pytonapi-2.0.0/pytonapi/rest/resources/_base.py +46 -0
  35. pytonapi-2.0.0/pytonapi/rest/resources/accounts.py +574 -0
  36. pytonapi-2.0.0/pytonapi/rest/resources/blockchain.py +428 -0
  37. pytonapi-2.0.0/pytonapi/rest/resources/connect.py +42 -0
  38. pytonapi-2.0.0/pytonapi/rest/resources/dns.py +84 -0
  39. pytonapi-2.0.0/pytonapi/rest/resources/emulation.py +138 -0
  40. pytonapi-2.0.0/pytonapi/rest/resources/events.py +64 -0
  41. pytonapi-2.0.0/pytonapi/rest/resources/extra_currency.py +25 -0
  42. pytonapi-2.0.0/pytonapi/rest/resources/gasless.py +67 -0
  43. pytonapi-2.0.0/pytonapi/rest/resources/jettons.py +142 -0
  44. pytonapi-2.0.0/pytonapi/rest/resources/lite_server.py +323 -0
  45. pytonapi-2.0.0/pytonapi/rest/resources/multisig.py +42 -0
  46. pytonapi-2.0.0/pytonapi/rest/resources/nft.py +203 -0
  47. pytonapi-2.0.0/pytonapi/rest/resources/purchases.py +36 -0
  48. pytonapi-2.0.0/pytonapi/rest/resources/rates.py +80 -0
  49. pytonapi-2.0.0/pytonapi/rest/resources/staking.py +100 -0
  50. pytonapi-2.0.0/pytonapi/rest/resources/storage.py +23 -0
  51. pytonapi-2.0.0/pytonapi/rest/resources/traces.py +50 -0
  52. pytonapi-2.0.0/pytonapi/rest/resources/utilities.py +70 -0
  53. pytonapi-2.0.0/pytonapi/rest/resources/wallet.py +129 -0
  54. pytonapi-2.0.0/pytonapi/streaming/__init__.py +19 -0
  55. pytonapi-2.0.0/pytonapi/streaming/client.py +89 -0
  56. pytonapi-2.0.0/pytonapi/streaming/models.py +35 -0
  57. pytonapi-2.0.0/pytonapi/streaming/sse.py +247 -0
  58. pytonapi-2.0.0/pytonapi/streaming/ws.py +232 -0
  59. pytonapi-2.0.0/pytonapi/types.py +209 -0
  60. pytonapi-2.0.0/pytonapi/utils.py +160 -0
  61. pytonapi-2.0.0/pytonapi/webhook/__init__.py +29 -0
  62. pytonapi-2.0.0/pytonapi/webhook/client.py +281 -0
  63. pytonapi-2.0.0/pytonapi/webhook/dispatcher.py +357 -0
  64. pytonapi-2.0.0/pytonapi/webhook/models.py +73 -0
  65. pytonapi-2.0.0/pytonapi.egg-info/PKG-INFO +97 -0
  66. pytonapi-2.0.0/pytonapi.egg-info/SOURCES.txt +72 -0
  67. pytonapi-2.0.0/pytonapi.egg-info/entry_points.txt +2 -0
  68. pytonapi-2.0.0/pytonapi.egg-info/requires.txt +2 -0
  69. pytonapi-2.0.0/tests/test_utils.py +122 -0
  70. pytonapi-0.4.9/PKG-INFO +0 -104
  71. pytonapi-0.4.9/README.md +0 -86
  72. pytonapi-0.4.9/pytonapi/__init__.py +0 -5
  73. pytonapi-0.4.9/pytonapi/base.py +0 -277
  74. pytonapi-0.4.9/pytonapi/exceptions.py +0 -71
  75. pytonapi-0.4.9/pytonapi/logger.py +0 -25
  76. pytonapi-0.4.9/pytonapi/methods/__init__.py +0 -45
  77. pytonapi-0.4.9/pytonapi/methods/accounts.py +0 -456
  78. pytonapi-0.4.9/pytonapi/methods/blockchain.py +0 -277
  79. pytonapi-0.4.9/pytonapi/methods/dns.py +0 -55
  80. pytonapi-0.4.9/pytonapi/methods/emulate.py +0 -139
  81. pytonapi-0.4.9/pytonapi/methods/events.py +0 -56
  82. pytonapi-0.4.9/pytonapi/methods/extra_currency.py +0 -17
  83. pytonapi-0.4.9/pytonapi/methods/gasless.py +0 -54
  84. pytonapi-0.4.9/pytonapi/methods/jettons.py +0 -85
  85. pytonapi-0.4.9/pytonapi/methods/liteserver.py +0 -273
  86. pytonapi-0.4.9/pytonapi/methods/multisig.py +0 -17
  87. pytonapi-0.4.9/pytonapi/methods/nft.py +0 -125
  88. pytonapi-0.4.9/pytonapi/methods/rates.py +0 -63
  89. pytonapi-0.4.9/pytonapi/methods/sse.py +0 -94
  90. pytonapi-0.4.9/pytonapi/methods/staking.py +0 -79
  91. pytonapi-0.4.9/pytonapi/methods/storage.py +0 -16
  92. pytonapi-0.4.9/pytonapi/methods/tonconnect.py +0 -28
  93. pytonapi-0.4.9/pytonapi/methods/traces.py +0 -42
  94. pytonapi-0.4.9/pytonapi/methods/utilites.py +0 -28
  95. pytonapi-0.4.9/pytonapi/methods/wallet.py +0 -103
  96. pytonapi-0.4.9/pytonapi/methods/webhooks.py +0 -81
  97. pytonapi-0.4.9/pytonapi/methods/websocket.py +0 -62
  98. pytonapi-0.4.9/pytonapi/schema/__init__.py +0 -35
  99. pytonapi-0.4.9/pytonapi/schema/_address.py +0 -29
  100. pytonapi-0.4.9/pytonapi/schema/_balance.py +0 -31
  101. pytonapi-0.4.9/pytonapi/schema/accounts.py +0 -82
  102. pytonapi-0.4.9/pytonapi/schema/blockchain.py +0 -258
  103. pytonapi-0.4.9/pytonapi/schema/dns.py +0 -33
  104. pytonapi-0.4.9/pytonapi/schema/domains.py +0 -28
  105. pytonapi-0.4.9/pytonapi/schema/events.py +0 -283
  106. pytonapi-0.4.9/pytonapi/schema/gasless.py +0 -27
  107. pytonapi-0.4.9/pytonapi/schema/jettons.py +0 -107
  108. pytonapi-0.4.9/pytonapi/schema/liteserver.py +0 -122
  109. pytonapi-0.4.9/pytonapi/schema/multisig.py +0 -39
  110. pytonapi-0.4.9/pytonapi/schema/nft.py +0 -100
  111. pytonapi-0.4.9/pytonapi/schema/rates.py +0 -28
  112. pytonapi-0.4.9/pytonapi/schema/staking.py +0 -60
  113. pytonapi-0.4.9/pytonapi/schema/traces.py +0 -145
  114. pytonapi-0.4.9/pytonapi/schema/utilites.py +0 -20
  115. pytonapi-0.4.9/pytonapi/schema/wallet.py +0 -27
  116. pytonapi-0.4.9/pytonapi/schema/webhooks.py +0 -32
  117. pytonapi-0.4.9/pytonapi/tonapi.py +0 -132
  118. pytonapi-0.4.9/pytonapi/utils.py +0 -99
  119. pytonapi-0.4.9/pytonapi.egg-info/PKG-INFO +0 -104
  120. pytonapi-0.4.9/pytonapi.egg-info/SOURCES.txt +0 -76
  121. pytonapi-0.4.9/pytonapi.egg-info/requires.txt +0 -2
  122. pytonapi-0.4.9/setup.py +0 -31
  123. pytonapi-0.4.9/tests/test_accounts_methods.py +0 -86
  124. pytonapi-0.4.9/tests/test_blockchain_methods.py +0 -81
  125. pytonapi-0.4.9/tests/test_dns_methods.py +0 -23
  126. pytonapi-0.4.9/tests/test_events_methods.py +0 -16
  127. pytonapi-0.4.9/tests/test_extra_currency_methods.py +0 -11
  128. pytonapi-0.4.9/tests/test_jettons_methods.py +0 -29
  129. pytonapi-0.4.9/tests/test_liteserver_methods.py +0 -11
  130. pytonapi-0.4.9/tests/test_nft_methods.py +0 -41
  131. pytonapi-0.4.9/tests/test_rates_methods.py +0 -21
  132. pytonapi-0.4.9/tests/test_sse_methods.py +0 -25
  133. pytonapi-0.4.9/tests/test_staking_methods.py +0 -24
  134. pytonapi-0.4.9/tests/test_storage_methods.py +0 -9
  135. pytonapi-0.4.9/tests/test_tonconnect_methods.py +0 -11
  136. pytonapi-0.4.9/tests/test_traces_methods.py +0 -15
  137. pytonapi-0.4.9/tests/test_utilities_methods.py +0 -15
  138. pytonapi-0.4.9/tests/test_wallet_method.py +0 -11
  139. pytonapi-0.4.9/tests/test_websocket_methods.py +0 -22
  140. {pytonapi-0.4.9 → pytonapi-2.0.0}/LICENSE +0 -0
  141. {pytonapi-0.4.9 → pytonapi-2.0.0}/pytonapi/py.typed +0 -0
  142. {pytonapi-0.4.9 → pytonapi-2.0.0}/pytonapi.egg-info/dependency_links.txt +0 -0
  143. {pytonapi-0.4.9 → pytonapi-2.0.0}/pytonapi.egg-info/top_level.txt +0 -0
  144. {pytonapi-0.4.9 → pytonapi-2.0.0}/setup.cfg +0 -0
@@ -0,0 +1,97 @@
1
+ Metadata-Version: 2.4
2
+ Name: pytonapi
3
+ Version: 2.0.0
4
+ Summary: Python SDK for TONAPI — REST API, streaming, and webhooks for TON blockchain.
5
+ Author: nessshon
6
+ Maintainer: nessshon
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/nessshon/pytonapi/
9
+ Project-URL: Examples, https://github.com/nessshon/pytonapi/tree/main/examples/
10
+ Keywords: AsyncIO,REST API,SDK,TON,TON blockchain,TONAPI,The Open Network,blockchain,crypto,streaming,webhooks
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Framework :: AsyncIO
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Financial and Insurance Industry
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Requires-Python: <3.15,>=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: aiohttp>=3.9.0
27
+ Requires-Dist: pydantic<3.0,>=2.4.1
28
+ Dynamic: license-file
29
+
30
+ # 📦 PyTONAPI
31
+
32
+ [![TON](https://img.shields.io/badge/TON-grey?logo=TON&logoColor=40AEF0)](https://ton.org)
33
+ ![Python Versions](https://img.shields.io/badge/Python-3.10%20--%203.14-black?color=FFE873&labelColor=3776AB)
34
+ [![PyPI](https://img.shields.io/pypi/v/pytonapi.svg?color=FFE873&labelColor=3776AB)](https://pypi.python.org/pypi/pytonapi)
35
+ [![License](https://img.shields.io/github/license/nessshon/pytonapi)](https://github.com/nessshon/pytonapi/blob/main/LICENSE)
36
+ [![Donate](https://img.shields.io/badge/Donate-TON-blue)](https://tonviewer.com/UQCZq3_Vd21-4y4m7Wc-ej9NFOhh_qvdfAkAYAOHoQ__Ness)
37
+
38
+ ![Image](assets/banner.png)
39
+
40
+ ![Downloads](https://pepy.tech/badge/pytonapi)
41
+ ![Downloads](https://pepy.tech/badge/pytonapi/month)
42
+ ![Downloads](https://pepy.tech/badge/pytonapi/week)
43
+
44
+ ### Python SDK for [TONAPI](https://tonapi.io)
45
+
46
+ Providing access to TON blockchain data via REST API, real-time streaming, and webhooks.
47
+ An API key is required and can be obtained at [tonconsole.com](https://tonconsole.com/). Documentation available
48
+ at [docs.tonconsole.com](https://docs.tonconsole.com/).
49
+
50
+ > For creating wallets, transferring TON, jettons, etc., use [tonutils](https://github.com/nessshon/tonutils).
51
+
52
+ **Features**
53
+
54
+ - **REST API** — accounts, NFTs, jettons, DNS, and more
55
+ - **Streaming** — real-time events via SSE and WebSocket
56
+ - **Webhooks** — push notifications with event dispatcher
57
+
58
+ > If this project has been useful to you, consider supporting its development.
59
+ > **TON**: `UQCZq3_Vd21-4y4m7Wc-ej9NFOhh_qvdfAkAYAOHoQ__Ness`
60
+
61
+ ## Installation
62
+
63
+ ```bash
64
+ pip install pytonapi
65
+ ```
66
+
67
+ ## Examples
68
+
69
+ **REST API**
70
+
71
+ - [Get account info](examples/get_account_info.py)
72
+ - [Get account transactions](examples/get_account_transactions.py)
73
+ - [Get NFTs by owner](examples/get_nft_by_owner.py)
74
+ - [Get NFTs by collection](examples/get_nft_by_collection.py)
75
+
76
+ **Streaming** (SSE & WebSocket)
77
+
78
+ - [SSE subscriptions](examples/streaming_sse.py)
79
+ - [WebSocket subscriptions](examples/streaming_websocket.py)
80
+
81
+ **Webhooks**
82
+
83
+ - [FastAPI webhook server](examples/webhook_fastapi.py)
84
+ - [aiohttp webhook server](examples/webhook_aiohttp.py)
85
+
86
+ **Transfers** (requires [tonutils](https://github.com/nessshon/tonutils))
87
+
88
+ - [Transfer TON](examples/transfer_ton.py)
89
+ - [Gasless transfer](examples/transfer_gasless.py)
90
+
91
+ ## Support
92
+
93
+ Supported by [TON Society](https://github.com/ton-society/grants-and-bounties) and [TONAPI](https://tonapi.io).
94
+
95
+ ## License
96
+
97
+ This repository is distributed under the [MIT License](LICENSE).
@@ -0,0 +1,68 @@
1
+ # 📦 PyTONAPI
2
+
3
+ [![TON](https://img.shields.io/badge/TON-grey?logo=TON&logoColor=40AEF0)](https://ton.org)
4
+ ![Python Versions](https://img.shields.io/badge/Python-3.10%20--%203.14-black?color=FFE873&labelColor=3776AB)
5
+ [![PyPI](https://img.shields.io/pypi/v/pytonapi.svg?color=FFE873&labelColor=3776AB)](https://pypi.python.org/pypi/pytonapi)
6
+ [![License](https://img.shields.io/github/license/nessshon/pytonapi)](https://github.com/nessshon/pytonapi/blob/main/LICENSE)
7
+ [![Donate](https://img.shields.io/badge/Donate-TON-blue)](https://tonviewer.com/UQCZq3_Vd21-4y4m7Wc-ej9NFOhh_qvdfAkAYAOHoQ__Ness)
8
+
9
+ ![Image](assets/banner.png)
10
+
11
+ ![Downloads](https://pepy.tech/badge/pytonapi)
12
+ ![Downloads](https://pepy.tech/badge/pytonapi/month)
13
+ ![Downloads](https://pepy.tech/badge/pytonapi/week)
14
+
15
+ ### Python SDK for [TONAPI](https://tonapi.io)
16
+
17
+ Providing access to TON blockchain data via REST API, real-time streaming, and webhooks.
18
+ An API key is required and can be obtained at [tonconsole.com](https://tonconsole.com/). Documentation available
19
+ at [docs.tonconsole.com](https://docs.tonconsole.com/).
20
+
21
+ > For creating wallets, transferring TON, jettons, etc., use [tonutils](https://github.com/nessshon/tonutils).
22
+
23
+ **Features**
24
+
25
+ - **REST API** — accounts, NFTs, jettons, DNS, and more
26
+ - **Streaming** — real-time events via SSE and WebSocket
27
+ - **Webhooks** — push notifications with event dispatcher
28
+
29
+ > If this project has been useful to you, consider supporting its development.
30
+ > **TON**: `UQCZq3_Vd21-4y4m7Wc-ej9NFOhh_qvdfAkAYAOHoQ__Ness`
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install pytonapi
36
+ ```
37
+
38
+ ## Examples
39
+
40
+ **REST API**
41
+
42
+ - [Get account info](examples/get_account_info.py)
43
+ - [Get account transactions](examples/get_account_transactions.py)
44
+ - [Get NFTs by owner](examples/get_nft_by_owner.py)
45
+ - [Get NFTs by collection](examples/get_nft_by_collection.py)
46
+
47
+ **Streaming** (SSE & WebSocket)
48
+
49
+ - [SSE subscriptions](examples/streaming_sse.py)
50
+ - [WebSocket subscriptions](examples/streaming_websocket.py)
51
+
52
+ **Webhooks**
53
+
54
+ - [FastAPI webhook server](examples/webhook_fastapi.py)
55
+ - [aiohttp webhook server](examples/webhook_aiohttp.py)
56
+
57
+ **Transfers** (requires [tonutils](https://github.com/nessshon/tonutils))
58
+
59
+ - [Transfer TON](examples/transfer_ton.py)
60
+ - [Gasless transfer](examples/transfer_gasless.py)
61
+
62
+ ## Support
63
+
64
+ Supported by [TON Society](https://github.com/ton-society/grants-and-bounties) and [TONAPI](https://tonapi.io).
65
+
66
+ ## License
67
+
68
+ This repository is distributed under the [MIT License](LICENSE).
@@ -0,0 +1,69 @@
1
+ [build-system]
2
+ requires = ["setuptools>=80"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pytonapi"
7
+ description = "Python SDK for TONAPI — REST API, streaming, and webhooks for TON blockchain."
8
+ requires-python = ">=3.10,<3.15"
9
+ authors = [
10
+ { name = "nessshon" },
11
+ ]
12
+ maintainers = [
13
+ { name = "nessshon" },
14
+ ]
15
+ dependencies = [
16
+ "aiohttp>=3.9.0",
17
+ "pydantic>=2.4.1,<3.0",
18
+ ]
19
+ keywords = [
20
+ "AsyncIO",
21
+ "REST API",
22
+ "SDK",
23
+ "TON",
24
+ "TON blockchain",
25
+ "TONAPI",
26
+ "The Open Network",
27
+ "blockchain",
28
+ "crypto",
29
+ "streaming",
30
+ "webhooks",
31
+ ]
32
+ classifiers = [
33
+ "Development Status :: 4 - Beta",
34
+ "Environment :: Console",
35
+ "Framework :: AsyncIO",
36
+ "Intended Audience :: Developers",
37
+ "Intended Audience :: Financial and Insurance Industry",
38
+ "Operating System :: OS Independent",
39
+ "Programming Language :: Python :: 3.10",
40
+ "Programming Language :: Python :: 3.11",
41
+ "Programming Language :: Python :: 3.12",
42
+ "Programming Language :: Python :: 3.13",
43
+ "Programming Language :: Python :: 3.14",
44
+ "Topic :: Software Development :: Libraries :: Python Modules",
45
+ ]
46
+ dynamic = ["version"]
47
+ readme = "README.md"
48
+ license = "MIT"
49
+
50
+ [project.scripts]
51
+ pytonapi = "pytonapi.cli:main"
52
+
53
+ [project.urls]
54
+ Homepage = "https://github.com/nessshon/pytonapi/"
55
+ Examples = "https://github.com/nessshon/pytonapi/tree/main/examples/"
56
+
57
+ [tool.setuptools.packages.find]
58
+ include = ["pytonapi", "pytonapi.*"]
59
+
60
+ [tool.setuptools.package-data]
61
+ pytonapi = ["py.typed"]
62
+
63
+ [tool.setuptools.dynamic]
64
+ version = { attr = "pytonapi.__init__.__version__" }
65
+
66
+ [tool.mypy]
67
+ python_version = "3.10"
68
+ follow_imports = "skip"
69
+ ignore_missing_imports = true
@@ -0,0 +1,13 @@
1
+ # Copyright (c) 2023 Shon Ness
2
+ #
3
+ # This source code is licensed under the MIT License found in the
4
+ # LICENSE file in the root directory of this source tree.
5
+
6
+
7
+ __all__ = [
8
+ "__uri__",
9
+ "__version__",
10
+ ]
11
+
12
+ __version__ = "2.0.0"
13
+ __uri__ = "https://github.com/nessshon/pytonapi"
@@ -0,0 +1,23 @@
1
+ import argparse
2
+
3
+ from pytonapi import __version__
4
+
5
+
6
+ def main() -> None:
7
+ """CLI entry-point."""
8
+ parser = argparse.ArgumentParser(
9
+ prog="pytonapi",
10
+ description="pytonapi CLI.",
11
+ )
12
+ parser.add_argument(
13
+ "-v",
14
+ "--version",
15
+ action="version",
16
+ version=f"pytonapi {__version__}",
17
+ )
18
+ parser.parse_args()
19
+ parser.print_help()
20
+
21
+
22
+ if __name__ == "__main__":
23
+ main()
@@ -0,0 +1,262 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import json
5
+ import typing as t
6
+
7
+ import aiohttp
8
+
9
+ from pytonapi.exceptions import (
10
+ TONAPIConnectionError,
11
+ TONAPIError,
12
+ TONAPIRetryLimitError,
13
+ TONAPISessionNotCreatedError,
14
+ raise_for_status,
15
+ )
16
+ from pytonapi.types import (
17
+ DEFAULT_RETRY_POLICY,
18
+ RetryPolicy,
19
+ )
20
+
21
+ __all__ = ["BaseClient"]
22
+
23
+ T = t.TypeVar("T")
24
+
25
+
26
+ class BaseClient:
27
+ """Base async HTTP client with session management and retry."""
28
+
29
+ def __init__(
30
+ self,
31
+ api_key: str,
32
+ base_url: str,
33
+ *,
34
+ timeout: float = 10.0,
35
+ session: t.Optional[aiohttp.ClientSession] = None,
36
+ headers: t.Optional[t.Dict[str, str]] = None,
37
+ cookies: t.Optional[t.Dict[str, str]] = None,
38
+ retry_policy: t.Optional[RetryPolicy] = DEFAULT_RETRY_POLICY,
39
+ ) -> None:
40
+ """Initialize the base HTTP client.
41
+
42
+ :param api_key: TONAPI key. Get one at https://tonconsole.com/.
43
+ :param base_url: Base URL for all requests.
44
+ :param timeout: Request timeout in seconds.
45
+ :param session: Optional external ``aiohttp.ClientSession``.
46
+ When provided, the client will not close it — the caller
47
+ is responsible for managing its lifecycle.
48
+ :param headers: Additional HTTP headers sent with every request.
49
+ :param cookies: Additional cookies sent with every request.
50
+ :param retry_policy: Retry policy, or ``None`` to disable retries.
51
+ """
52
+ self._api_key = api_key
53
+ self._base_url = base_url.rstrip("/")
54
+
55
+ self._headers = headers or {}
56
+ self._cookies = cookies or {}
57
+ self._timeout = aiohttp.ClientTimeout(total=timeout)
58
+ self._session: t.Optional[aiohttp.ClientSession] = session
59
+
60
+ self._is_external_session = session is not None
61
+ self._retry_policy = retry_policy
62
+
63
+ async def create_session(self) -> BaseClient:
64
+ """Create an ``aiohttp.ClientSession`` for making requests.
65
+
66
+ If an external session was provided via the ``session`` parameter,
67
+ this method does nothing — the external session is used as-is.
68
+
69
+ :return: This client instance.
70
+ """
71
+ if self._session is None or self._session.closed:
72
+ self._session = aiohttp.ClientSession(
73
+ headers=self._build_headers(),
74
+ cookies=self._cookies,
75
+ timeout=self._timeout,
76
+ )
77
+ self._is_external_session = False
78
+ return self
79
+
80
+ async def close_session(self) -> None:
81
+ """Close the ``aiohttp.ClientSession``.
82
+
83
+ If the session was provided externally, it is not closed here —
84
+ the caller is responsible for managing its lifecycle.
85
+ """
86
+ if self._session and not self._session.closed and not self._is_external_session:
87
+ await self._session.close()
88
+ self._session = None
89
+
90
+ async def __aenter__(self) -> BaseClient:
91
+ """Enter the async context manager."""
92
+ await self.create_session()
93
+ return self
94
+
95
+ async def __aexit__(
96
+ self,
97
+ exc_type: t.Optional[t.Type[BaseException]],
98
+ exc_val: t.Optional[BaseException],
99
+ exc_tb: t.Optional[t.Any],
100
+ ) -> None:
101
+ """Exit the async context manager."""
102
+ await self.close_session()
103
+
104
+ def _build_headers(self) -> t.Dict[str, str]:
105
+ """Build default request headers.
106
+
107
+ :return: Merged headers dict.
108
+ """
109
+ base = {
110
+ "Authorization": f"Bearer {self._api_key}",
111
+ "Accept": "application/json",
112
+ }
113
+ base.update(self._headers)
114
+ return base
115
+
116
+ @staticmethod
117
+ def _parse_body(text: str) -> t.Tuple[t.Any, str]:
118
+ """Parse response text and extract error message.
119
+
120
+ :param text: Raw response text.
121
+ :return: Tuple of ``(parsed_data, error_message)``.
122
+ """
123
+ try:
124
+ data = json.loads(text)
125
+ except (json.JSONDecodeError,):
126
+ return None, text
127
+
128
+ if isinstance(data, dict):
129
+ message = data.get("error") or data.get("Error") or text
130
+ if isinstance(message, dict):
131
+ message = str(message)
132
+ return data, message
133
+
134
+ return data, str(data)
135
+
136
+ async def request(
137
+ self,
138
+ method: str,
139
+ path: str,
140
+ *,
141
+ params: t.Optional[t.Dict[str, t.Any]] = None,
142
+ body: t.Optional[t.Any] = None,
143
+ headers: t.Optional[t.Dict[str, t.Any]] = None,
144
+ response_model: t.Optional[t.Type[T]] = None,
145
+ ) -> t.Any:
146
+ """Execute an HTTP request with retry.
147
+
148
+ :param method: HTTP method (``GET``, ``POST``, etc.).
149
+ :param path: API path.
150
+ :param params: Query parameters.
151
+ :param body: JSON request body.
152
+ :param headers: Additional request headers.
153
+ :param response_model: Pydantic model to parse response into.
154
+ :return: Parsed model instance, raw dict, or ``None``.
155
+ """
156
+ url = f"{self._base_url}{path}"
157
+ if params:
158
+ params = {
159
+ k: str(v).lower() if isinstance(v, bool) else v
160
+ for k, v in params.items()
161
+ if v is not None
162
+ }
163
+ if headers:
164
+ headers = {k: str(v) for k, v in headers.items() if v is not None}
165
+
166
+ if self._session is None or self._session.closed:
167
+ raise TONAPISessionNotCreatedError(self.__class__.__name__)
168
+
169
+ session = self._session
170
+ last_error: t.Optional[Exception] = None
171
+ last_status: t.Optional[int] = None
172
+ max_attempts = 1
173
+
174
+ if self._retry_policy:
175
+ max_rule_retries = max(
176
+ (r.max_retries for r in self._retry_policy.rules),
177
+ default=0,
178
+ )
179
+ max_attempts = max_rule_retries + 1
180
+
181
+ for attempt in range(max_attempts):
182
+ try:
183
+ async with session.request(
184
+ method=method,
185
+ url=url,
186
+ params=params,
187
+ json=body,
188
+ headers=headers,
189
+ ) as response:
190
+ if 200 <= response.status < 300:
191
+ return await self._handle_success(
192
+ response,
193
+ response_model,
194
+ )
195
+
196
+ last_status = response.status
197
+
198
+ if self._retry_policy:
199
+ rule = self._retry_policy.find_rule(response.status)
200
+ if rule and attempt < rule.max_retries:
201
+ delay = rule.delay_for_attempt(attempt)
202
+ await response.read()
203
+ await asyncio.sleep(delay)
204
+ continue
205
+
206
+ text = await response.text()
207
+ content_type = response.headers.get("Content-Type", "")
208
+ raise_for_status(response.status, text, content_type)
209
+
210
+ except (TONAPIError,):
211
+ raise
212
+ except (aiohttp.ClientError,) as exc:
213
+ last_error = exc
214
+ if attempt < max_attempts - 1:
215
+ await asyncio.sleep(1.0)
216
+ continue
217
+ raise TONAPIConnectionError(
218
+ f"Connection error: {exc}",
219
+ ) from exc
220
+
221
+ raise TONAPIRetryLimitError(
222
+ attempts=max_attempts,
223
+ last_status=last_status,
224
+ last_error=last_error,
225
+ )
226
+
227
+ async def _handle_success(
228
+ self,
229
+ response: aiohttp.ClientResponse,
230
+ response_model: t.Optional[t.Type[T]],
231
+ ) -> t.Any:
232
+ """Process a successful (2xx) response.
233
+
234
+ :param response: Aiohttp response.
235
+ :param response_model: Expected return type.
236
+ :return: Parsed result.
237
+ """
238
+ content_type = response.headers.get("Content-Type", "")
239
+ if "text/html" in content_type:
240
+ raise TONAPIError(
241
+ f"HTTP {response.status}: Unexpected HTML response",
242
+ )
243
+
244
+ if response_model is None:
245
+ text = await response.text()
246
+ data, _ = self._parse_body(text)
247
+ return data
248
+ if response_model is bytes:
249
+ return await response.read()
250
+ if response_model is str:
251
+ return await response.text()
252
+ if response_model is bool:
253
+ text = (await response.text()).strip().lower()
254
+ return text in ("true", "1")
255
+ if response_model in (int, float):
256
+ return response_model(await response.text()) # type: ignore[call-arg]
257
+
258
+ text = await response.text()
259
+ data, _ = self._parse_body(text)
260
+ if data is None:
261
+ raise TONAPIError(f"Expected JSON response, got: {text}")
262
+ return response_model.model_validate(data) # type: ignore[attr-defined]