├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml ├── release.yaml └── workflows │ ├── ci.yml │ ├── conventional-label.yaml │ └── publish.yml ├── .gitignore ├── .pre-commit-config.yaml ├── LICENSE ├── Makefile ├── README.md ├── docs ├── cover.png └── webhook-listener.png ├── poetry.lock ├── pyproject.toml ├── requirements.txt ├── telepay ├── __init__.py └── v1 │ ├── __init__.py │ ├── _async │ ├── __init__.py │ └── client.py │ ├── _sync │ ├── __init__.py │ └── client.py │ ├── auth.py │ ├── errors.py │ ├── http_clients.py │ ├── models │ ├── __init__.py │ ├── account.py │ ├── assets.py │ ├── invoice.py │ ├── wallets.py │ └── webhooks.py │ ├── utils.py │ └── webhooks.py └── tests ├── __init__.py └── v1 ├── __init__.py ├── _async ├── __init__.py └── test_client.py ├── _sync ├── __init__.py └── test_client.py └── utils.py /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | 1. Go to '...' 17 | 2. Click on '....' 18 | 3. Scroll down to '....' 19 | 4. See error 20 | 21 | **Expected behavior** 22 | A clear and concise description of what you expected to happen. 23 | 24 | **Screenshots** 25 | If applicable, add screenshots to help explain your problem. 26 | 27 | **Desktop (please complete the following information):** 28 | 29 | * OS: [e.g. iOS] 30 | * Browser [e.g. chrome, safari] 31 | * Version [e.g. 22] 32 | 33 | **Smartphone (please complete the following information):** 34 | 35 | * Device: [e.g. iPhone6] 36 | * OS: [e.g. iOS8.1] 37 | * Browser [e.g. stock browser, safari] 38 | * Version [e.g. 22] 39 | 40 | **Additional context** 41 | Add any other context about the problem here. 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: 'enhancement' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "pip" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | target-branch: "develop" 8 | -------------------------------------------------------------------------------- /.github/release.yaml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | labels: 4 | - ignore-for-release 5 | authors: 6 | - octocat 7 | categories: 8 | - title: Breaking Changes 🛠 9 | labels: 10 | - breaking 11 | - title: Exciting New Features 🎉 12 | labels: 13 | - feature 14 | - title: Fixes 🔧 15 | labels: 16 | - fix 17 | - title: Other Changes 18 | labels: 19 | - "*" 20 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [ push, pull_request ] 3 | jobs: 4 | CI: 5 | runs-on: ubuntu-latest 6 | strategy: 7 | max-parallel: 4 8 | matrix: 9 | python-version: ["3.10"] 10 | poetry-version: ["1.3.2"] 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | with: 15 | ref: ${{ github.event.pull_request.head.sha }} 16 | - name: Set up Python ${{ matrix.python-version }} 17 | uses: actions/setup-python@v4 18 | with: 19 | python-version: ${{ matrix.python-version }} 20 | - name: Set up Poetry 21 | uses: abatilo/actions-poetry@v2 22 | with: 23 | poetry-version: ${{ matrix.poetry-version }} 24 | - name: Run tests 25 | env: 26 | TELEPAY_SECRET_API_KEY: ${{ secrets.TELEPAY_SECRET_API_KEY }} 27 | run: make tests 28 | - name: Upload coverage 29 | uses: codecov/codecov-action@v1 30 | -------------------------------------------------------------------------------- /.github/workflows/conventional-label.yaml: -------------------------------------------------------------------------------- 1 | # Warning, do not check out untrusted code with 2 | # the pull_request_target event. 3 | on: 4 | pull_request_target: 5 | types: [ opened, edited ] 6 | name: conventional-release-labels 7 | jobs: 8 | label: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: bcoe/conventional-release-labels@v1 12 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Publish 3 | on: 4 | push: 5 | tags: v*.*.* 6 | jobs: 7 | Publish: 8 | runs-on: ubuntu-latest 9 | strategy: 10 | max-parallel: 1 11 | matrix: 12 | python-version: ["3.10"] 13 | poetry-version: ["1.3.2"] 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Set up Python 17 | uses: actions/setup-python@v4 18 | with: 19 | python-version: ${{ matrix.python-version }} 20 | - name: Set up Poetry 21 | uses: abatilo/actions-poetry@v2 22 | with: 23 | poetry-version: ${{ matrix.poetry-version }} 24 | - name: Run tests 25 | env: 26 | TELEPAY_SECRET_API_KEY: ${{ secrets.TELEPAY_SECRET_API_KEY }} 27 | run: make tests 28 | - name: Run build 29 | run: poetry build 30 | - name: Publish in GitHub Releases 31 | uses: svenstaro/upload-release-action@v2 32 | with: 33 | repo_token: ${{ secrets.GITHUB_TOKEN }} 34 | file: dist/* 35 | tag: ${{ github.ref }} 36 | overwrite: true 37 | file_glob: true 38 | - name: Pyblish to PyPI 39 | uses: pypa/gh-action-pypi-publish@release/v1 40 | with: 41 | password: ${{ secrets.PYPI_API_TOKEN }} 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # Jetbrains IDE 132 | .idea/ -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v4.0.1 5 | hooks: 6 | - id: trailing-whitespace 7 | - id: check-added-large-files 8 | - id: mixed-line-ending 9 | args: ['--fix=lf'] 10 | 11 | - repo: https://github.com/pre-commit/mirrors-isort 12 | rev: v5.8.0 13 | hooks: 14 | - id: isort 15 | args: ['--multi-line=3', '--trailing-comma', '--force-grid-wrap=0', '--use-parentheses', '--line-width=88'] 16 | 17 | - repo: https://github.com/humitos/mirrors-autoflake.git 18 | rev: v1.1 19 | hooks: 20 | - id: autoflake 21 | args: ['--in-place', '--remove-all-unused-imports'] 22 | 23 | # - repo: https://github.com/ambv/black 24 | # rev: 21.5b1 25 | # hooks: 26 | # - id: black 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 TelePay.cash 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | install: 2 | poetry install 3 | 4 | tests: install 5 | poetry run flake8 . --count --show-source --statistics --max-line-length=88 --extend-ignore=E203 --exclude=.venv 6 | poetry run black . --check 7 | poetry run isort . --profile=black 8 | poetry run pre-commit run --all-files 9 | poetry run pytest -v 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python SDK for the TelePay API 2 | 3 | ![TelePay Python](https://github.com/TelePay-cash/telepay-python/blob/main/docs/cover.png?raw=true) 4 | 5 | Official TelePay client library for the Python language, so you can easily process cryptocurrency payments using the REST API. 6 | 7 | [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) 8 | [![Test](https://github.com/telepay-cash/telepay-python/workflows/CI/badge.svg)](https://github.com/telepay-cash/telepay-python/actions?query=workflow%3ACI) 9 | [![Version](https://img.shields.io/pypi/v/telepay?color=%2334D058&label=Version&style=flat-square)](https://pypi.org/project/telepay) 10 | [![Last commit](https://img.shields.io/github/last-commit/telepay-cash/telepay-python.svg?style=flat-square)](https://github.com/telepay-cash/telepay-python/commits) 11 | [![GitHub commit activity](https://img.shields.io/github/commit-activity/m/telepay-cash/telepay-python?style=flat-square)](https://github.com/telepay-cash/telepay-python/commits) 12 | [![Github Stars](https://img.shields.io/github/stars/telepay-cash/telepay-python?style=flat-square&logo=github&)](https://github.com/telepay-cash/telepay-python/stargazers) 13 | [![Github Forks](https://img.shields.io/github/forks/telepay-cash/telepay-python?style=flat-square&logo=github)](https://github.com/telepay-cash/telepay-python/network/members) 14 | [![Github Watchers](https://img.shields.io/github/watchers/telepay-cash/telepay-python?style=flat-square&logo=github)](https://github.com/telepay-cash/telepay-python) 15 | [![GitHub contributors](https://img.shields.io/github/contributors/telepay-cash/telepay-python?label=code%20contributors&style=flat-square)](https://github.com/telepay-cash/telepay-python/graphs/contributors) 16 | [![Downloads](https://pepy.tech/badge/telepay?style=flat-square)](https://pepy.tech/project/telepay) 17 | [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg?style=flat-square)](https://github.com/psf/black) 18 | [![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat-square&labelColor=ef8336)](https://pycqa.github.io/isort/) 19 | [![Telegram](https://img.shields.io/badge/Telegram-2CA5E0?style=flat-squeare&logo=telegram&logoColor=white)](https://t.me/TelePayCash) 20 | [![Blog](https://img.shields.io/badge/RSS-FFA500?style=flat-square&logo=rss&logoColor=white)](https://blog.telepay.cash) 21 | 22 | ## Installation 23 | 24 | Install the package with pip: 25 | 26 | ```bash 27 | pip install telepay 28 | ``` 29 | 30 | Or using [Poetry](https://python-poetry.org/): 31 | 32 | ```bash 33 | poetry add telepay 34 | ``` 35 | 36 | ## Using the library 37 | 38 | Refer to the [TelePay Docs](https://telepay.readme.io) and follow the [first steps guide](https://telepay.readme.io/reference/first-steps), you'll get your TelePay account and API key. 39 | 40 | To make requests to the TelePay API, you need to import a client. We have two clients: 41 | * `TelePaySyncClient`: make requests synchronously. 42 | * `TelePayAsyncClient` make requests asynchronously. 43 | 44 | **Import and use the client** 45 | 46 | ```python 47 | from telepay.v1 import TelePaySyncClient, TelePayAsyncClient 48 | 49 | client = TelePaySyncClient(secret_api_key) 50 | client = TelePayAsyncClient(secret_api_key) 51 | ``` 52 | 53 | **Use the client as a context manager** 54 | 55 | We recommend using the client as a context manager, like this: 56 | 57 | ```python 58 | with TelePaySyncClient(secret_api_key) as client: 59 | # use the client 60 | ... 61 | ``` 62 | 63 | or 64 | 65 | ```python 66 | async with TelePayAsyncClient(secret_api_key) as client: 67 | # use the client 68 | ... 69 | ``` 70 | 71 | ## API endpoints 72 | 73 | The API endpoints are documented in the [TelePay documentation](https://telepay.readme.io/reference/endpoints), refer to that pages to know more about them. 74 | 75 | To manage the requests, if the client is async, you should use the `await` keyword, like this: 76 | 77 | ```python 78 | response = await client.method(...) 79 | ``` 80 | 81 | Where `method` is the endpoint method. 82 | 83 | **get_me** 84 | 85 | Info about the current merchant. [Read docs](https://telepay.readme.io/reference/getme) 86 | 87 | ```python 88 | account = client.get_me() 89 | ``` 90 | 91 | **get_balance** 92 | 93 | Get your merchant wallet assets with corresponding balance. [Read docs](https://telepay.readme.io/reference/getbalance) 94 | 95 | ```python 96 | wallets = client.get_balance() 97 | ``` 98 | 99 | Or get a specific wallet balance by specifying the `asset`, `blockchain` and `network`. 100 | 101 | ```python 102 | wallet = client.get_balance(asset='TON', blockchain='TON', network='network') 103 | ``` 104 | 105 | ### Assets 106 | 107 | **get_asset** 108 | 109 | Get asset details. [Read docs](https://telepay.readme.io/reference/getasset) 110 | 111 | ```python 112 | asset = client.get_asset(asset='TON', blockchain='TON') 113 | ``` 114 | 115 | **get_assets** 116 | 117 | Get assets suported by TelePay. [Read docs](https://telepay.readme.io/reference/getassets) 118 | 119 | ```python 120 | assets = client.get_assets() 121 | ``` 122 | 123 | ### Invoices 124 | 125 | **get_invoice** 126 | 127 | Get invoice details, by its number. [Read docs](https://telepay.readme.io/reference/getinvoice) 128 | 129 | ```python 130 | invoice = client.get_invoice(number) 131 | ``` 132 | 133 | **get_invoices** 134 | 135 | Get your merchant invoices. [Read docs](https://telepay.readme.io/reference/getinvoices) 136 | 137 | ```python 138 | invoices = client.get_invoices() 139 | ``` 140 | 141 | **create_invoice** 142 | 143 | Creates an invoice, associated to your merchant. [Read docs](https://telepay.readme.io/reference/createinvoice) 144 | 145 | ```python 146 | invoice = client.create_invoice( 147 | asset='TON', 148 | blockchain='TON', 149 | network='mainnet', 150 | amount=1, 151 | description='Product', 152 | metadata={ 153 | 'color': 'red', 154 | 'size': 'large', 155 | }, 156 | success_url='https://example.com/success', 157 | cancel_url='https://example.com/cancel', 158 | expires_at=30 159 | ) 160 | ``` 161 | 162 | **cancel_invoice** 163 | 164 | Cancel invoice, by its number. [Read docs](https://telepay.readme.io/reference/cancelinvoice) 165 | 166 | ```python 167 | invoice = client.cancel_invoice(number) 168 | ``` 169 | 170 | **delete_invoice** 171 | 172 | Delete invoice, by its number. [Read docs](https://telepay.readme.io/reference/deleteinvoice) 173 | 174 | ```python 175 | status = client.delete_invoice(number) 176 | ``` 177 | 178 | ### Transfers 179 | 180 | **transfer** 181 | 182 | Transfer funds between internal wallets. Off-chain operation. [Read docs](https://telepay.readme.io/reference/transfer) 183 | 184 | ```python 185 | status = client.transfer( 186 | asset='TON', 187 | blockchain='TON', 188 | network='mainnet', 189 | amount=1, 190 | username='test', 191 | message='Thanks' 192 | ) 193 | ``` 194 | 195 | ### Withdrawals 196 | 197 | **get_withdraw_minimum** 198 | 199 | Obtains minimum amount required to withdraw funds on a given asset. [Read docs](https://telepay.readme.io/reference/getwithdrawminimum) 200 | 201 | ```python 202 | minimum = client.get_withdraw_minimum( 203 | asset='TON', 204 | blockchain='TON', 205 | network='mainnet', 206 | ) 207 | ``` 208 | 209 | **get_withdraw_fee** 210 | 211 | Get estimated withdraw fee, composed of blockchain fee and processing fee. [Read docs](https://telepay.readme.io/reference/getwithdrawfee) 212 | 213 | ```python 214 | fees = client.get_withdraw_fee( 215 | to_address='EQCKYK7bYBt1t8UmdhImrbiSzC5ijfo_H3Zc_Hk8ksRpOkOk', 216 | asset='TON', 217 | blockchain='TON', 218 | network='mainnet', 219 | amount=1, 220 | message='test' 221 | ) 222 | ``` 223 | 224 | **withdraw** 225 | 226 | Withdraw funds from merchant wallet to external wallet. On-chain operation. [Read docs](https://telepay.readme.io/reference/withdraw) 227 | 228 | ```python 229 | status = client.withdraw( 230 | to_address='EQCKYK7bYBt1t8UmdhImrbiSzC5ijfo_H3Zc_Hk8ksRpOkOk', 231 | asset='TON', 232 | blockchain='TON', 233 | network='mainnet', 234 | amount=1, 235 | message='test' 236 | ) 237 | ``` 238 | 239 | ### Webhooks 240 | 241 | > Webhooks allows you to get updates delivered to your app server whenever we have events on our side. We will send a POST request over HTTPS, with serialized JSON data of the event, to the endpoint defined by you in the Developers > Webhooks section, in your merchant dashboard. [Read more in the docs](https://telepay.readme.io/reference/webhooks). 242 | 243 | **get_webhook** 244 | 245 | Get webhook details, by its id. [Read docs](https://telepay.readme.io/reference/getwebhook) 246 | 247 | ```python 248 | client.get_webhook(id) 249 | ``` 250 | 251 | **get_webhooks** 252 | 253 | Get your merchant webhooks. [Read docs](https://telepay.readme.io/reference/getwebhooks) 254 | 255 | ```python 256 | webhooks = client.get_webhooks() 257 | ``` 258 | 259 | **create_webhook** 260 | 261 | Create a webhook. [Read docs](https://telepay.readme.io/reference/createwebhook) 262 | 263 | ```python 264 | webhook = client.create_webhook( 265 | url='https://example.com', 266 | secret='hello', 267 | events=['all'], 268 | active=True 269 | ) 270 | ``` 271 | 272 | **update_webhook** 273 | 274 | Update a webhook. [Read docs](https://telepay.readme.io/reference/updatewebhook) 275 | 276 | ```python 277 | webhook = client.update_webhook( 278 | url='https://example.com', 279 | secret='hello', 280 | events=['invoice.completed'], 281 | active=True 282 | ) 283 | ``` 284 | 285 | **activate_webhook** 286 | 287 | Activate a webhook, by its id. [Read docs](https://telepay.readme.io/reference/activatewebhook) 288 | 289 | ```python 290 | client.activate_webhook(id) 291 | ``` 292 | 293 | **deactivate_webhook** 294 | 295 | Deactivate a webhook, by its id. [Read docs](https://telepay.readme.io/reference/deactivatewebhook) 296 | 297 | ```python 298 | client.deactivate_webhook(id) 299 | ``` 300 | 301 | **delete_webhook** 302 | 303 | Delete a webhook, by its id. [Read docs](https://telepay.readme.io/reference/deletewebhook) 304 | 305 | ```python 306 | client.deactivate_webhook(id) 307 | ``` 308 | 309 | ### Webhook utils 310 | 311 | The `telepay.v1.webhooks` module contains utilities to manage webhooks received on your side. 312 | 313 | * `get_signature(data, secret)`: Returns a webhook signature, used to verify data integrity of the webhook. This is optional, but highly recommended. 314 | * `TelePayWebhookListener`: A lightweight webhook listener, to receive webhooks from TelePay. You could build your own, like using django views, flask views, or any other web framework. Your choice. 315 | 316 | **Example using `TelePayWebhookListener`** 317 | 318 | ```python 319 | from telepay.v1.webhooks import TelePayWebhookListener 320 | 321 | def callback(headers, data): 322 | print("Executing callback...") 323 | # do your stuff here 324 | 325 | listener = TelePayWebhookListener( 326 | secret="SECRET", 327 | callback=callback, 328 | host="localhost", 329 | port=5000, 330 | url="/webhook", 331 | log_level="error", 332 | ) 333 | 334 | listener.listen() 335 | ``` 336 | 337 | Running the listener will output something like this: 338 | 339 | ![Webhook listener](https://github.com/TelePay-cash/telepay-python/blob/main/docs/webhook-listener.png?raw=true) 340 | 341 | Modify the listener parameters to your needs, knowing this: 342 | * `secret`: A secret token that your side knows and it's configured in the webhook definition, on the TelePay dashboard. 343 | * `callback`: The callback function that is called when new webhook arrives, receiving it's HTTP headers and data. 344 | * `host`: The host on which the listener will be running. 345 | * `port`: The port on which the listener will be exposed. 346 | * `url`: The webhook url, which is secret and should only be known by your app and TelePay. Otherwise, it could lead to security issues. 347 | * `log_level`: The listener logger level, like `"error"`, `"info"` or `"debug"`. 348 | 349 | ## Contributors ✨ 350 | 351 | The library is made by ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 352 | 353 | 354 | 355 | 356 | 357 | 358 | 366 | 374 | 382 | 383 |
359 | 360 |
361 | Carlos Lugones 362 |
363 |
364 | 💻 365 |
367 | 368 |
369 | Luis Diaz 370 |
371 |
372 | 💻 373 |
375 | 376 |
377 | Reinier Hernández 378 |
379 |
380 | 💻 381 |
384 | 385 | 386 | 387 | 388 | 389 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 390 | -------------------------------------------------------------------------------- /docs/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TelePay-cash/telepay-python/50d0cee6945bb3b389fa1745f135c992c681a7d8/docs/cover.png -------------------------------------------------------------------------------- /docs/webhook-listener.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TelePay-cash/telepay-python/50d0cee6945bb3b389fa1745f135c992c681a7d8/docs/webhook-listener.png -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "anyio" 5 | version = "3.6.2" 6 | description = "High level compatibility layer for multiple asynchronous event loop implementations" 7 | category = "main" 8 | optional = false 9 | python-versions = ">=3.6.2" 10 | files = [ 11 | {file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"}, 12 | {file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"}, 13 | ] 14 | 15 | [package.dependencies] 16 | idna = ">=2.8" 17 | sniffio = ">=1.1" 18 | trio = {version = ">=0.16,<0.22", optional = true, markers = "extra == \"trio\""} 19 | 20 | [package.extras] 21 | doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] 22 | test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"] 23 | trio = ["trio (>=0.16,<0.22)"] 24 | 25 | [[package]] 26 | name = "asgiref" 27 | version = "3.6.0" 28 | description = "ASGI specs, helper code, and adapters" 29 | category = "main" 30 | optional = false 31 | python-versions = ">=3.7" 32 | files = [ 33 | {file = "asgiref-3.6.0-py3-none-any.whl", hash = "sha256:71e68008da809b957b7ee4b43dbccff33d1b23519fb8344e33f049897077afac"}, 34 | {file = "asgiref-3.6.0.tar.gz", hash = "sha256:9567dfe7bd8d3c8c892227827c41cce860b368104c3431da67a0c5a65a949506"}, 35 | ] 36 | 37 | [package.extras] 38 | tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] 39 | 40 | [[package]] 41 | name = "async-generator" 42 | version = "1.10" 43 | description = "Async generators and context managers for Python 3.5+" 44 | category = "dev" 45 | optional = false 46 | python-versions = ">=3.5" 47 | files = [ 48 | {file = "async_generator-1.10-py3-none-any.whl", hash = "sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b"}, 49 | {file = "async_generator-1.10.tar.gz", hash = "sha256:6ebb3d106c12920aaae42ccb6f787ef5eefdcdd166ea3d628fa8476abe712144"}, 50 | ] 51 | 52 | [[package]] 53 | name = "atomicwrites" 54 | version = "1.4.1" 55 | description = "Atomic file writes." 56 | category = "dev" 57 | optional = false 58 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 59 | files = [ 60 | {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, 61 | ] 62 | 63 | [[package]] 64 | name = "attrs" 65 | version = "22.2.0" 66 | description = "Classes Without Boilerplate" 67 | category = "dev" 68 | optional = false 69 | python-versions = ">=3.6" 70 | files = [ 71 | {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, 72 | {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, 73 | ] 74 | 75 | [package.extras] 76 | cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] 77 | dev = ["attrs[docs,tests]"] 78 | docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] 79 | tests = ["attrs[tests-no-zope]", "zope.interface"] 80 | tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] 81 | 82 | [[package]] 83 | name = "black" 84 | version = "22.12.0" 85 | description = "The uncompromising code formatter." 86 | category = "dev" 87 | optional = false 88 | python-versions = ">=3.7" 89 | files = [ 90 | {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"}, 91 | {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"}, 92 | {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"}, 93 | {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"}, 94 | {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"}, 95 | {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"}, 96 | {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"}, 97 | {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"}, 98 | {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"}, 99 | {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"}, 100 | {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"}, 101 | {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"}, 102 | ] 103 | 104 | [package.dependencies] 105 | click = ">=8.0.0" 106 | mypy-extensions = ">=0.4.3" 107 | pathspec = ">=0.9.0" 108 | platformdirs = ">=2" 109 | tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} 110 | 111 | [package.extras] 112 | colorama = ["colorama (>=0.4.3)"] 113 | d = ["aiohttp (>=3.7.4)"] 114 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] 115 | uvloop = ["uvloop (>=0.15.2)"] 116 | 117 | [[package]] 118 | name = "certifi" 119 | version = "2022.12.7" 120 | description = "Python package for providing Mozilla's CA Bundle." 121 | category = "main" 122 | optional = false 123 | python-versions = ">=3.6" 124 | files = [ 125 | {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, 126 | {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, 127 | ] 128 | 129 | [[package]] 130 | name = "cffi" 131 | version = "1.15.1" 132 | description = "Foreign Function Interface for Python calling C code." 133 | category = "dev" 134 | optional = false 135 | python-versions = "*" 136 | files = [ 137 | {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, 138 | {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, 139 | {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, 140 | {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, 141 | {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, 142 | {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, 143 | {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, 144 | {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, 145 | {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, 146 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, 147 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, 148 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, 149 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, 150 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, 151 | {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, 152 | {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, 153 | {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, 154 | {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, 155 | {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, 156 | {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, 157 | {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, 158 | {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, 159 | {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, 160 | {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, 161 | {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, 162 | {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, 163 | {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, 164 | {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, 165 | {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, 166 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, 167 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, 168 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, 169 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, 170 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, 171 | {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, 172 | {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, 173 | {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, 174 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, 175 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, 176 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, 177 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, 178 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, 179 | {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, 180 | {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, 181 | {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, 182 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, 183 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, 184 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, 185 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, 186 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, 187 | {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, 188 | {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, 189 | {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, 190 | {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, 191 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, 192 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, 193 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, 194 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, 195 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, 196 | {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, 197 | {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, 198 | {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, 199 | {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, 200 | {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, 201 | ] 202 | 203 | [package.dependencies] 204 | pycparser = "*" 205 | 206 | [[package]] 207 | name = "cfgv" 208 | version = "3.3.1" 209 | description = "Validate configuration and produce human readable error messages." 210 | category = "dev" 211 | optional = false 212 | python-versions = ">=3.6.1" 213 | files = [ 214 | {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, 215 | {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, 216 | ] 217 | 218 | [[package]] 219 | name = "click" 220 | version = "8.1.3" 221 | description = "Composable command line interface toolkit" 222 | category = "main" 223 | optional = false 224 | python-versions = ">=3.7" 225 | files = [ 226 | {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, 227 | {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, 228 | ] 229 | 230 | [package.dependencies] 231 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 232 | 233 | [[package]] 234 | name = "colorama" 235 | version = "0.4.6" 236 | description = "Cross-platform colored terminal text." 237 | category = "main" 238 | optional = false 239 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 240 | files = [ 241 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 242 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 243 | ] 244 | 245 | [[package]] 246 | name = "distlib" 247 | version = "0.3.6" 248 | description = "Distribution utilities" 249 | category = "dev" 250 | optional = false 251 | python-versions = "*" 252 | files = [ 253 | {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, 254 | {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, 255 | ] 256 | 257 | [[package]] 258 | name = "fastapi" 259 | version = "0.75.2" 260 | description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" 261 | category = "main" 262 | optional = false 263 | python-versions = ">=3.6.1" 264 | files = [ 265 | {file = "fastapi-0.75.2-py3-none-any.whl", hash = "sha256:a70d31f4249b6b42dbe267667d22f83af645b2d857876c97f83ca9573215784f"}, 266 | {file = "fastapi-0.75.2.tar.gz", hash = "sha256:b5dac161ee19d33346040d3f44d8b7a9ac09b37df9efff95891f5e7641fa482f"}, 267 | ] 268 | 269 | [package.dependencies] 270 | pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0" 271 | starlette = "0.17.1" 272 | 273 | [package.extras] 274 | all = ["email_validator (>=1.1.1,<2.0.0)", "itsdangerous (>=1.1.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "orjson (>=3.2.1,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "pyyaml (>=5.3.1,<7.0.0)", "requests (>=2.24.0,<3.0.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)"] 275 | dev = ["autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "python-jose[cryptography] (>=3.3.0,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)"] 276 | doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "typer (>=0.4.1,<0.5.0)"] 277 | test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==22.3.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "email_validator (>=1.1.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "orjson (>=3.2.1,<4.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "requests (>=2.24.0,<3.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "types-dataclasses (==0.6.5)", "types-orjson (==3.6.2)", "types-ujson (==4.2.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)"] 278 | 279 | [[package]] 280 | name = "filelock" 281 | version = "3.9.0" 282 | description = "A platform independent file lock." 283 | category = "dev" 284 | optional = false 285 | python-versions = ">=3.7" 286 | files = [ 287 | {file = "filelock-3.9.0-py3-none-any.whl", hash = "sha256:f58d535af89bb9ad5cd4df046f741f8553a418c01a7856bf0d173bbc9f6bd16d"}, 288 | {file = "filelock-3.9.0.tar.gz", hash = "sha256:7b319f24340b51f55a2bf7a12ac0755a9b03e718311dac567a0f4f7fabd2f5de"}, 289 | ] 290 | 291 | [package.extras] 292 | docs = ["furo (>=2022.12.7)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] 293 | testing = ["covdefaults (>=2.2.2)", "coverage (>=7.0.1)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"] 294 | 295 | [[package]] 296 | name = "flake8" 297 | version = "4.0.1" 298 | description = "the modular source code checker: pep8 pyflakes and co" 299 | category = "dev" 300 | optional = false 301 | python-versions = ">=3.6" 302 | files = [ 303 | {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, 304 | {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, 305 | ] 306 | 307 | [package.dependencies] 308 | mccabe = ">=0.6.0,<0.7.0" 309 | pycodestyle = ">=2.8.0,<2.9.0" 310 | pyflakes = ">=2.4.0,<2.5.0" 311 | 312 | [[package]] 313 | name = "h11" 314 | version = "0.14.0" 315 | description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" 316 | category = "main" 317 | optional = false 318 | python-versions = ">=3.7" 319 | files = [ 320 | {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, 321 | {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, 322 | ] 323 | 324 | [[package]] 325 | name = "httpcore" 326 | version = "0.16.3" 327 | description = "A minimal low-level HTTP client." 328 | category = "main" 329 | optional = false 330 | python-versions = ">=3.7" 331 | files = [ 332 | {file = "httpcore-0.16.3-py3-none-any.whl", hash = "sha256:da1fb708784a938aa084bde4feb8317056c55037247c787bd7e19eb2c2949dc0"}, 333 | {file = "httpcore-0.16.3.tar.gz", hash = "sha256:c5d6f04e2fc530f39e0c077e6a30caa53f1451096120f1f38b954afd0b17c0cb"}, 334 | ] 335 | 336 | [package.dependencies] 337 | anyio = ">=3.0,<5.0" 338 | certifi = "*" 339 | h11 = ">=0.13,<0.15" 340 | sniffio = ">=1.0.0,<2.0.0" 341 | 342 | [package.extras] 343 | http2 = ["h2 (>=3,<5)"] 344 | socks = ["socksio (>=1.0.0,<2.0.0)"] 345 | 346 | [[package]] 347 | name = "httpx" 348 | version = "0.23.3" 349 | description = "The next generation HTTP client." 350 | category = "main" 351 | optional = false 352 | python-versions = ">=3.7" 353 | files = [ 354 | {file = "httpx-0.23.3-py3-none-any.whl", hash = "sha256:a211fcce9b1254ea24f0cd6af9869b3d29aba40154e947d2a07bb499b3e310d6"}, 355 | {file = "httpx-0.23.3.tar.gz", hash = "sha256:9818458eb565bb54898ccb9b8b251a28785dd4a55afbc23d0eb410754fe7d0f9"}, 356 | ] 357 | 358 | [package.dependencies] 359 | certifi = "*" 360 | httpcore = ">=0.15.0,<0.17.0" 361 | rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} 362 | sniffio = "*" 363 | 364 | [package.extras] 365 | brotli = ["brotli", "brotlicffi"] 366 | cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"] 367 | http2 = ["h2 (>=3,<5)"] 368 | socks = ["socksio (>=1.0.0,<2.0.0)"] 369 | 370 | [[package]] 371 | name = "identify" 372 | version = "2.5.15" 373 | description = "File identification library for Python" 374 | category = "dev" 375 | optional = false 376 | python-versions = ">=3.7" 377 | files = [ 378 | {file = "identify-2.5.15-py2.py3-none-any.whl", hash = "sha256:1f4b36c5f50f3f950864b2a047308743f064eaa6f6645da5e5c780d1c7125487"}, 379 | {file = "identify-2.5.15.tar.gz", hash = "sha256:c22aa206f47cc40486ecf585d27ad5f40adbfc494a3fa41dc3ed0499a23b123f"}, 380 | ] 381 | 382 | [package.extras] 383 | license = ["ukkonen"] 384 | 385 | [[package]] 386 | name = "idna" 387 | version = "3.4" 388 | description = "Internationalized Domain Names in Applications (IDNA)" 389 | category = "main" 390 | optional = false 391 | python-versions = ">=3.5" 392 | files = [ 393 | {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, 394 | {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, 395 | ] 396 | 397 | [[package]] 398 | name = "iniconfig" 399 | version = "2.0.0" 400 | description = "brain-dead simple config-ini parsing" 401 | category = "dev" 402 | optional = false 403 | python-versions = ">=3.7" 404 | files = [ 405 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 406 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 407 | ] 408 | 409 | [[package]] 410 | name = "isort" 411 | version = "5.11.4" 412 | description = "A Python utility / library to sort Python imports." 413 | category = "dev" 414 | optional = false 415 | python-versions = ">=3.7.0" 416 | files = [ 417 | {file = "isort-5.11.4-py3-none-any.whl", hash = "sha256:c033fd0edb91000a7f09527fe5c75321878f98322a77ddcc81adbd83724afb7b"}, 418 | {file = "isort-5.11.4.tar.gz", hash = "sha256:6db30c5ded9815d813932c04c2f85a360bcdd35fed496f4d8f35495ef0a261b6"}, 419 | ] 420 | 421 | [package.extras] 422 | colors = ["colorama (>=0.4.3,<0.5.0)"] 423 | pipfile-deprecated-finder = ["pipreqs", "requirementslib"] 424 | plugins = ["setuptools"] 425 | requirements-deprecated-finder = ["pip-api", "pipreqs"] 426 | 427 | [[package]] 428 | name = "mccabe" 429 | version = "0.6.1" 430 | description = "McCabe checker, plugin for flake8" 431 | category = "dev" 432 | optional = false 433 | python-versions = "*" 434 | files = [ 435 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 436 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 437 | ] 438 | 439 | [[package]] 440 | name = "mypy-extensions" 441 | version = "0.4.3" 442 | description = "Experimental type system extensions for programs checked with the mypy typechecker." 443 | category = "dev" 444 | optional = false 445 | python-versions = "*" 446 | files = [ 447 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 448 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 449 | ] 450 | 451 | [[package]] 452 | name = "nodeenv" 453 | version = "1.7.0" 454 | description = "Node.js virtual environment builder" 455 | category = "dev" 456 | optional = false 457 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" 458 | files = [ 459 | {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, 460 | {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, 461 | ] 462 | 463 | [package.dependencies] 464 | setuptools = "*" 465 | 466 | [[package]] 467 | name = "outcome" 468 | version = "1.2.0" 469 | description = "Capture the outcome of Python function calls." 470 | category = "dev" 471 | optional = false 472 | python-versions = ">=3.7" 473 | files = [ 474 | {file = "outcome-1.2.0-py2.py3-none-any.whl", hash = "sha256:c4ab89a56575d6d38a05aa16daeaa333109c1f96167aba8901ab18b6b5e0f7f5"}, 475 | {file = "outcome-1.2.0.tar.gz", hash = "sha256:6f82bd3de45da303cf1f771ecafa1633750a358436a8bb60e06a1ceb745d2672"}, 476 | ] 477 | 478 | [package.dependencies] 479 | attrs = ">=19.2.0" 480 | 481 | [[package]] 482 | name = "packaging" 483 | version = "23.0" 484 | description = "Core utilities for Python packages" 485 | category = "dev" 486 | optional = false 487 | python-versions = ">=3.7" 488 | files = [ 489 | {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, 490 | {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, 491 | ] 492 | 493 | [[package]] 494 | name = "pathspec" 495 | version = "0.10.3" 496 | description = "Utility library for gitignore style pattern matching of file paths." 497 | category = "dev" 498 | optional = false 499 | python-versions = ">=3.7" 500 | files = [ 501 | {file = "pathspec-0.10.3-py3-none-any.whl", hash = "sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6"}, 502 | {file = "pathspec-0.10.3.tar.gz", hash = "sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6"}, 503 | ] 504 | 505 | [[package]] 506 | name = "platformdirs" 507 | version = "2.6.2" 508 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 509 | category = "dev" 510 | optional = false 511 | python-versions = ">=3.7" 512 | files = [ 513 | {file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"}, 514 | {file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"}, 515 | ] 516 | 517 | [package.extras] 518 | docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] 519 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] 520 | 521 | [[package]] 522 | name = "pluggy" 523 | version = "1.0.0" 524 | description = "plugin and hook calling mechanisms for python" 525 | category = "dev" 526 | optional = false 527 | python-versions = ">=3.6" 528 | files = [ 529 | {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, 530 | {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, 531 | ] 532 | 533 | [package.extras] 534 | dev = ["pre-commit", "tox"] 535 | testing = ["pytest", "pytest-benchmark"] 536 | 537 | [[package]] 538 | name = "pre-commit" 539 | version = "2.21.0" 540 | description = "A framework for managing and maintaining multi-language pre-commit hooks." 541 | category = "dev" 542 | optional = false 543 | python-versions = ">=3.7" 544 | files = [ 545 | {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, 546 | {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, 547 | ] 548 | 549 | [package.dependencies] 550 | cfgv = ">=2.0.0" 551 | identify = ">=1.0.0" 552 | nodeenv = ">=0.11.1" 553 | pyyaml = ">=5.1" 554 | virtualenv = ">=20.10.0" 555 | 556 | [[package]] 557 | name = "py" 558 | version = "1.11.0" 559 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 560 | category = "dev" 561 | optional = false 562 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 563 | files = [ 564 | {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, 565 | {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, 566 | ] 567 | 568 | [[package]] 569 | name = "pycodestyle" 570 | version = "2.8.0" 571 | description = "Python style guide checker" 572 | category = "dev" 573 | optional = false 574 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 575 | files = [ 576 | {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, 577 | {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, 578 | ] 579 | 580 | [[package]] 581 | name = "pycparser" 582 | version = "2.21" 583 | description = "C parser in Python" 584 | category = "dev" 585 | optional = false 586 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 587 | files = [ 588 | {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, 589 | {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, 590 | ] 591 | 592 | [[package]] 593 | name = "pydantic" 594 | version = "1.10.4" 595 | description = "Data validation and settings management using python type hints" 596 | category = "main" 597 | optional = false 598 | python-versions = ">=3.7" 599 | files = [ 600 | {file = "pydantic-1.10.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5635de53e6686fe7a44b5cf25fcc419a0d5e5c1a1efe73d49d48fe7586db854"}, 601 | {file = "pydantic-1.10.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6dc1cc241440ed7ca9ab59d9929075445da6b7c94ced281b3dd4cfe6c8cff817"}, 602 | {file = "pydantic-1.10.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51bdeb10d2db0f288e71d49c9cefa609bca271720ecd0c58009bd7504a0c464c"}, 603 | {file = "pydantic-1.10.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78cec42b95dbb500a1f7120bdf95c401f6abb616bbe8785ef09887306792e66e"}, 604 | {file = "pydantic-1.10.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8775d4ef5e7299a2f4699501077a0defdaac5b6c4321173bcb0f3c496fbadf85"}, 605 | {file = "pydantic-1.10.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:572066051eeac73d23f95ba9a71349c42a3e05999d0ee1572b7860235b850cc6"}, 606 | {file = "pydantic-1.10.4-cp310-cp310-win_amd64.whl", hash = "sha256:7feb6a2d401f4d6863050f58325b8d99c1e56f4512d98b11ac64ad1751dc647d"}, 607 | {file = "pydantic-1.10.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39f4a73e5342b25c2959529f07f026ef58147249f9b7431e1ba8414a36761f53"}, 608 | {file = "pydantic-1.10.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:983e720704431a6573d626b00662eb78a07148c9115129f9b4351091ec95ecc3"}, 609 | {file = "pydantic-1.10.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75d52162fe6b2b55964fbb0af2ee58e99791a3138588c482572bb6087953113a"}, 610 | {file = "pydantic-1.10.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fdf8d759ef326962b4678d89e275ffc55b7ce59d917d9f72233762061fd04a2d"}, 611 | {file = "pydantic-1.10.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05a81b006be15655b2a1bae5faa4280cf7c81d0e09fcb49b342ebf826abe5a72"}, 612 | {file = "pydantic-1.10.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d88c4c0e5c5dfd05092a4b271282ef0588e5f4aaf345778056fc5259ba098857"}, 613 | {file = "pydantic-1.10.4-cp311-cp311-win_amd64.whl", hash = "sha256:6a05a9db1ef5be0fe63e988f9617ca2551013f55000289c671f71ec16f4985e3"}, 614 | {file = "pydantic-1.10.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:887ca463c3bc47103c123bc06919c86720e80e1214aab79e9b779cda0ff92a00"}, 615 | {file = "pydantic-1.10.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdf88ab63c3ee282c76d652fc86518aacb737ff35796023fae56a65ced1a5978"}, 616 | {file = "pydantic-1.10.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a48f1953c4a1d9bd0b5167ac50da9a79f6072c63c4cef4cf2a3736994903583e"}, 617 | {file = "pydantic-1.10.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a9f2de23bec87ff306aef658384b02aa7c32389766af3c5dee9ce33e80222dfa"}, 618 | {file = "pydantic-1.10.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:cd8702c5142afda03dc2b1ee6bc358b62b3735b2cce53fc77b31ca9f728e4bc8"}, 619 | {file = "pydantic-1.10.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6e7124d6855b2780611d9f5e1e145e86667eaa3bd9459192c8dc1a097f5e9903"}, 620 | {file = "pydantic-1.10.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b53e1d41e97063d51a02821b80538053ee4608b9a181c1005441f1673c55423"}, 621 | {file = "pydantic-1.10.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:55b1625899acd33229c4352ce0ae54038529b412bd51c4915349b49ca575258f"}, 622 | {file = "pydantic-1.10.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:301d626a59edbe5dfb48fcae245896379a450d04baeed50ef40d8199f2733b06"}, 623 | {file = "pydantic-1.10.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6f9d649892a6f54a39ed56b8dfd5e08b5f3be5f893da430bed76975f3735d15"}, 624 | {file = "pydantic-1.10.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d7b5a3821225f5c43496c324b0d6875fde910a1c2933d726a743ce328fbb2a8c"}, 625 | {file = "pydantic-1.10.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f2f7eb6273dd12472d7f218e1fef6f7c7c2f00ac2e1ecde4db8824c457300416"}, 626 | {file = "pydantic-1.10.4-cp38-cp38-win_amd64.whl", hash = "sha256:4b05697738e7d2040696b0a66d9f0a10bec0efa1883ca75ee9e55baf511909d6"}, 627 | {file = "pydantic-1.10.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a9a6747cac06c2beb466064dda999a13176b23535e4c496c9d48e6406f92d42d"}, 628 | {file = "pydantic-1.10.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eb992a1ef739cc7b543576337bebfc62c0e6567434e522e97291b251a41dad7f"}, 629 | {file = "pydantic-1.10.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:990406d226dea0e8f25f643b370224771878142155b879784ce89f633541a024"}, 630 | {file = "pydantic-1.10.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e82a6d37a95e0b1b42b82ab340ada3963aea1317fd7f888bb6b9dfbf4fff57c"}, 631 | {file = "pydantic-1.10.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9193d4f4ee8feca58bc56c8306bcb820f5c7905fd919e0750acdeeeef0615b28"}, 632 | {file = "pydantic-1.10.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2b3ce5f16deb45c472dde1a0ee05619298c864a20cded09c4edd820e1454129f"}, 633 | {file = "pydantic-1.10.4-cp39-cp39-win_amd64.whl", hash = "sha256:9cbdc268a62d9a98c56e2452d6c41c0263d64a2009aac69246486f01b4f594c4"}, 634 | {file = "pydantic-1.10.4-py3-none-any.whl", hash = "sha256:4948f264678c703f3877d1c8877c4e3b2e12e549c57795107f08cf70c6ec7774"}, 635 | {file = "pydantic-1.10.4.tar.gz", hash = "sha256:b9a3859f24eb4e097502a3be1fb4b2abb79b6103dd9e2e0edb70613a4459a648"}, 636 | ] 637 | 638 | [package.dependencies] 639 | typing-extensions = ">=4.2.0" 640 | 641 | [package.extras] 642 | dotenv = ["python-dotenv (>=0.10.4)"] 643 | email = ["email-validator (>=1.0.3)"] 644 | 645 | [[package]] 646 | name = "pyflakes" 647 | version = "2.4.0" 648 | description = "passive checker of Python programs" 649 | category = "dev" 650 | optional = false 651 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 652 | files = [ 653 | {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, 654 | {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, 655 | ] 656 | 657 | [[package]] 658 | name = "pytest" 659 | version = "6.2.5" 660 | description = "pytest: simple powerful testing with Python" 661 | category = "dev" 662 | optional = false 663 | python-versions = ">=3.6" 664 | files = [ 665 | {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, 666 | {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, 667 | ] 668 | 669 | [package.dependencies] 670 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 671 | attrs = ">=19.2.0" 672 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 673 | iniconfig = "*" 674 | packaging = "*" 675 | pluggy = ">=0.12,<2.0" 676 | py = ">=1.8.2" 677 | toml = "*" 678 | 679 | [package.extras] 680 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 681 | 682 | [[package]] 683 | name = "python-dotenv" 684 | version = "0.20.0" 685 | description = "Read key-value pairs from a .env file and set them as environment variables" 686 | category = "main" 687 | optional = false 688 | python-versions = ">=3.5" 689 | files = [ 690 | {file = "python-dotenv-0.20.0.tar.gz", hash = "sha256:b7e3b04a59693c42c36f9ab1cc2acc46fa5df8c78e178fc33a8d4cd05c8d498f"}, 691 | {file = "python_dotenv-0.20.0-py3-none-any.whl", hash = "sha256:d92a187be61fe482e4fd675b6d52200e7be63a12b724abbf931a40ce4fa92938"}, 692 | ] 693 | 694 | [package.extras] 695 | cli = ["click (>=5.0)"] 696 | 697 | [[package]] 698 | name = "pyyaml" 699 | version = "6.0" 700 | description = "YAML parser and emitter for Python" 701 | category = "dev" 702 | optional = false 703 | python-versions = ">=3.6" 704 | files = [ 705 | {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, 706 | {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, 707 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, 708 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, 709 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, 710 | {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, 711 | {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, 712 | {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, 713 | {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, 714 | {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, 715 | {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, 716 | {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, 717 | {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, 718 | {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, 719 | {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, 720 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, 721 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, 722 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, 723 | {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, 724 | {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, 725 | {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, 726 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, 727 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, 728 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, 729 | {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, 730 | {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, 731 | {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, 732 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, 733 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, 734 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, 735 | {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, 736 | {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, 737 | {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, 738 | {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, 739 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, 740 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, 741 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, 742 | {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, 743 | {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, 744 | {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, 745 | ] 746 | 747 | [[package]] 748 | name = "rfc3986" 749 | version = "1.5.0" 750 | description = "Validating URI References per RFC 3986" 751 | category = "main" 752 | optional = false 753 | python-versions = "*" 754 | files = [ 755 | {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, 756 | {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, 757 | ] 758 | 759 | [package.dependencies] 760 | idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} 761 | 762 | [package.extras] 763 | idna2008 = ["idna"] 764 | 765 | [[package]] 766 | name = "setuptools" 767 | version = "66.1.1" 768 | description = "Easily download, build, install, upgrade, and uninstall Python packages" 769 | category = "dev" 770 | optional = false 771 | python-versions = ">=3.7" 772 | files = [ 773 | {file = "setuptools-66.1.1-py3-none-any.whl", hash = "sha256:6f590d76b713d5de4e49fe4fbca24474469f53c83632d5d0fd056f7ff7e8112b"}, 774 | {file = "setuptools-66.1.1.tar.gz", hash = "sha256:ac4008d396bc9cd983ea483cb7139c0240a07bbc74ffb6232fceffedc6cf03a8"}, 775 | ] 776 | 777 | [package.extras] 778 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] 779 | testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] 780 | testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] 781 | 782 | [[package]] 783 | name = "sniffio" 784 | version = "1.3.0" 785 | description = "Sniff out which async library your code is running under" 786 | category = "main" 787 | optional = false 788 | python-versions = ">=3.7" 789 | files = [ 790 | {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, 791 | {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, 792 | ] 793 | 794 | [[package]] 795 | name = "sortedcontainers" 796 | version = "2.4.0" 797 | description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" 798 | category = "dev" 799 | optional = false 800 | python-versions = "*" 801 | files = [ 802 | {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, 803 | {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, 804 | ] 805 | 806 | [[package]] 807 | name = "starlette" 808 | version = "0.17.1" 809 | description = "The little ASGI library that shines." 810 | category = "main" 811 | optional = false 812 | python-versions = ">=3.6" 813 | files = [ 814 | {file = "starlette-0.17.1-py3-none-any.whl", hash = "sha256:26a18cbda5e6b651c964c12c88b36d9898481cd428ed6e063f5f29c418f73050"}, 815 | {file = "starlette-0.17.1.tar.gz", hash = "sha256:57eab3cc975a28af62f6faec94d355a410634940f10b30d68d31cb5ec1b44ae8"}, 816 | ] 817 | 818 | [package.dependencies] 819 | anyio = ">=3.0.0,<4" 820 | 821 | [package.extras] 822 | full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"] 823 | 824 | [[package]] 825 | name = "toml" 826 | version = "0.10.2" 827 | description = "Python Library for Tom's Obvious, Minimal Language" 828 | category = "dev" 829 | optional = false 830 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 831 | files = [ 832 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 833 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 834 | ] 835 | 836 | [[package]] 837 | name = "tomli" 838 | version = "2.0.1" 839 | description = "A lil' TOML parser" 840 | category = "dev" 841 | optional = false 842 | python-versions = ">=3.7" 843 | files = [ 844 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 845 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 846 | ] 847 | 848 | [[package]] 849 | name = "trio" 850 | version = "0.21.0" 851 | description = "A friendly Python library for async concurrency and I/O" 852 | category = "dev" 853 | optional = false 854 | python-versions = ">=3.7" 855 | files = [ 856 | {file = "trio-0.21.0-py3-none-any.whl", hash = "sha256:4dc0bf9d5cc78767fc4516325b6d80cc0968705a31d0eec2ecd7cdda466265b0"}, 857 | {file = "trio-0.21.0.tar.gz", hash = "sha256:523f39b7b69eef73501cebfe1aafd400a9aad5b03543a0eded52952488ff1c13"}, 858 | ] 859 | 860 | [package.dependencies] 861 | async-generator = ">=1.9" 862 | attrs = ">=19.2.0" 863 | cffi = {version = ">=1.14", markers = "os_name == \"nt\" and implementation_name != \"pypy\""} 864 | idna = "*" 865 | outcome = "*" 866 | sniffio = "*" 867 | sortedcontainers = "*" 868 | 869 | [[package]] 870 | name = "typing-extensions" 871 | version = "4.4.0" 872 | description = "Backported and Experimental Type Hints for Python 3.7+" 873 | category = "main" 874 | optional = false 875 | python-versions = ">=3.7" 876 | files = [ 877 | {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, 878 | {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, 879 | ] 880 | 881 | [[package]] 882 | name = "uvicorn" 883 | version = "0.17.6" 884 | description = "The lightning-fast ASGI server." 885 | category = "main" 886 | optional = false 887 | python-versions = ">=3.7" 888 | files = [ 889 | {file = "uvicorn-0.17.6-py3-none-any.whl", hash = "sha256:19e2a0e96c9ac5581c01eb1a79a7d2f72bb479691acd2b8921fce48ed5b961a6"}, 890 | {file = "uvicorn-0.17.6.tar.gz", hash = "sha256:5180f9d059611747d841a4a4c4ab675edf54c8489e97f96d0583ee90ac3bfc23"}, 891 | ] 892 | 893 | [package.dependencies] 894 | asgiref = ">=3.4.0" 895 | click = ">=7.0" 896 | h11 = ">=0.8" 897 | 898 | [package.extras] 899 | standard = ["PyYAML (>=5.1)", "colorama (>=0.4)", "httptools (>=0.4.0)", "python-dotenv (>=0.13)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchgod (>=0.6)", "websockets (>=10.0)"] 900 | 901 | [[package]] 902 | name = "virtualenv" 903 | version = "20.17.1" 904 | description = "Virtual Python Environment builder" 905 | category = "dev" 906 | optional = false 907 | python-versions = ">=3.6" 908 | files = [ 909 | {file = "virtualenv-20.17.1-py3-none-any.whl", hash = "sha256:ce3b1684d6e1a20a3e5ed36795a97dfc6af29bc3970ca8dab93e11ac6094b3c4"}, 910 | {file = "virtualenv-20.17.1.tar.gz", hash = "sha256:f8b927684efc6f1cc206c9db297a570ab9ad0e51c16fa9e45487d36d1905c058"}, 911 | ] 912 | 913 | [package.dependencies] 914 | distlib = ">=0.3.6,<1" 915 | filelock = ">=3.4.1,<4" 916 | platformdirs = ">=2.4,<3" 917 | 918 | [package.extras] 919 | docs = ["proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-argparse (>=0.3.2)", "sphinx-rtd-theme (>=1)", "towncrier (>=22.8)"] 920 | testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"] 921 | 922 | [metadata] 923 | lock-version = "2.0" 924 | python-versions = "^3.10" 925 | content-hash = "e98e6dde7a4d27de1250c557b28769d3b8fe88688c25c2c149088924dcd32463" 926 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "telepay" 3 | version = "1.1.0" 4 | description = "Python SDK for the TelePay API" 5 | authors = ["Carlos Lugones", "Reinier Hernández", "Luis Diaz"] 6 | license = "MIT" 7 | readme = "README.md" 8 | homepage = "https://github.com/TelePay-cash/telepay-python" 9 | repository = "https://github.com/TelePay-cash/telepay-python" 10 | keywords = ["payments", "gateway", "cryptocurrencies", "API"] 11 | classifiers = [ 12 | "Development Status :: 4 - Beta", 13 | "Environment :: Console", 14 | "Operating System :: OS Independent", 15 | "Topic :: Software Development :: Libraries :: Python Modules", 16 | "Programming Language :: Python", 17 | "Intended Audience :: Developers", 18 | "Natural Language :: English" 19 | ] 20 | include = [ 21 | "LICENSE", 22 | ] 23 | 24 | [tool.poetry.urls] 25 | "Bug Tracker" = "https://github.com/TelePay-cash/telepay-python/issues" 26 | 27 | [tool.poetry.dependencies] 28 | python = "^3.10" 29 | httpx = "^0.23.0" 30 | python-dotenv = "^0.20.0" 31 | fastapi = "^0.75.1" 32 | uvicorn = "^0.17.6" 33 | colorama = "^0.4.4" 34 | 35 | [tool.poetry.dev-dependencies] 36 | pytest = "^6.2.5" 37 | anyio = {extras = ["trio"], version = "^3.3.4"} 38 | flake8 = "^4.0.1" 39 | isort = "^5.10.1" 40 | pre-commit = "^2.18.1" 41 | black = "^22.3.0" 42 | 43 | [build-system] 44 | requires = ["poetry-core>=1.0.0"] 45 | build-backend = "poetry.core.masonry.api" 46 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | anyio==3.6.2 ; python_version >= "3.10" and python_version < "4.0" 2 | asgiref==3.6.0 ; python_version >= "3.10" and python_version < "4.0" 3 | certifi==2022.12.7 ; python_version >= "3.10" and python_version < "4.0" 4 | click==8.1.3 ; python_version >= "3.10" and python_version < "4.0" 5 | colorama==0.4.6 ; python_version >= "3.10" and python_version < "4.0" 6 | fastapi==0.75.2 ; python_version >= "3.10" and python_version < "4.0" 7 | h11==0.14.0 ; python_version >= "3.10" and python_version < "4.0" 8 | httpcore==0.16.3 ; python_version >= "3.10" and python_version < "4.0" 9 | httpx==0.23.3 ; python_version >= "3.10" and python_version < "4.0" 10 | idna==3.4 ; python_version >= "3.10" and python_version < "4.0" 11 | pydantic==1.10.4 ; python_version >= "3.10" and python_version < "4.0" 12 | python-dotenv==0.20.0 ; python_version >= "3.10" and python_version < "4.0" 13 | rfc3986[idna2008]==1.5.0 ; python_version >= "3.10" and python_version < "4.0" 14 | sniffio==1.3.0 ; python_version >= "3.10" and python_version < "4.0" 15 | starlette==0.17.1 ; python_version >= "3.10" and python_version < "4.0" 16 | typing-extensions==4.4.0 ; python_version >= "3.10" and python_version < "4.0" 17 | uvicorn==0.17.6 ; python_version >= "3.10" and python_version < "4.0" 18 | -------------------------------------------------------------------------------- /telepay/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.0.2" 2 | __author__ = "Carlos Lugones " 3 | __license__ = "MIT" 4 | -------------------------------------------------------------------------------- /telepay/v1/__init__.py: -------------------------------------------------------------------------------- 1 | from ._async.client import TelePayAsyncClient # noqa: F401 2 | from ._sync.client import TelePaySyncClient # noqa: F401 3 | from .auth import TelePayAuth # noqa: F401 4 | from .errors import TelePayError # noqa: F401 5 | from .models.account import Account # noqa: F401 6 | from .models.assets import Assets # noqa: F401 7 | from .models.invoice import Invoice # noqa: F401 8 | from .models.wallets import Wallet, Wallets # noqa: F401 9 | from .models.webhooks import Webhook, Webhooks # noqa: F401 10 | from .webhooks import TelePayWebhookListener # noqa: F401 11 | -------------------------------------------------------------------------------- /telepay/v1/_async/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TelePay-cash/telepay-python/50d0cee6945bb3b389fa1745f135c992c681a7d8/telepay/v1/_async/__init__.py -------------------------------------------------------------------------------- /telepay/v1/_async/client.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from dataclasses import dataclass, field 3 | from typing import Union 4 | 5 | from httpx._config import Timeout 6 | from httpx._types import TimeoutTypes 7 | 8 | from ..auth import TelePayAuth 9 | from ..http_clients import AsyncClient 10 | from ..models.account import Account 11 | from ..models.assets import Asset, Assets 12 | from ..models.invoice import Invoice, InvoiceList 13 | from ..models.wallets import Wallet, Wallets 14 | from ..models.webhooks import Webhook, Webhooks 15 | from ..utils import validate_response 16 | 17 | logger = logging.getLogger(__name__) 18 | 19 | 20 | @dataclass 21 | class TelePayAsyncClient: 22 | """ 23 | Creates a TelePay async client. 24 | * API_SECRET: Your merchant private API key. 25 | Any requests without this authentication key will result in error 403. 26 | """ 27 | 28 | timeout: TimeoutTypes = field(default=Timeout(60)) 29 | 30 | def __init__(self, secret_api_key, timeout=Timeout(60)) -> None: 31 | self.base_url = "https://api.telepay.cash/rest/" 32 | self.timeout = timeout 33 | self.http_client = AsyncClient( 34 | base_url=self.base_url, 35 | headers={"Authorization": secret_api_key}, 36 | timeout=self.timeout, 37 | ) 38 | 39 | async def __aenter__(self) -> "TelePayAsyncClient": 40 | return self 41 | 42 | async def __aexit__(self, exc_type, exc_val, exc_tb) -> None: 43 | await self.close() 44 | 45 | async def close(self) -> None: 46 | await self.http_client.aclose() 47 | 48 | @staticmethod 49 | def from_auth(auth: TelePayAuth, timeout=Timeout(60)) -> "TelePayAsyncClient": 50 | return TelePayAsyncClient(auth.secret_api_key, timeout=timeout) 51 | 52 | async def get_me(self) -> Account: 53 | """ 54 | Info about the current account 55 | """ 56 | response = await self.http_client.get("getMe") 57 | logger.debug(f"Response: {response.text}") 58 | validate_response(response) 59 | return Account.from_json(response.json()) 60 | 61 | async def get_balance( 62 | self, asset=None, blockchain=None, network=None 63 | ) -> Union[Wallet, Wallets]: 64 | """ 65 | Get your merchant wallet assets with corresponding balance 66 | """ 67 | if asset and blockchain and network: 68 | response = await self.http_client.post( 69 | "getBalance", 70 | json={"asset": asset, "blockchain": blockchain, "network": network}, 71 | ) 72 | logger.debug(f"Response: {response.text}") 73 | validate_response(response) 74 | return Wallets.from_json(response.json()) 75 | else: 76 | response = await self.http_client.get("getBalance") 77 | logger.debug(f"Response: {response.text}") 78 | validate_response(response) 79 | return Wallets.from_json(response.json()) 80 | 81 | async def get_asset(self, asset: str, blockchain: str) -> Asset: 82 | """ 83 | Get asset details 84 | """ 85 | response = await self.http_client.request( 86 | method="GET", 87 | url="getAsset", 88 | json={ 89 | "asset": asset, 90 | "blockchain": blockchain, 91 | }, 92 | ) 93 | logger.debug(f"Response: {response.text}") 94 | validate_response(response) 95 | return Asset.from_json(response.json()) 96 | 97 | async def get_assets(self) -> Assets: 98 | """ 99 | Get assets suported by TelePay 100 | """ 101 | response = await self.http_client.get("getAssets") 102 | logger.debug(f"Response: {response.text}") 103 | validate_response(response) 104 | return Assets.from_json(response.json()) 105 | 106 | async def get_invoices(self) -> InvoiceList: 107 | """ 108 | Get your merchant invoices 109 | """ 110 | response = await self.http_client.get("getInvoices") 111 | logger.debug(f"Response: {response.text}") 112 | validate_response(response) 113 | return InvoiceList.from_json(response.json()) 114 | 115 | async def get_invoice(self, number: str) -> Invoice: 116 | """ 117 | Get invoice details, by ID 118 | """ 119 | response = await self.http_client.get(f"getInvoice/{number}") 120 | logger.debug(f"Response: {response.text}") 121 | validate_response(response) 122 | return Invoice.from_json(response.json()) 123 | 124 | async def create_invoice( 125 | self, 126 | asset: str, 127 | blockchain: str, 128 | network: str, 129 | amount: float, 130 | success_url: str, 131 | cancel_url: str, 132 | expires_at: int, 133 | metadata: dict = None, 134 | description: str = None, 135 | ) -> Invoice: 136 | """ 137 | Create an invoice 138 | """ 139 | response = await self.http_client.post( 140 | "createInvoice", 141 | json={ 142 | "asset": asset, 143 | "blockchain": blockchain, 144 | "network": network, 145 | "amount": amount, 146 | "description": description, 147 | "metadata": metadata, 148 | "success_url": success_url, 149 | "cancel_url": cancel_url, 150 | "expires_at": expires_at, 151 | }, 152 | ) 153 | logger.debug(f"Response: {response.text}") 154 | validate_response(response) 155 | return Invoice.from_json(response.json()) 156 | 157 | async def cancel_invoice(self, number: str) -> Invoice: 158 | """ 159 | Cancel an invoice 160 | """ 161 | response = await self.http_client.post(f"cancelInvoice/{number}") 162 | logger.debug(f"Response: {response.text}") 163 | validate_response(response) 164 | return Invoice.from_json(response.json()) 165 | 166 | async def delete_invoice(self, number: str) -> dict: 167 | """ 168 | Delete an invoice 169 | """ 170 | response = await self.http_client.post(f"deleteInvoice/{number}") 171 | logger.debug(f"Response: {response.text}") 172 | validate_response(response) 173 | return response.json() 174 | 175 | async def transfer( 176 | self, 177 | asset: str, 178 | blockchain: str, 179 | network: str, 180 | amount: float, 181 | username: str, 182 | message: str = None, 183 | ) -> dict: 184 | """ 185 | Transfer funds between internal wallets. 186 | Off-chain operation. 187 | """ 188 | response = await self.http_client.post( 189 | "transfer", 190 | json={ 191 | "asset": asset, 192 | "blockchain": blockchain, 193 | "network": network, 194 | "amount": amount, 195 | "username": username, 196 | "message": message, 197 | }, 198 | ) 199 | logger.debug(f"Response: {response.text}") 200 | validate_response(response) 201 | return response.json() 202 | 203 | async def get_withdraw_minimum( 204 | self, 205 | asset: str, 206 | blockchain: str, 207 | network: str = None, 208 | ) -> dict: 209 | """ 210 | Get minimum withdraw amount. 211 | """ 212 | response = await self.http_client.post( 213 | "getWithdrawMinimum", 214 | json={ 215 | "asset": asset, 216 | "blockchain": blockchain, 217 | "network": network, 218 | }, 219 | ) 220 | logger.debug(f"Response: {response.text}") 221 | validate_response(response) 222 | return response.json() 223 | 224 | async def get_withdraw_fee( 225 | self, 226 | asset: str, 227 | blockchain: str, 228 | network: str, 229 | amount: float, 230 | to_address: str, 231 | message: str = None, 232 | ) -> dict: 233 | """ 234 | Get estimated withdraw fee, composed of blockchain fee and processing fee. 235 | """ 236 | response = await self.http_client.post( 237 | "getWithdrawFee", 238 | json={ 239 | "to_address": to_address, 240 | "asset": asset, 241 | "blockchain": blockchain, 242 | "network": network, 243 | "amount": amount, 244 | "message": message, 245 | }, 246 | ) 247 | logger.debug(f"Response: {response.text}") 248 | validate_response(response) 249 | return response.json() 250 | 251 | async def withdraw( 252 | self, 253 | to_address: str, 254 | asset: str, 255 | blockchain: str, 256 | network: str, 257 | amount: float, 258 | message: str, 259 | ) -> dict: 260 | """ 261 | Withdraw funds from merchant wallet to external wallet. 262 | On-chain operation. 263 | """ 264 | response = await self.http_client.post( 265 | "withdraw", 266 | json={ 267 | "to_address": to_address, 268 | "asset": asset, 269 | "blockchain": blockchain, 270 | "network": network, 271 | "amount": amount, 272 | "message": message, 273 | }, 274 | ) 275 | logger.debug(f"Response: {response.text}") 276 | validate_response(response) 277 | return response.json() 278 | 279 | async def create_webhook( 280 | self, url: str, secret: str, events: list, active: bool 281 | ) -> Webhook: 282 | """ 283 | Create a webhook 284 | """ 285 | response = await self.http_client.post( 286 | "createWebhook", 287 | json={ 288 | "url": url, 289 | "secret": secret, 290 | "events": events, 291 | "active": active, 292 | }, 293 | ) 294 | logger.debug(f"Response: {response.text}") 295 | validate_response(response) 296 | return Webhook.from_json(response.json()) 297 | 298 | async def update_webhook( 299 | self, id: str, url: str, secret: str, events: list, active: bool 300 | ) -> Webhook: 301 | """ 302 | Update a webhook 303 | """ 304 | response = await self.http_client.post( 305 | f"updateWebhook/{id}", 306 | json={ 307 | "url": url, 308 | "secret": secret, 309 | "events": events, 310 | "active": active, 311 | }, 312 | ) 313 | logger.debug(f"Response: {response.text}") 314 | validate_response(response) 315 | return Webhook.from_json(response.json()) 316 | 317 | async def activate_webhook(self, id: str) -> Webhook: 318 | """ 319 | Activate a webhook 320 | """ 321 | response = await self.http_client.post(f"activateWebhook/{id}") 322 | validate_response(response) 323 | return Webhook.from_json(response.json()) 324 | 325 | async def deactivate_webhook(self, id: str) -> Webhook: 326 | """ 327 | Deactivate a webhook 328 | """ 329 | response = await self.http_client.post(f"deactivateWebhook/{id}") 330 | validate_response(response) 331 | return Webhook.from_json(response.json()) 332 | 333 | async def delete_webhook(self, id: str) -> dict: 334 | """ 335 | Delete a webhook 336 | """ 337 | response = await self.http_client.post(f"deleteWebhook/{id}") 338 | logger.debug(f"Response: {response.text}") 339 | validate_response(response) 340 | return response.json() 341 | 342 | async def get_webhook(self, id: str) -> Webhook: 343 | """ 344 | Get webhook 345 | """ 346 | response = await self.http_client.get(f"getWebhook/{id}") 347 | logger.debug(f"Response: {response.text}") 348 | validate_response(response) 349 | return Webhook.from_json(response.json()) 350 | 351 | async def get_webhooks(self) -> Webhooks: 352 | """ 353 | Get webhooks 354 | """ 355 | response = await self.http_client.get("getWebhooks") 356 | logger.debug(f"Response: {response.text}") 357 | validate_response(response) 358 | return Webhooks.from_json(response.json()) 359 | -------------------------------------------------------------------------------- /telepay/v1/_sync/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TelePay-cash/telepay-python/50d0cee6945bb3b389fa1745f135c992c681a7d8/telepay/v1/_sync/__init__.py -------------------------------------------------------------------------------- /telepay/v1/_sync/client.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from dataclasses import dataclass, field 3 | from typing import Union 4 | 5 | from httpx._config import Timeout 6 | from httpx._types import TimeoutTypes 7 | 8 | from ..auth import TelePayAuth 9 | from ..http_clients import SyncClient 10 | from ..models.account import Account 11 | from ..models.assets import Asset, Assets 12 | from ..models.invoice import Invoice, InvoiceList 13 | from ..models.wallets import Wallet, Wallets 14 | from ..models.webhooks import Webhook, Webhooks 15 | from ..utils import validate_response 16 | 17 | logger = logging.getLogger(__name__) 18 | 19 | 20 | @dataclass 21 | class TelePaySyncClient: 22 | """ 23 | Creates a TelePay client. 24 | * API_SECRET: Your merchant private API key. 25 | Any requests without this authentication key will result in error 403. 26 | """ 27 | 28 | timeout: TimeoutTypes = field(default=Timeout(60)) 29 | 30 | def __init__(self, secret_api_key, timeout=Timeout(60)) -> None: 31 | self.base_url = "https://api.telepay.cash/rest/" 32 | self.timeout = timeout 33 | self.http_client = SyncClient( 34 | base_url=self.base_url, 35 | headers={"Authorization": secret_api_key}, 36 | timeout=self.timeout, 37 | ) 38 | 39 | def __enter__(self) -> "TelePaySyncClient": 40 | return self 41 | 42 | def __exit__(self, exc_type, exc_val, exc_tb) -> None: 43 | self.close() 44 | 45 | def close(self) -> None: 46 | self.http_client.aclose() 47 | 48 | @staticmethod 49 | def from_auth(auth: TelePayAuth, timeout=Timeout(60)) -> "TelePaySyncClient": 50 | return TelePaySyncClient(auth.secret_api_key, timeout=timeout) 51 | 52 | def get_me(self) -> Account: 53 | """ 54 | Info about the current account 55 | """ 56 | response = self.http_client.get("getMe") 57 | logger.debug(f"response: {response.text}") 58 | validate_response(response) 59 | return Account.from_json(response.json()) 60 | 61 | def get_balance( 62 | self, asset=None, blockchain=None, network=None 63 | ) -> Union[Wallet, Wallets]: 64 | """ 65 | Get your merchant wallet assets with corresponding balance 66 | """ 67 | if asset and blockchain and network: 68 | response = self.http_client.post( 69 | "getBalance", 70 | json={"asset": asset, "blockchain": blockchain, "network": network}, 71 | ) 72 | logger.debug(f"response: {response.text}") 73 | validate_response(response) 74 | return Wallets.from_json(response.json()) 75 | else: 76 | response = self.http_client.get("getBalance") 77 | logger.debug(f"response: {response.text}") 78 | validate_response(response) 79 | return Wallets.from_json(response.json()) 80 | 81 | def get_asset(self, asset: str, blockchain: str) -> Asset: 82 | """ 83 | Get asset details 84 | """ 85 | response = self.http_client.request( 86 | method="GET", 87 | url="getAsset", 88 | json={ 89 | "asset": asset, 90 | "blockchain": blockchain, 91 | }, 92 | ) 93 | logger.debug(f"response: {response.text}") 94 | validate_response(response) 95 | return Asset.from_json(response.json()) 96 | 97 | def get_assets(self) -> Assets: 98 | """ 99 | Get assets suported by TelePay 100 | """ 101 | response = self.http_client.get("getAssets") 102 | logger.debug(f"response: {response.text}") 103 | validate_response(response) 104 | return Assets.from_json(response.json()) 105 | 106 | def get_invoices(self) -> InvoiceList: 107 | """ 108 | Get your merchant invoices 109 | """ 110 | response = self.http_client.get("getInvoices") 111 | logger.debug(f"response: {response.text}") 112 | validate_response(response) 113 | return InvoiceList.from_json(response.json()) 114 | 115 | def get_invoice(self, number: str) -> Invoice: 116 | """ 117 | Get invoice details, by ID 118 | """ 119 | response = self.http_client.get(f"getInvoice/{number}") 120 | logger.debug(f"response: {response.text}") 121 | validate_response(response) 122 | return Invoice.from_json(response.json()) 123 | 124 | def create_invoice( 125 | self, 126 | asset: str, 127 | blockchain: str, 128 | network: str, 129 | amount: float, 130 | success_url: str, 131 | cancel_url: str, 132 | expires_at: int, 133 | metadata: dict = None, 134 | description: str = None, 135 | ) -> Invoice: 136 | """ 137 | Create an invoice 138 | """ 139 | response = self.http_client.post( 140 | "createInvoice", 141 | json={ 142 | "asset": asset, 143 | "blockchain": blockchain, 144 | "network": network, 145 | "amount": amount, 146 | "description": description, 147 | "metadata": metadata, 148 | "success_url": success_url, 149 | "cancel_url": cancel_url, 150 | "expires_at": expires_at, 151 | }, 152 | ) 153 | logger.debug(f"response: {response.text}") 154 | validate_response(response) 155 | return Invoice.from_json(response.json()) 156 | 157 | def cancel_invoice(self, number: str) -> Invoice: 158 | """ 159 | Cancel an invoice 160 | """ 161 | response = self.http_client.post(f"cancelInvoice/{number}") 162 | logger.debug(f"response: {response.text}") 163 | validate_response(response) 164 | return Invoice.from_json(response.json()) 165 | 166 | def delete_invoice(self, number: str) -> dict: 167 | """ 168 | Delete an invoice 169 | """ 170 | response = self.http_client.post(f"deleteInvoice/{number}") 171 | logger.debug(f"response: {response.text}") 172 | validate_response(response) 173 | return response.json() 174 | 175 | def transfer( 176 | self, 177 | asset: str, 178 | blockchain: str, 179 | network: str, 180 | amount: float, 181 | username: str, 182 | message: str = None, 183 | ) -> dict: 184 | """ 185 | Transfer funds between internal wallets. 186 | Off-chain operation. 187 | """ 188 | response = self.http_client.post( 189 | "transfer", 190 | json={ 191 | "asset": asset, 192 | "blockchain": blockchain, 193 | "network": network, 194 | "amount": amount, 195 | "username": username, 196 | "message": message, 197 | }, 198 | ) 199 | logger.debug(f"response: {response.text}") 200 | validate_response(response) 201 | return response.json() 202 | 203 | def get_withdraw_minimum( 204 | self, 205 | asset: str, 206 | blockchain: str, 207 | network: str = None, 208 | ) -> dict: 209 | """ 210 | Get minimum withdraw amount. 211 | """ 212 | response = self.http_client.post( 213 | "getWithdrawMinimum", 214 | json={ 215 | "asset": asset, 216 | "blockchain": blockchain, 217 | "network": network, 218 | }, 219 | ) 220 | logger.debug(f"response: {response.text}") 221 | validate_response(response) 222 | return response.json() 223 | 224 | def get_withdraw_fee( 225 | self, 226 | to_address: str, 227 | asset: str, 228 | blockchain: str, 229 | network: str, 230 | amount: float, 231 | message: str = None, 232 | ) -> dict: 233 | """ 234 | Get estimated withdraw fee, composed of blockchain fee and processing fee. 235 | """ 236 | response = self.http_client.post( 237 | "getWithdrawFee", 238 | json={ 239 | "to_address": to_address, 240 | "asset": asset, 241 | "blockchain": blockchain, 242 | "network": network, 243 | "amount": amount, 244 | "message": message, 245 | }, 246 | ) 247 | logger.debug(f"response: {response.text}") 248 | validate_response(response) 249 | return response.json() 250 | 251 | def withdraw( 252 | self, 253 | to_address: str, 254 | asset: str, 255 | blockchain: str, 256 | network: str, 257 | amount: float, 258 | message: str, 259 | ) -> dict: 260 | """ 261 | Withdraw funds from merchant wallet to external wallet. 262 | On-chain operation. 263 | """ 264 | response = self.http_client.post( 265 | "withdraw", 266 | json={ 267 | "to_address": to_address, 268 | "asset": asset, 269 | "blockchain": blockchain, 270 | "network": network, 271 | "amount": amount, 272 | "message": message, 273 | }, 274 | ) 275 | logger.debug(f"response: {response.text}") 276 | validate_response(response) 277 | return response.json() 278 | 279 | def create_webhook( 280 | self, url: str, secret: str, events: list, active: bool 281 | ) -> Webhook: 282 | """ 283 | Create a webhook 284 | """ 285 | response = self.http_client.post( 286 | "createWebhook", 287 | json={ 288 | "url": url, 289 | "secret": secret, 290 | "events": events, 291 | "active": active, 292 | }, 293 | ) 294 | logger.debug(f"response: {response.text}") 295 | validate_response(response) 296 | return Webhook.from_json(response.json()) 297 | 298 | def update_webhook( 299 | self, id: str, url: str, secret: str, events: list, active: bool 300 | ) -> Webhook: 301 | """ 302 | Update a webhook 303 | """ 304 | response = self.http_client.post( 305 | f"updateWebhook/{id}", 306 | json={ 307 | "url": url, 308 | "secret": secret, 309 | "events": events, 310 | "active": active, 311 | }, 312 | ) 313 | logger.debug(f"response: {response.text}") 314 | validate_response(response) 315 | return Webhook.from_json(response.json()) 316 | 317 | def activate_webhook(self, id: str) -> Webhook: 318 | """ 319 | Activate a webhook 320 | """ 321 | response = self.http_client.post(f"activateWebhook/{id}") 322 | logger.debug(f"response: {response.text}") 323 | validate_response(response) 324 | return Webhook.from_json(response.json()) 325 | 326 | def deactivate_webhook(self, id: str) -> Webhook: 327 | """ 328 | Deactivate a webhook 329 | """ 330 | response = self.http_client.post(f"deactivateWebhook/{id}") 331 | logger.debug(f"response: {response.text}") 332 | validate_response(response) 333 | return Webhook.from_json(response.json()) 334 | 335 | def delete_webhook(self, id: str) -> dict: 336 | """ 337 | Delete a webhook 338 | """ 339 | response = self.http_client.post(f"deleteWebhook/{id}") 340 | logger.debug(f"response: {response.text}") 341 | validate_response(response) 342 | return response.json() 343 | 344 | def get_webhook(self, id: str) -> Webhook: 345 | """ 346 | Get webhook 347 | """ 348 | response = self.http_client.get(f"getWebhook/{id}") 349 | logger.debug(f"response: {response.text}") 350 | validate_response(response) 351 | return Webhook.from_json(response.json()) 352 | 353 | def get_webhooks(self) -> Webhooks: 354 | """ 355 | Get webhooks 356 | """ 357 | response = self.http_client.get("getWebhooks") 358 | logger.debug(f"response: {response.text}") 359 | validate_response(response) 360 | return Webhooks.from_json(response.json()) 361 | -------------------------------------------------------------------------------- /telepay/v1/auth.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | from os import environ 3 | 4 | from dotenv import load_dotenv 5 | 6 | from .errors import TelePayError 7 | 8 | load_dotenv() 9 | 10 | 11 | @dataclass 12 | class TelePayAuth: 13 | secret_api_key: str = field(default=environ["TELEPAY_SECRET_API_KEY"]) 14 | 15 | def __init__(self, secret_api_key=None): 16 | if secret_api_key: 17 | self.secret_api_key = secret_api_key 18 | else: 19 | self.secret_api_key = environ["TELEPAY_SECRET_API_KEY"] 20 | self.__post_init__() 21 | 22 | def __post_init__(self): 23 | if not self.secret_api_key: 24 | raise TelePayError(0, "TELEPAY_SECRET_API_KEY is not setted") 25 | -------------------------------------------------------------------------------- /telepay/v1/errors.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | from typing import Optional 3 | 4 | 5 | @dataclass 6 | class TelePayError(Exception): 7 | status_code: int 8 | error: str 9 | message: Optional[str] = field(default=None) 10 | -------------------------------------------------------------------------------- /telepay/v1/http_clients.py: -------------------------------------------------------------------------------- 1 | from httpx import AsyncClient # noqa 2 | from httpx import Client as BaseClient 3 | 4 | 5 | class SyncClient(BaseClient): 6 | def aclose(self) -> None: 7 | self.close() 8 | -------------------------------------------------------------------------------- /telepay/v1/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TelePay-cash/telepay-python/50d0cee6945bb3b389fa1745f135c992c681a7d8/telepay/v1/models/__init__.py -------------------------------------------------------------------------------- /telepay/v1/models/account.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from dataclasses import dataclass 3 | from typing import Any 4 | 5 | from ..utils import parse_json 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | @dataclass 11 | class Account: 12 | merchant: dict 13 | 14 | def __post_init__(self): 15 | pass 16 | 17 | @classmethod 18 | def from_json(cls, json: Any) -> "Account": 19 | del json["version"] 20 | logger.debug(f"Parsing Account from JSON: {json}") 21 | return parse_json(cls, **json) 22 | -------------------------------------------------------------------------------- /telepay/v1/models/assets.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from dataclasses import dataclass 3 | from typing import Any, List 4 | 5 | from ..utils import parse_json 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | @dataclass 11 | class Asset: 12 | asset: str 13 | blockchain: str 14 | usd_price: float 15 | url: str 16 | networks: List[str] 17 | coingecko_id: str 18 | 19 | @classmethod 20 | def from_json(cls, json: Any) -> "Asset": 21 | return parse_json(cls, **json) 22 | 23 | 24 | @dataclass 25 | class Assets: 26 | assets: List[Asset] 27 | 28 | def __post_init__(self): 29 | pass 30 | 31 | @classmethod 32 | def from_json(cls, json: Any) -> "Assets": 33 | logger.debug(f"Parsing Assets from JSON: {json}") 34 | return parse_json(cls, **json) 35 | -------------------------------------------------------------------------------- /telepay/v1/models/invoice.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from dataclasses import dataclass 3 | from datetime import datetime 4 | from typing import Any 5 | 6 | from ..utils import parse_json 7 | 8 | FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ" 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | @dataclass 14 | class Invoice: 15 | asset: str 16 | blockchain: str 17 | network: str 18 | amount: str 19 | description: str 20 | 21 | number: str 22 | status: str 23 | metadata: str 24 | 25 | success_url: str 26 | cancel_url: str 27 | 28 | created_at: datetime 29 | updated_at: datetime 30 | expires_at: datetime 31 | 32 | # after created 33 | checkout_url: str 34 | onchain_url: str 35 | 36 | # after payment completed 37 | explorer_url: str 38 | 39 | def __post_init__(self): 40 | self.asset = str(self.asset) 41 | self.blockchain = str(self.blockchain) 42 | self.network = str(self.network) 43 | self.amount = str(self.amount) 44 | self.description = str(self.description) 45 | self.success_url = str(self.success_url) 46 | self.cancel_url = str(self.cancel_url) 47 | self.status = str(self.status) 48 | self.number = str(self.number) 49 | self.metadata = str(self.metadata) 50 | self.created_at = datetime.strptime(self.created_at, FORMAT) 51 | self.expires_at = datetime.strptime(self.expires_at, FORMAT) 52 | 53 | if self.updated_at is not None: 54 | self.updated_at = datetime.strptime(self.updated_at, FORMAT) 55 | 56 | @classmethod 57 | def from_json(cls, json: Any) -> "Invoice": 58 | logger.debug(f"Parsing Invoice from JSON: {json}") 59 | return parse_json(cls, **json) 60 | 61 | 62 | @dataclass 63 | class InvoiceList: 64 | invoices: list 65 | 66 | def __post_init__(self): 67 | self.invoices = [Invoice.from_json(invoice) for invoice in self.invoices] 68 | 69 | @classmethod 70 | def from_json(cls, json: Any) -> "InvoiceList": 71 | logger.debug(f"Parsing InvoiceList from JSON: {json}") 72 | return parse_json(cls, **json) 73 | -------------------------------------------------------------------------------- /telepay/v1/models/wallets.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from dataclasses import dataclass 3 | from typing import Any, List 4 | 5 | from ..utils import parse_json 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | @dataclass 11 | class Wallet: 12 | asset: str 13 | blockchain: str 14 | network: str 15 | balance: float 16 | 17 | @classmethod 18 | def from_json(cls, json: Any) -> "Wallet": 19 | logger.debug(f"Parsing wallet from JSON: {json}") 20 | return parse_json(cls, **json) 21 | 22 | 23 | @dataclass 24 | class Wallets: 25 | wallets: List[Wallet] 26 | 27 | def __post_init__(self): 28 | pass 29 | 30 | @classmethod 31 | def from_json(cls, json: Any) -> "Wallets": 32 | logger.debug(f"Parsing wallets from JSON: {json}") 33 | return parse_json(cls, **json) 34 | -------------------------------------------------------------------------------- /telepay/v1/models/webhooks.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from dataclasses import dataclass 3 | from typing import Any, List 4 | 5 | from ..utils import parse_json 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | @dataclass 11 | class Webhook: 12 | id: str 13 | url: str 14 | secret: str 15 | events: List[str] 16 | active: bool 17 | 18 | @classmethod 19 | def from_json(cls, json: Any) -> "Webhook": 20 | logger.debug(f"Parsing Webhook from JSON: {json}") 21 | return parse_json(cls, **json) 22 | 23 | 24 | @dataclass 25 | class Webhooks: 26 | webhooks: List[Webhook] 27 | 28 | def __post_init__(self): 29 | self.webhooks = [Webhook.from_json(webhook) for webhook in self.webhooks] 30 | 31 | @classmethod 32 | def from_json(cls, json: Any) -> "Webhooks": 33 | logger.debug(f"Parsing Webhooks from JSON: {json}") 34 | return parse_json(cls, **json) 35 | -------------------------------------------------------------------------------- /telepay/v1/utils.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from inspect import signature 3 | from json import JSONDecodeError 4 | from typing import Any, Type, TypeVar 5 | 6 | from httpx import Response 7 | 8 | from .errors import TelePayError 9 | 10 | T = TypeVar("T") 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | def validate_response(response: Response) -> None: 16 | if response.status_code < 200 or response.status_code >= 300: 17 | error_data = {} 18 | try: 19 | error_data = response.json() 20 | except JSONDecodeError as e: 21 | logger.error(e.msg) 22 | logger.error(f"{response.content} couldn't parse to json") 23 | finally: 24 | error = error_data.pop("error", response.status_code) 25 | message = error_data.pop("message", response.content) 26 | raise TelePayError( 27 | status_code=response.status_code, 28 | error=error, 29 | message=message, 30 | ) 31 | 32 | 33 | def parse_json(cls: Type[T], **json: Any) -> T: 34 | cls_fields = {field for field in signature(cls).parameters} 35 | native_args, new_args = {}, {} 36 | for name, val in json.items(): 37 | if name in cls_fields: 38 | native_args[name] = val 39 | else: 40 | new_args[name] = val 41 | ret = cls(**native_args) 42 | for new_name, new_val in new_args.items(): 43 | setattr(ret, new_name, new_val) 44 | return ret 45 | -------------------------------------------------------------------------------- /telepay/v1/webhooks.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import json 3 | import logging 4 | from dataclasses import dataclass 5 | 6 | import uvicorn 7 | from colorama import Fore, Style 8 | from fastapi import FastAPI, Request 9 | 10 | from .errors import TelePayError 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | # events 15 | INVOICE_COMPLETED = "invoice.completed" 16 | INVOICE_CANCELLED = "invoice.cancelled" 17 | INVOICE_EXPIRED = "invoice.expired" 18 | INVOICE_DELETED = "invoice.deleted" 19 | 20 | 21 | def get_signature(data, secret): 22 | """ 23 | Get the webhook signature using the request data and your secret API key 24 | """ 25 | hash_secret = hashlib.sha1(secret.encode()).hexdigest() 26 | hash_data = hashlib.sha512(data.encode()).hexdigest() 27 | signature = hashlib.sha512((hash_secret + hash_data).encode()).hexdigest() 28 | return signature 29 | 30 | 31 | @dataclass 32 | class TelePayWebhookListener: 33 | secret: str 34 | callback: callable 35 | host: str = "localhost" 36 | port: str = 5000 37 | url: str = "/webhook" 38 | log_level: str = "error" 39 | 40 | app = FastAPI() 41 | 42 | def __post_init__(self): 43 | @self.app.post(self.url) 44 | async def listen_webhook(request: Request): 45 | data = str(json.loads(await request.json())) 46 | 47 | request_signature = request.headers["Webhook-Signature"] 48 | 49 | signature = get_signature(str(data), self.secret) 50 | 51 | if signature != request_signature: 52 | logger.debug(f"Signature mismatch: {signature} != {request_signature}") 53 | 54 | raise TelePayError( 55 | message="Invalid signature", 56 | status_code=400, 57 | error="invalid_signature", 58 | ) 59 | 60 | self.callback(request.headers, data) 61 | 62 | return "Thanks TelePay" 63 | 64 | def listen(self): 65 | url = f"http://{self.host}:{self.port}{self.url}" 66 | logger.debug(f"Listening on {url}") 67 | 68 | print( 69 | Fore.CYAN 70 | + r""" 71 | _____ _ ______ 72 | |_ _| | | | ___ \ 73 | | | ___| | ___| |_/ /_ _ _ _ 74 | | |/ _ \ |/ _ \ __/ _` | | | | 75 | | | __/ | __/ | | (_| | |_| | 76 | \_/\___|_|\___\_| \__,_|\__, |.cash ⚡️ 77 | __/ | 78 | |___/ 79 | """ 80 | ) 81 | print( 82 | Style.RESET_ALL 83 | + f""" 84 | [ Webhook listener ] 85 | 86 | * Docs: https://telepay.readme.io/reference/webhooks 87 | * Listening on {url} (Press CTRL+C to quit) 88 | """ 89 | ) 90 | uvicorn.run(self.app, host=self.host, port=self.port, log_level=self.log_level) 91 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TelePay-cash/telepay-python/50d0cee6945bb3b389fa1745f135c992c681a7d8/tests/__init__.py -------------------------------------------------------------------------------- /tests/v1/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TelePay-cash/telepay-python/50d0cee6945bb3b389fa1745f135c992c681a7d8/tests/v1/__init__.py -------------------------------------------------------------------------------- /tests/v1/_async/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TelePay-cash/telepay-python/50d0cee6945bb3b389fa1745f135c992c681a7d8/tests/v1/_async/__init__.py -------------------------------------------------------------------------------- /tests/v1/_async/test_client.py: -------------------------------------------------------------------------------- 1 | import os 2 | import uuid 3 | 4 | from httpx import Timeout 5 | from pytest import fixture 6 | from pytest import mark as pytest_mark 7 | 8 | from telepay.v1 import Invoice, TelePayAsyncClient, TelePayAuth, TelePayError, Webhook 9 | 10 | from ..utils import ERRORS, random_text 11 | 12 | TIMEOUT = 60 13 | 14 | 15 | @fixture(name="client") 16 | async def create_client(): 17 | client = TelePayAsyncClient.from_auth(TelePayAuth(), timeout=Timeout(TIMEOUT)) 18 | yield client 19 | await client.close() 20 | 21 | 22 | @fixture(name="invoice") 23 | async def create_invoice(client: TelePayAsyncClient): 24 | invoice = await client.create_invoice( 25 | asset="TON", 26 | blockchain="TON", 27 | network="testnet", 28 | amount=1, 29 | description="Testing", 30 | metadata={"color": "red", "size": "large"}, 31 | success_url="https://example.com/success", 32 | cancel_url="https://example.com/cancel", 33 | expires_at=1, 34 | ) 35 | yield invoice 36 | 37 | 38 | @fixture(name="webhook") 39 | async def create_webhook(client: TelePayAsyncClient): 40 | webhook = await client.create_webhook( 41 | url=f"https://{uuid.uuid4().hex}.com", 42 | secret="hello", 43 | events=["all"], 44 | active=False, 45 | ) 46 | yield webhook 47 | 48 | 49 | @pytest_mark.anyio 50 | async def test_error(client: TelePayAsyncClient): 51 | client = TelePayAsyncClient("") 52 | try: 53 | await client.get_me() 54 | assert False 55 | except TelePayError: 56 | assert True 57 | 58 | 59 | @pytest_mark.anyio 60 | async def test_client_with_context(): 61 | api_key = os.environ["TELEPAY_SECRET_API_KEY"] 62 | async with TelePayAsyncClient(secret_api_key=api_key) as client: 63 | assert client is not None 64 | 65 | 66 | @pytest_mark.anyio 67 | async def test_get_me(client: TelePayAsyncClient): 68 | try: 69 | await client.get_me() 70 | except TelePayError as e: 71 | if e.status_code == 403: 72 | assert e.error == "forbidden" 73 | assert e.message == ERRORS["forbidden"] 74 | 75 | 76 | @pytest_mark.anyio 77 | async def test_get_balance(client: TelePayAsyncClient): 78 | try: 79 | await client.get_balance() 80 | await client.get_balance(asset="TON", blockchain="TON", network="testnet") 81 | except TelePayError as e: 82 | if e.status_code == 403: 83 | assert e.error == "forbidden" 84 | assert e.message == ERRORS["forbidden"] 85 | 86 | 87 | @pytest_mark.anyio 88 | async def test_get_asset(client: TelePayAsyncClient): 89 | try: 90 | await client.get_asset(asset="TON", blockchain="TON") 91 | except TelePayError as e: 92 | if e.status_code == 403: 93 | assert e.error == "forbidden" 94 | assert e.message == ERRORS["forbidden"] 95 | 96 | 97 | @pytest_mark.anyio 98 | async def test_get_assets(client: TelePayAsyncClient): 99 | try: 100 | await client.get_assets() 101 | except TelePayError as e: 102 | if e.status_code == 403: 103 | assert e.error == "forbidden" 104 | assert e.message == ERRORS["forbidden"] 105 | 106 | 107 | @pytest_mark.anyio 108 | async def test_get_invoices(client: TelePayAsyncClient): 109 | try: 110 | await client.get_invoices() 111 | except TelePayError as e: 112 | if e.status_code == 403: 113 | assert e.error == "forbidden" 114 | assert e.message == ERRORS["forbidden"] 115 | 116 | 117 | @pytest_mark.anyio 118 | async def test_get_invoice(client: TelePayAsyncClient, invoice: Invoice): 119 | try: 120 | await client.get_invoice(invoice.number) 121 | except TelePayError as e: 122 | if e.status_code == 403: 123 | assert e.error == "forbidden" 124 | assert e.message == ERRORS["forbidden"] 125 | 126 | 127 | @pytest_mark.anyio 128 | async def test_get_invoice_not_found(client: TelePayAsyncClient): 129 | number = random_text(10) 130 | try: 131 | await client.get_invoice(number) 132 | assert False 133 | except TelePayError as e: 134 | if e.status_code == 403: 135 | assert e.error == "forbidden" 136 | assert e.message == ERRORS["forbidden"] 137 | elif e.status_code == 404: 138 | assert e.error == "invoice.not-found" 139 | assert e.message == ERRORS["invoice.not-found"] 140 | 141 | 142 | @pytest_mark.anyio 143 | async def test_cancel_invoice(client: TelePayAsyncClient, invoice: Invoice): 144 | try: 145 | await client.cancel_invoice(invoice.number) 146 | except TelePayError as e: 147 | if e.status_code == 403: 148 | assert e.error == "forbidden" 149 | assert e.message == ERRORS["forbidden"] 150 | elif e.status_code == 404: 151 | assert e.error == "invoice.not-found" 152 | assert e.message == ERRORS["invoice.not-found"] 153 | 154 | 155 | @pytest_mark.anyio 156 | async def test_cancel_invoice_not_found(client: TelePayAsyncClient): 157 | number = random_text(10) 158 | try: 159 | await client.cancel_invoice(number) 160 | except TelePayError as e: 161 | if e.status_code == 403: 162 | assert e.error == "forbidden" 163 | assert e.message == ERRORS["forbidden"] 164 | elif e.status_code == 404: 165 | assert e.error == "invoice.not-found" 166 | assert e.message == ERRORS["invoice.not-found"] 167 | 168 | 169 | @pytest_mark.anyio 170 | async def test_delete_invoice(client: TelePayAsyncClient, invoice: Invoice): 171 | try: 172 | await client.cancel_invoice(invoice.number) 173 | await client.delete_invoice(invoice.number) 174 | except TelePayError as e: 175 | if e.status_code == 403: 176 | assert e.error == "forbidden" 177 | assert e.message == ERRORS["forbidden"] 178 | elif e.status_code == 404: 179 | assert e.error == "invoice.not-found" 180 | assert e.message == ERRORS["invoice.not-found"] 181 | 182 | 183 | @pytest_mark.anyio 184 | async def test_delete_invoice_not_found(client: TelePayAsyncClient): 185 | number = random_text(10) 186 | try: 187 | await client.delete_invoice(number) 188 | assert False 189 | except TelePayError as e: 190 | if e.status_code == 403: 191 | assert e.error == "forbidden" 192 | assert e.message == ERRORS["forbidden"] 193 | elif e.status_code == 404: 194 | assert e.error == "invoice.not-found" 195 | assert e.message == ERRORS["invoice.not-found"] 196 | 197 | 198 | @pytest_mark.anyio 199 | async def test_transfer_without_funds(client: TelePayAsyncClient): 200 | try: 201 | await client.transfer( 202 | asset="TON", 203 | blockchain="TON", 204 | network="testnet", 205 | amount=1, 206 | username="telepay", 207 | ) 208 | except TelePayError as e: 209 | if e.status_code == 401: 210 | assert e.error == "transfer.insufficient-funds" 211 | assert e.message == ERRORS["transfer.insufficient-funds"] 212 | elif e.status_code == 403: 213 | assert e.error == "forbidden" 214 | assert e.message == ERRORS["forbidden"] 215 | elif e.status_code == 404: 216 | assert e.error == "invoice.not-found" 217 | assert e.message == ERRORS["invoice.not-found"] 218 | 219 | 220 | @pytest_mark.anyio 221 | async def test_transfer_to_wrong_user(client: TelePayAsyncClient): 222 | username = random_text(20) 223 | try: 224 | await client.transfer( 225 | asset="TON", 226 | blockchain="TON", 227 | network="testnet", 228 | amount=1, 229 | username=username, 230 | ) 231 | except TelePayError as e: 232 | if e.status_code == 401: 233 | assert e.error == "transfer.insufficient-funds" 234 | assert e.message == ERRORS["transfer.insufficient-funds"] 235 | elif e.status_code == 403: 236 | assert e.error == "forbidden" 237 | assert e.message == ERRORS["forbidden"] 238 | elif e.status_code == 404: 239 | assert e.error == "account.not-found" 240 | assert e.message == ERRORS["account.not-found"] 241 | 242 | 243 | @pytest_mark.anyio 244 | async def test_transfer_to_itself(client: TelePayAsyncClient): 245 | account = await client.get_me() 246 | username = account.merchant["username"] 247 | try: 248 | await client.transfer( 249 | asset="TON", 250 | blockchain="TON", 251 | network="testnet", 252 | amount=1, 253 | username=username, 254 | ) 255 | except TelePayError as e: 256 | if e.status_code == 401: 257 | assert e.error == "transfer.not-possible" 258 | assert e.message == ERRORS["transfer.not-possible"] 259 | elif e.status_code == 403: 260 | assert e.error == "forbidden" 261 | assert e.message == ERRORS["forbidden"] 262 | 263 | 264 | @pytest_mark.anyio 265 | async def test_withdraw_minimum(client: TelePayAsyncClient): 266 | try: 267 | await client.get_withdraw_minimum( 268 | asset="TON", 269 | blockchain="TON", 270 | network="testnet", 271 | ) 272 | except TelePayError as e: 273 | if e.status_code == 403: 274 | assert e.error == "forbidden" 275 | assert e.message == ERRORS["forbidden"] 276 | 277 | 278 | @pytest_mark.anyio 279 | async def test_get_withdraw_fee(client: TelePayAsyncClient): 280 | try: 281 | await client.get_withdraw_fee( 282 | to_address="EQCKYK7bYBt1t8UmdhImrbiSzC5ijfo_H3Zc_Hk8ksRpOkOk", 283 | asset="TON", 284 | blockchain="TON", 285 | network="testnet", 286 | amount=1, 287 | message="test", 288 | ) 289 | except TelePayError as e: 290 | if e.status_code == 403: 291 | assert e.error == "forbidden" 292 | assert e.message == ERRORS["forbidden"] 293 | 294 | 295 | @pytest_mark.anyio 296 | async def test_withdraw(client: TelePayAsyncClient): 297 | try: 298 | await client.withdraw( 299 | to_address="EQCKYK7bYBt1t8UmdhImrbiSzC5ijfo_H3Zc_Hk8ksRpOkOk", 300 | asset="TON", 301 | blockchain="TON", 302 | network="testnet", 303 | amount=1, 304 | message="test", 305 | ) 306 | except TelePayError as e: 307 | if e.status_code == 503: 308 | assert e.error == "unavailable" 309 | assert e.message == ERRORS["unavailable"] 310 | elif e.status_code == 401: 311 | assert e.error == "withdrawal.insufficient-funds" 312 | assert e.message == ERRORS["withdrawal.insufficient-funds"] 313 | elif e.status_code == 403: 314 | assert e.error == "forbidden" 315 | assert e.message == ERRORS["forbidden"] 316 | 317 | 318 | @pytest_mark.anyio 319 | async def test_update_webhook(client: TelePayAsyncClient, webhook: Webhook): 320 | try: 321 | await client.update_webhook( 322 | id=webhook.id, 323 | url="https://example.com", 324 | secret="hello", 325 | events=["invoice.completed"], 326 | active=False, 327 | ) 328 | await client.delete_webhook(id=webhook.id) 329 | except TelePayError as e: 330 | if e.status_code == 403: 331 | assert e.error == "forbidden" 332 | assert e.message == ERRORS["forbidden"] 333 | if e.status_code == 404: 334 | assert e.error == "webhook.not-found" 335 | assert e.message == ERRORS["webhook.not-found"] 336 | 337 | 338 | @pytest_mark.anyio 339 | async def test_activate_webhook(client: TelePayAsyncClient, webhook: Webhook): 340 | try: 341 | await client.activate_webhook(id=webhook.id) 342 | await client.delete_webhook(id=webhook.id) 343 | except TelePayError as e: 344 | if e.status_code == 403: 345 | assert e.error == "forbidden" 346 | assert e.message == ERRORS["forbidden"] 347 | if e.status_code == 404: 348 | assert e.error == "webhook.not-found" 349 | assert e.message == ERRORS["webhook.not-found"] 350 | 351 | 352 | @pytest_mark.anyio 353 | async def test_deactivate_webhook(client: TelePayAsyncClient, webhook: Webhook): 354 | try: 355 | await client.deactivate_webhook(id=webhook.id) 356 | await client.delete_webhook(id=webhook.id) 357 | except TelePayError as e: 358 | if e.status_code == 403: 359 | assert e.error == "forbidden" 360 | assert e.message == ERRORS["forbidden"] 361 | if e.status_code == 404: 362 | assert e.error == "webhook.not-found" 363 | assert e.message == ERRORS["webhook.not-found"] 364 | 365 | 366 | @pytest_mark.anyio 367 | async def test_delete_webhook(client: TelePayAsyncClient, webhook: Webhook): 368 | try: 369 | await client.delete_webhook(id=webhook.id) 370 | except TelePayError as e: 371 | if e.status_code == 403: 372 | assert e.error == "forbidden" 373 | assert e.message == ERRORS["forbidden"] 374 | if e.status_code == 404: 375 | assert e.error == "webhook.not-found" 376 | assert e.message == ERRORS["webhook.not-found"] 377 | 378 | 379 | @pytest_mark.anyio 380 | async def test_get_webhook(client: TelePayAsyncClient, webhook: Webhook): 381 | try: 382 | await client.get_webhook(id=webhook.id) 383 | await client.delete_webhook(id=webhook.id) 384 | except TelePayError as e: 385 | if e.status_code == 403: 386 | assert e.error == "forbidden" 387 | assert e.message == ERRORS["forbidden"] 388 | if e.status_code == 404: 389 | assert e.error == "webhook.not-found" 390 | assert e.message == ERRORS["webhook.not-found"] 391 | 392 | 393 | @pytest_mark.anyio 394 | async def test_get_webhooks(client: TelePayAsyncClient): 395 | try: 396 | await client.get_webhooks() 397 | except TelePayError as e: 398 | if e.status_code == 403: 399 | assert e.error == "forbidden" 400 | assert e.message == ERRORS["forbidden"] 401 | if e.status_code == 404: 402 | assert e.error == "webhook.not-found" 403 | assert e.message == ERRORS["webhook.not-found"] 404 | -------------------------------------------------------------------------------- /tests/v1/_sync/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TelePay-cash/telepay-python/50d0cee6945bb3b389fa1745f135c992c681a7d8/tests/v1/_sync/__init__.py -------------------------------------------------------------------------------- /tests/v1/_sync/test_client.py: -------------------------------------------------------------------------------- 1 | import os 2 | import uuid 3 | 4 | import pytest 5 | from httpx import Timeout 6 | from pytest import fixture 7 | 8 | from telepay.v1 import Invoice, TelePayAuth, TelePayError, TelePaySyncClient, Webhook 9 | 10 | from ..utils import ERRORS, random_text 11 | 12 | TIMEOUT = 60 13 | 14 | 15 | @fixture(name="client") 16 | def create_client(): 17 | client = TelePaySyncClient.from_auth(TelePayAuth(), timeout=Timeout(TIMEOUT)) 18 | yield client 19 | client.close() 20 | 21 | 22 | @fixture(name="invoice") 23 | def create_invoice(client: TelePaySyncClient): 24 | invoice = client.create_invoice( 25 | asset="TON", 26 | blockchain="TON", 27 | network="testnet", 28 | amount=1, 29 | description="Testing", 30 | metadata={"color": "red", "size": "large"}, 31 | success_url="https://example.com/success", 32 | cancel_url="https://example.com/cancel", 33 | expires_at=1, 34 | ) 35 | yield invoice 36 | 37 | 38 | @fixture(name="webhook") 39 | def create_webhook(client: TelePaySyncClient): 40 | webhook = client.create_webhook( 41 | url=f"https://{uuid.uuid4().hex}.com", 42 | secret="hello", 43 | events=["all"], 44 | active=False, 45 | ) 46 | yield webhook 47 | 48 | 49 | def test_error(client: TelePaySyncClient): 50 | client = TelePaySyncClient("") 51 | with pytest.raises(TelePayError): 52 | client.get_me() 53 | 54 | 55 | def test_client_with_context(): 56 | api_key = os.environ["TELEPAY_SECRET_API_KEY"] 57 | with TelePaySyncClient(secret_api_key=api_key) as client: 58 | assert client is not None 59 | 60 | 61 | def test_get_me(client: TelePaySyncClient): 62 | try: 63 | client.get_me() 64 | except TelePayError as e: 65 | if e.status_code == 403: 66 | assert e.error == "forbidden" 67 | assert e.message == ERRORS["forbidden"] 68 | 69 | 70 | def test_get_balance(client: TelePaySyncClient): 71 | try: 72 | client.get_balance() 73 | client.get_balance(asset="TON", blockchain="TON", network="testnet") 74 | except TelePayError as e: 75 | if e.status_code == 403: 76 | assert e.error == "forbidden" 77 | assert e.message == ERRORS["forbidden"] 78 | 79 | 80 | def test_get_asset(client: TelePaySyncClient): 81 | try: 82 | client.get_asset(asset="TON", blockchain="TON") 83 | except TelePayError as e: 84 | if e.status_code == 403: 85 | assert e.error == "forbidden" 86 | assert e.message == ERRORS["forbidden"] 87 | 88 | 89 | def test_get_assets(client: TelePaySyncClient): 90 | try: 91 | client.get_assets() 92 | except TelePayError as e: 93 | if e.status_code == 403: 94 | assert e.error == "forbidden" 95 | assert e.message == ERRORS["forbidden"] 96 | 97 | 98 | def test_get_invoices(client: TelePaySyncClient): 99 | try: 100 | client.get_invoices() 101 | except TelePayError as e: 102 | if e.status_code == 403: 103 | assert e.error == "forbidden" 104 | assert e.message == ERRORS["forbidden"] 105 | 106 | 107 | def test_get_invoice(client: TelePaySyncClient, invoice: Invoice): 108 | try: 109 | client.get_invoice(invoice.number) 110 | except TelePayError as e: 111 | if e.status_code == 403: 112 | assert e.error == "forbidden" 113 | assert e.message == ERRORS["forbidden"] 114 | 115 | 116 | def test_get_invoice_not_found(client: TelePaySyncClient): 117 | number = random_text(10) 118 | try: 119 | client.get_invoice(number) 120 | assert False 121 | except TelePayError as e: 122 | if e.status_code == 403: 123 | assert e.error == "forbidden" 124 | assert e.message == ERRORS["forbidden"] 125 | elif e.status_code == 404: 126 | assert e.error == "invoice.not-found" 127 | assert e.message == ERRORS["invoice.not-found"] 128 | 129 | 130 | def test_cancel_invoice(client: TelePaySyncClient, invoice: Invoice): 131 | try: 132 | client.cancel_invoice(invoice.number) 133 | except TelePayError as e: 134 | if e.status_code == 403: 135 | assert e.error == "forbidden" 136 | assert e.message == ERRORS["forbidden"] 137 | elif e.status_code == 404: 138 | assert e.error == "invoice.not-found" 139 | assert e.message == ERRORS["invoice.not-found"] 140 | 141 | 142 | def test_cancel_invoice_not_found(client: TelePaySyncClient): 143 | number = random_text(10) 144 | try: 145 | client.cancel_invoice(number) 146 | except TelePayError as e: 147 | if e.status_code == 403: 148 | assert e.error == "forbidden" 149 | assert e.message == ERRORS["forbidden"] 150 | elif e.status_code == 404: 151 | assert e.error == "invoice.not-found" 152 | assert e.message == ERRORS["invoice.not-found"] 153 | 154 | 155 | def test_delete_invoice(client: TelePaySyncClient, invoice: Invoice): 156 | try: 157 | client.cancel_invoice(invoice.number) 158 | client.delete_invoice(invoice.number) 159 | except TelePayError as e: 160 | if e.status_code == 403: 161 | assert e.error == "forbidden" 162 | assert e.message == ERRORS["forbidden"] 163 | elif e.status_code == 404: 164 | assert e.error == "invoice.not-found" 165 | assert e.message == ERRORS["invoice.not-found"] 166 | 167 | 168 | def test_delete_invoice_not_found(client: TelePaySyncClient): 169 | number = random_text(10) 170 | try: 171 | client.delete_invoice(number) 172 | assert False 173 | except TelePayError as e: 174 | if e.status_code == 403: 175 | assert e.error == "forbidden" 176 | assert e.message == ERRORS["forbidden"] 177 | elif e.status_code == 404: 178 | assert e.error == "invoice.not-found" 179 | assert e.message == ERRORS["invoice.not-found"] 180 | 181 | 182 | def test_transfer_without_funds(client: TelePaySyncClient): 183 | try: 184 | client.transfer( 185 | asset="TON", 186 | blockchain="TON", 187 | network="testnet", 188 | amount=1, 189 | username="telepay", 190 | ) 191 | except TelePayError as e: 192 | if e.status_code == 401: 193 | assert e.error == "transfer.insufficient-funds" 194 | assert e.message == ERRORS["transfer.insufficient-funds"] 195 | elif e.status_code == 403: 196 | assert e.error == "forbidden" 197 | assert e.message == ERRORS["forbidden"] 198 | elif e.status_code == 404: 199 | assert e.error == "invoice.not-found" 200 | assert e.message == ERRORS["invoice.not-found"] 201 | 202 | 203 | def test_transfer_to_wrong_user(client: TelePaySyncClient): 204 | username = random_text(20) 205 | try: 206 | client.transfer( 207 | asset="TON", 208 | blockchain="TON", 209 | network="testnet", 210 | amount=1, 211 | username=username, 212 | ) 213 | except TelePayError as e: 214 | if e.status_code == 401: 215 | assert e.error == "transfer.insufficient-funds" 216 | assert e.message == ERRORS["transfer.insufficient-funds"] 217 | elif e.status_code == 403: 218 | assert e.error == "forbidden" 219 | assert e.message == ERRORS["forbidden"] 220 | elif e.status_code == 404: 221 | assert e.error == "account.not-found" 222 | assert e.message == ERRORS["account.not-found"] 223 | 224 | 225 | def test_transfer_to_itself(client: TelePaySyncClient): 226 | account = client.get_me() 227 | username = account.merchant["username"] 228 | try: 229 | client.transfer( 230 | asset="TON", 231 | blockchain="TON", 232 | network="testnet", 233 | amount=1, 234 | username=username, 235 | ) 236 | except TelePayError as e: 237 | if e.status_code == 401: 238 | assert e.error == "transfer.not-possible" 239 | assert e.message == ERRORS["transfer.not-possible"] 240 | elif e.status_code == 403: 241 | assert e.error == "forbidden" 242 | assert e.message == ERRORS["forbidden"] 243 | 244 | 245 | def test_withdraw_minimum(client: TelePaySyncClient): 246 | try: 247 | client.get_withdraw_minimum( 248 | asset="TON", 249 | blockchain="TON", 250 | network="testnet", 251 | ) 252 | except TelePayError as e: 253 | if e.status_code == 403: 254 | assert e.error == "forbidden" 255 | assert e.message == ERRORS["forbidden"] 256 | 257 | 258 | def test_get_withdraw_fee(client: TelePaySyncClient): 259 | try: 260 | client.get_withdraw_fee( 261 | to_address="EQCKYK7bYBt1t8UmdhImrbiSzC5ijfo_H3Zc_Hk8ksRpOkOk", 262 | asset="TON", 263 | blockchain="TON", 264 | network="testnet", 265 | amount=1, 266 | message="test", 267 | ) 268 | except TelePayError as e: 269 | if e.status_code == 403: 270 | assert e.error == "forbidden" 271 | assert e.message == ERRORS["forbidden"] 272 | 273 | 274 | def test_withdraw(client: TelePaySyncClient): 275 | try: 276 | client.withdraw( 277 | to_address="EQCKYK7bYBt1t8UmdhImrbiSzC5ijfo_H3Zc_Hk8ksRpOkOk", 278 | asset="TON", 279 | blockchain="TON", 280 | network="testnet", 281 | amount=1, 282 | message="test", 283 | ) 284 | except TelePayError as e: 285 | if e.status_code == 503: 286 | assert e.error == "unavailable" 287 | assert e.message == ERRORS["unavailable"] 288 | elif e.status_code == 401: 289 | assert e.error == "withdrawal.insufficient-funds" 290 | assert e.message == ERRORS["withdrawal.insufficient-funds"] 291 | elif e.status_code == 403: 292 | assert e.error == "forbidden" 293 | assert e.message == ERRORS["forbidden"] 294 | 295 | 296 | def test_update_webhook(client: TelePaySyncClient, webhook: Webhook): 297 | try: 298 | client.update_webhook( 299 | id=webhook.id, 300 | url="https://example.com", 301 | secret="hello", 302 | events=["invoice.completed"], 303 | active=False, 304 | ) 305 | client.delete_webhook(id=webhook.id) 306 | except TelePayError as e: 307 | if e.status_code == 403: 308 | assert e.error == "forbidden" 309 | assert e.message == ERRORS["forbidden"] 310 | if e.status_code == 404: 311 | assert e.error == "webhook.not-found" 312 | assert e.message == ERRORS["webhook.not-found"] 313 | 314 | 315 | def test_activate_webhook(client: TelePaySyncClient, webhook: Webhook): 316 | try: 317 | client.activate_webhook(id=webhook.id) 318 | client.delete_webhook(id=webhook.id) 319 | except TelePayError as e: 320 | if e.status_code == 403: 321 | assert e.error == "forbidden" 322 | assert e.message == ERRORS["forbidden"] 323 | if e.status_code == 404: 324 | assert e.error == "webhook.not-found" 325 | assert e.message == ERRORS["webhook.not-found"] 326 | 327 | 328 | def test_deactivate_webhook(client: TelePaySyncClient, webhook: Webhook): 329 | try: 330 | client.deactivate_webhook(id=webhook.id) 331 | client.delete_webhook(id=webhook.id) 332 | except TelePayError as e: 333 | if e.status_code == 403: 334 | assert e.error == "forbidden" 335 | assert e.message == ERRORS["forbidden"] 336 | if e.status_code == 404: 337 | assert e.error == "webhook.not-found" 338 | assert e.message == ERRORS["webhook.not-found"] 339 | 340 | 341 | def test_delete_webhook(client: TelePaySyncClient, webhook: Webhook): 342 | try: 343 | client.delete_webhook(id=webhook.id) 344 | except TelePayError as e: 345 | if e.status_code == 403: 346 | assert e.error == "forbidden" 347 | assert e.message == ERRORS["forbidden"] 348 | if e.status_code == 404: 349 | assert e.error == "webhook.not-found" 350 | assert e.message == ERRORS["webhook.not-found"] 351 | 352 | 353 | def test_get_webhook(client: TelePaySyncClient, webhook: Webhook): 354 | try: 355 | client.get_webhook(id=webhook.id) 356 | client.delete_webhook(id=webhook.id) 357 | except TelePayError as e: 358 | if e.status_code == 403: 359 | assert e.error == "forbidden" 360 | assert e.message == ERRORS["forbidden"] 361 | if e.status_code == 404: 362 | assert e.error == "webhook.not-found" 363 | assert e.message == ERRORS["webhook.not-found"] 364 | 365 | 366 | def test_get_webhooks(client: TelePaySyncClient): 367 | try: 368 | client.get_webhooks() 369 | except TelePayError as e: 370 | if e.status_code == 403: 371 | assert e.error == "forbidden" 372 | assert e.message == ERRORS["forbidden"] 373 | if e.status_code == 404: 374 | assert e.error == "webhook.not-found" 375 | assert e.message == ERRORS["webhook.not-found"] 376 | -------------------------------------------------------------------------------- /tests/v1/utils.py: -------------------------------------------------------------------------------- 1 | import random 2 | import string 3 | 4 | ERRORS = { 5 | "forbidden": "You are not authorized to perform this action.", 6 | "unavailable": "This action is temporarly unavailable.", 7 | "account.not-found": "Account not found.", 8 | "invoice.not-found": "Invoice not found.", 9 | "transfer.insufficient-funds": "Transfer failed. Insufficient funds.", 10 | "transfer.not-possible": "Transfer failed. Not possible to perform.", 11 | "withdrawal.insufficient-funds": "Withdrawal failed. Insufficient funds.", 12 | "webhook.not-found": "Webhook not found.", 13 | } 14 | 15 | 16 | def random_text(length): 17 | chars = string.ascii_uppercase + string.digits 18 | return "".join(random.choice(chars) for _ in range(length)) 19 | --------------------------------------------------------------------------------