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.
- pytonapi-2.0.0/PKG-INFO +97 -0
- pytonapi-2.0.0/README.md +68 -0
- pytonapi-2.0.0/pyproject.toml +69 -0
- pytonapi-2.0.0/pytonapi/__init__.py +13 -0
- pytonapi-2.0.0/pytonapi/cli.py +23 -0
- pytonapi-2.0.0/pytonapi/client.py +262 -0
- pytonapi-2.0.0/pytonapi/exceptions.py +202 -0
- pytonapi-2.0.0/pytonapi/rest/__init__.py +3 -0
- pytonapi-2.0.0/pytonapi/rest/client.py +97 -0
- pytonapi-2.0.0/pytonapi/rest/limiter.py +33 -0
- pytonapi-2.0.0/pytonapi/rest/mixin.py +111 -0
- pytonapi-2.0.0/pytonapi/rest/models/__init__.py +513 -0
- pytonapi-2.0.0/pytonapi/rest/models/_enums.py +76 -0
- pytonapi-2.0.0/pytonapi/rest/models/accounts.py +486 -0
- pytonapi-2.0.0/pytonapi/rest/models/blockchain.py +453 -0
- pytonapi-0.4.9/pytonapi/schema/tonconnect.py → pytonapi-2.0.0/pytonapi/rest/models/connect.py +3 -3
- pytonapi-2.0.0/pytonapi/rest/models/dns.py +67 -0
- pytonapi-2.0.0/pytonapi/rest/models/emulation.py +21 -0
- pytonapi-2.0.0/pytonapi/rest/models/events.py +30 -0
- {pytonapi-0.4.9/pytonapi/schema → pytonapi-2.0.0/pytonapi/rest/models}/extra_currency.py +4 -0
- pytonapi-2.0.0/pytonapi/rest/models/gasless.py +40 -0
- pytonapi-2.0.0/pytonapi/rest/models/jettons.py +55 -0
- pytonapi-2.0.0/pytonapi/rest/models/lite_server.py +19 -0
- pytonapi-2.0.0/pytonapi/rest/models/multisig.py +49 -0
- pytonapi-2.0.0/pytonapi/rest/models/nft.py +75 -0
- pytonapi-2.0.0/pytonapi/rest/models/purchases.py +43 -0
- pytonapi-2.0.0/pytonapi/rest/models/rates.py +22 -0
- pytonapi-2.0.0/pytonapi/rest/models/staking.py +51 -0
- {pytonapi-0.4.9/pytonapi/schema → pytonapi-2.0.0/pytonapi/rest/models}/storage.py +4 -8
- pytonapi-2.0.0/pytonapi/rest/models/traces.py +17 -0
- pytonapi-2.0.0/pytonapi/rest/models/utilities.py +11 -0
- pytonapi-2.0.0/pytonapi/rest/models/wallet.py +67 -0
- pytonapi-2.0.0/pytonapi/rest/resources/__init__.py +43 -0
- pytonapi-2.0.0/pytonapi/rest/resources/_base.py +46 -0
- pytonapi-2.0.0/pytonapi/rest/resources/accounts.py +574 -0
- pytonapi-2.0.0/pytonapi/rest/resources/blockchain.py +428 -0
- pytonapi-2.0.0/pytonapi/rest/resources/connect.py +42 -0
- pytonapi-2.0.0/pytonapi/rest/resources/dns.py +84 -0
- pytonapi-2.0.0/pytonapi/rest/resources/emulation.py +138 -0
- pytonapi-2.0.0/pytonapi/rest/resources/events.py +64 -0
- pytonapi-2.0.0/pytonapi/rest/resources/extra_currency.py +25 -0
- pytonapi-2.0.0/pytonapi/rest/resources/gasless.py +67 -0
- pytonapi-2.0.0/pytonapi/rest/resources/jettons.py +142 -0
- pytonapi-2.0.0/pytonapi/rest/resources/lite_server.py +323 -0
- pytonapi-2.0.0/pytonapi/rest/resources/multisig.py +42 -0
- pytonapi-2.0.0/pytonapi/rest/resources/nft.py +203 -0
- pytonapi-2.0.0/pytonapi/rest/resources/purchases.py +36 -0
- pytonapi-2.0.0/pytonapi/rest/resources/rates.py +80 -0
- pytonapi-2.0.0/pytonapi/rest/resources/staking.py +100 -0
- pytonapi-2.0.0/pytonapi/rest/resources/storage.py +23 -0
- pytonapi-2.0.0/pytonapi/rest/resources/traces.py +50 -0
- pytonapi-2.0.0/pytonapi/rest/resources/utilities.py +70 -0
- pytonapi-2.0.0/pytonapi/rest/resources/wallet.py +129 -0
- pytonapi-2.0.0/pytonapi/streaming/__init__.py +19 -0
- pytonapi-2.0.0/pytonapi/streaming/client.py +89 -0
- pytonapi-2.0.0/pytonapi/streaming/models.py +35 -0
- pytonapi-2.0.0/pytonapi/streaming/sse.py +247 -0
- pytonapi-2.0.0/pytonapi/streaming/ws.py +232 -0
- pytonapi-2.0.0/pytonapi/types.py +209 -0
- pytonapi-2.0.0/pytonapi/utils.py +160 -0
- pytonapi-2.0.0/pytonapi/webhook/__init__.py +29 -0
- pytonapi-2.0.0/pytonapi/webhook/client.py +281 -0
- pytonapi-2.0.0/pytonapi/webhook/dispatcher.py +357 -0
- pytonapi-2.0.0/pytonapi/webhook/models.py +73 -0
- pytonapi-2.0.0/pytonapi.egg-info/PKG-INFO +97 -0
- pytonapi-2.0.0/pytonapi.egg-info/SOURCES.txt +72 -0
- pytonapi-2.0.0/pytonapi.egg-info/entry_points.txt +2 -0
- pytonapi-2.0.0/pytonapi.egg-info/requires.txt +2 -0
- pytonapi-2.0.0/tests/test_utils.py +122 -0
- pytonapi-0.4.9/PKG-INFO +0 -104
- pytonapi-0.4.9/README.md +0 -86
- pytonapi-0.4.9/pytonapi/__init__.py +0 -5
- pytonapi-0.4.9/pytonapi/base.py +0 -277
- pytonapi-0.4.9/pytonapi/exceptions.py +0 -71
- pytonapi-0.4.9/pytonapi/logger.py +0 -25
- pytonapi-0.4.9/pytonapi/methods/__init__.py +0 -45
- pytonapi-0.4.9/pytonapi/methods/accounts.py +0 -456
- pytonapi-0.4.9/pytonapi/methods/blockchain.py +0 -277
- pytonapi-0.4.9/pytonapi/methods/dns.py +0 -55
- pytonapi-0.4.9/pytonapi/methods/emulate.py +0 -139
- pytonapi-0.4.9/pytonapi/methods/events.py +0 -56
- pytonapi-0.4.9/pytonapi/methods/extra_currency.py +0 -17
- pytonapi-0.4.9/pytonapi/methods/gasless.py +0 -54
- pytonapi-0.4.9/pytonapi/methods/jettons.py +0 -85
- pytonapi-0.4.9/pytonapi/methods/liteserver.py +0 -273
- pytonapi-0.4.9/pytonapi/methods/multisig.py +0 -17
- pytonapi-0.4.9/pytonapi/methods/nft.py +0 -125
- pytonapi-0.4.9/pytonapi/methods/rates.py +0 -63
- pytonapi-0.4.9/pytonapi/methods/sse.py +0 -94
- pytonapi-0.4.9/pytonapi/methods/staking.py +0 -79
- pytonapi-0.4.9/pytonapi/methods/storage.py +0 -16
- pytonapi-0.4.9/pytonapi/methods/tonconnect.py +0 -28
- pytonapi-0.4.9/pytonapi/methods/traces.py +0 -42
- pytonapi-0.4.9/pytonapi/methods/utilites.py +0 -28
- pytonapi-0.4.9/pytonapi/methods/wallet.py +0 -103
- pytonapi-0.4.9/pytonapi/methods/webhooks.py +0 -81
- pytonapi-0.4.9/pytonapi/methods/websocket.py +0 -62
- pytonapi-0.4.9/pytonapi/schema/__init__.py +0 -35
- pytonapi-0.4.9/pytonapi/schema/_address.py +0 -29
- pytonapi-0.4.9/pytonapi/schema/_balance.py +0 -31
- pytonapi-0.4.9/pytonapi/schema/accounts.py +0 -82
- pytonapi-0.4.9/pytonapi/schema/blockchain.py +0 -258
- pytonapi-0.4.9/pytonapi/schema/dns.py +0 -33
- pytonapi-0.4.9/pytonapi/schema/domains.py +0 -28
- pytonapi-0.4.9/pytonapi/schema/events.py +0 -283
- pytonapi-0.4.9/pytonapi/schema/gasless.py +0 -27
- pytonapi-0.4.9/pytonapi/schema/jettons.py +0 -107
- pytonapi-0.4.9/pytonapi/schema/liteserver.py +0 -122
- pytonapi-0.4.9/pytonapi/schema/multisig.py +0 -39
- pytonapi-0.4.9/pytonapi/schema/nft.py +0 -100
- pytonapi-0.4.9/pytonapi/schema/rates.py +0 -28
- pytonapi-0.4.9/pytonapi/schema/staking.py +0 -60
- pytonapi-0.4.9/pytonapi/schema/traces.py +0 -145
- pytonapi-0.4.9/pytonapi/schema/utilites.py +0 -20
- pytonapi-0.4.9/pytonapi/schema/wallet.py +0 -27
- pytonapi-0.4.9/pytonapi/schema/webhooks.py +0 -32
- pytonapi-0.4.9/pytonapi/tonapi.py +0 -132
- pytonapi-0.4.9/pytonapi/utils.py +0 -99
- pytonapi-0.4.9/pytonapi.egg-info/PKG-INFO +0 -104
- pytonapi-0.4.9/pytonapi.egg-info/SOURCES.txt +0 -76
- pytonapi-0.4.9/pytonapi.egg-info/requires.txt +0 -2
- pytonapi-0.4.9/setup.py +0 -31
- pytonapi-0.4.9/tests/test_accounts_methods.py +0 -86
- pytonapi-0.4.9/tests/test_blockchain_methods.py +0 -81
- pytonapi-0.4.9/tests/test_dns_methods.py +0 -23
- pytonapi-0.4.9/tests/test_events_methods.py +0 -16
- pytonapi-0.4.9/tests/test_extra_currency_methods.py +0 -11
- pytonapi-0.4.9/tests/test_jettons_methods.py +0 -29
- pytonapi-0.4.9/tests/test_liteserver_methods.py +0 -11
- pytonapi-0.4.9/tests/test_nft_methods.py +0 -41
- pytonapi-0.4.9/tests/test_rates_methods.py +0 -21
- pytonapi-0.4.9/tests/test_sse_methods.py +0 -25
- pytonapi-0.4.9/tests/test_staking_methods.py +0 -24
- pytonapi-0.4.9/tests/test_storage_methods.py +0 -9
- pytonapi-0.4.9/tests/test_tonconnect_methods.py +0 -11
- pytonapi-0.4.9/tests/test_traces_methods.py +0 -15
- pytonapi-0.4.9/tests/test_utilities_methods.py +0 -15
- pytonapi-0.4.9/tests/test_wallet_method.py +0 -11
- pytonapi-0.4.9/tests/test_websocket_methods.py +0 -22
- {pytonapi-0.4.9 → pytonapi-2.0.0}/LICENSE +0 -0
- {pytonapi-0.4.9 → pytonapi-2.0.0}/pytonapi/py.typed +0 -0
- {pytonapi-0.4.9 → pytonapi-2.0.0}/pytonapi.egg-info/dependency_links.txt +0 -0
- {pytonapi-0.4.9 → pytonapi-2.0.0}/pytonapi.egg-info/top_level.txt +0 -0
- {pytonapi-0.4.9 → pytonapi-2.0.0}/setup.cfg +0 -0
pytonapi-2.0.0/PKG-INFO
ADDED
|
@@ -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
|
+
[](https://ton.org)
|
|
33
|
+

|
|
34
|
+
[](https://pypi.python.org/pypi/pytonapi)
|
|
35
|
+
[](https://github.com/nessshon/pytonapi/blob/main/LICENSE)
|
|
36
|
+
[](https://tonviewer.com/UQCZq3_Vd21-4y4m7Wc-ej9NFOhh_qvdfAkAYAOHoQ__Ness)
|
|
37
|
+
|
|
38
|
+

|
|
39
|
+
|
|
40
|
+

|
|
41
|
+

|
|
42
|
+

|
|
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).
|
pytonapi-2.0.0/README.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# 📦 PyTONAPI
|
|
2
|
+
|
|
3
|
+
[](https://ton.org)
|
|
4
|
+

|
|
5
|
+
[](https://pypi.python.org/pypi/pytonapi)
|
|
6
|
+
[](https://github.com/nessshon/pytonapi/blob/main/LICENSE)
|
|
7
|
+
[](https://tonviewer.com/UQCZq3_Vd21-4y4m7Wc-ej9NFOhh_qvdfAkAYAOHoQ__Ness)
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+

|
|
12
|
+

|
|
13
|
+

|
|
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]
|