├── .all-contributorsrc ├── .coveragerc ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── ci.yml │ └── publish.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .vscode └── settings.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── banner.jpg ├── poetry.lock ├── pyproject.toml ├── qvapay ├── __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 │ ├── info.py │ ├── invoice.py │ ├── link.py │ ├── owner.py │ ├── paginated_transactions.py │ ├── paid_by.py │ ├── transaction.py │ └── transaction_detail.py │ └── utils.py └── tests ├── __init__.py └── v1 ├── __init__.py ├── _async ├── __init__.py └── test_client.py └── _sync ├── __init__.py └── test_client.py /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "qvapay-python", 3 | "projectOwner": "lugodev", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "imageSize": 100, 10 | "commit": true, 11 | "commitConvention": "gitmoji", 12 | "contributors": [ 13 | { 14 | "login": "lugodev", 15 | "name": "Carlos Lugones", 16 | "avatar_url": "https://avatars.githubusercontent.com/u/18733370?v=4", 17 | "profile": "https://bio.link/lugodev", 18 | "contributions": [ 19 | "code" 20 | ] 21 | }, 22 | { 23 | "login": "codeshard", 24 | "name": "Ozkar L. Garcell", 25 | "avatar_url": "https://avatars.githubusercontent.com/u/5880754?v=4", 26 | "profile": "http://codeshard.github.io/", 27 | "contributions": [ 28 | "code" 29 | ] 30 | }, 31 | { 32 | "login": "leynier", 33 | "name": "Leynier Gutiérrez González", 34 | "avatar_url": "https://avatars.githubusercontent.com/u/36774373?v=4", 35 | "profile": "https://github.com/leynier", 36 | "contributions": [ 37 | "code" 38 | ] 39 | }, 40 | { 41 | "login": "jorgeajimenezl", 42 | "name": "Jorge Alejandro Jimenez Luna", 43 | "avatar_url": "https://avatars.githubusercontent.com/u/18174581?v=4", 44 | "profile": "https://github.com/jorgeajimenezl", 45 | "contributions": [ 46 | "code" 47 | ] 48 | }, 49 | { 50 | "login": "ragnarok22", 51 | "name": "Reinier Hernández", 52 | "avatar_url": "https://avatars.githubusercontent.com/u/8838803?v=4", 53 | "profile": "https://blog.ragnarok22.dev", 54 | "contributions": [ 55 | "bug" 56 | ] 57 | } 58 | ], 59 | "contributorsPerLine": 7, 60 | "skipCi": true 61 | } 62 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | tests/* 4 | -------------------------------------------------------------------------------- /.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/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | 2 | name: CI 3 | on: [push, pull_request] 4 | jobs: 5 | CI: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | with: 10 | ref: ${{ github.event.pull_request.head.sha }} 11 | - name: Set up Python 3.8 12 | uses: actions/setup-python@v2 13 | with: 14 | python-version: 3.8 15 | - name: Set up Poetry 2.1.0 16 | uses: abatilo/actions-poetry@v2.1.0 17 | with: 18 | poetry-version: 1.1.2 19 | - name: Run tests 20 | env: 21 | QVAPAY_APP_ID: ${{ secrets.QVAPAY_APP_ID }} 22 | QVAPAY_APP_SECRET: ${{ secrets.QVAPAY_APP_SECRET }} 23 | run: make tests 24 | - name: Upload coverage 25 | uses: codecov/codecov-action@v1 26 | # with: 27 | # token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos 28 | -------------------------------------------------------------------------------- /.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 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Set up Python 3.8 12 | uses: actions/setup-python@v2 13 | with: 14 | python-version: 3.8 15 | - name: Set up Poetry 2.1.0 16 | uses: abatilo/actions-poetry@v2.1.0 17 | with: 18 | poetry-version: 1.1.2 19 | - name: Run tests 20 | env: 21 | QVAPAY_APP_ID: ${{ secrets.QVAPAY_APP_ID }} 22 | QVAPAY_APP_SECRET: ${{ secrets.QVAPAY_APP_SECRET }} 23 | run: make tests 24 | - name: Run build 25 | run: poetry build 26 | - name: Publish in GitHub Releases 27 | uses: svenstaro/upload-release-action@v2 28 | with: 29 | repo_token: ${{ secrets.GITHUB_TOKEN }} 30 | file: dist/* 31 | tag: ${{ github.ref }} 32 | overwrite: true 33 | file_glob: true 34 | - name: Publish in PyPI 35 | uses: d1618033/gh-action-python-publish-using-poetry@master 36 | with: 37 | pypi_username: ${{ secrets.PYPI_USERNAME }} 38 | pypi_password: ${{ secrets.PYPI_PASSWORD }} 39 | -------------------------------------------------------------------------------- /.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 | !.vscode/settings.json -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "markdownlint.config": { 3 | "MD024": false, 4 | "MD041": false 5 | }, 6 | "python.testing.pytestArgs": [ 7 | "tests" 8 | ], 9 | "python.testing.unittestEnabled": false, 10 | "python.testing.pytestEnabled": true 11 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v0.3.0 (2021-10-30) 2 | 3 | ### Fix 4 | 5 | - improve parsing method for evicting errors by fields aggregations 6 | 7 | ### Feat 8 | 9 | - integrate pre-commit for avoid format errors 10 | - split implementation in two classes 11 | 12 | ## v0.2.0 (2021-09-17) 13 | 14 | ### Fix 15 | 16 | - add aclose method for async with and use run_until_complete 17 | - remove user_id and paid_by_user_id properties from Transaction 18 | - add type hint in __enter__ method of QvaPayClient class 19 | 20 | ### Feat 21 | 22 | - add cache to GitHub Actions 23 | - add context manager; updated README 24 | 25 | ## v0.1.0 (2021-09-05) 26 | 27 | ### Feat 28 | 29 | - add post init validation to QvaPayAuth 30 | - add not required status message to QvaPayError 31 | - improve implementation 32 | 33 | ### Fix 34 | 35 | - change UUID by str in remote_id of transaction model 36 | - use python-dotenv for obtain authentication info from environment 37 | 38 | ### Perf 39 | 40 | - remove pydantic dependency 41 | 42 | ## v0.0.3 (2021-08-30) 43 | 44 | ### Perf 45 | 46 | - contributors 47 | - contributors & deps 48 | - added all contributors to README.md 49 | - improved client 50 | - preparing for publishing to pypi 51 | 52 | ### Fix 53 | 54 | - switched from stage to production 55 | - removed unused field 56 | - improved imports 57 | - improved imports 58 | - removed unused code 59 | - changed signed attr type 60 | - fixed invoice model attrs 61 | - fixed info model using alias values and str repr from datetimes 62 | - path and pep8 fixes 63 | - added vscode settings file 64 | - error class for qvapay requests 65 | - removed unused import 66 | 67 | ### Feat 68 | 69 | - added github dependabot 70 | - transaction endpoints 71 | - fixed transaction model 72 | - added alias to uuid field 73 | - using json dataclass 74 | - added link model 75 | - renamed folder to better understanding of the project layout 76 | - fixed info class and post init function 77 | - using dataclass decorator, PEP 557 PEP 526 78 | - qvapay client 79 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at leynier41@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Asynchronous QvaPay client for Python 2 | 3 | ## Contributions are highly appreciated 4 | 5 | You can help out by: 6 | 7 | * Reporting a bug 8 | * Reviewing the code 9 | * Submitting a fix 10 | * Proposing new features 11 | * Becoming a maintainer 12 | 13 | ## Branches 14 | 15 | We're using the following branches to manage work: 16 | 17 | * `develop` is semi-stable and should be used as the branch to fork from 18 | * `main` is stable and ready for prod (or it will be once we merge in the first release) 19 | * `feature`, `bug` branches: unstable development 20 | 21 | ## How to report bugs 22 | 23 | We use GitHub issues to track public bugs. Report a bug by [opening a new issue](https://github.com/leynier/aioqvapay/issues/new/choose); it's that easy! 24 | 25 | ### Write bug reports with detail, background, and sample code 26 | 27 | **Great Bug Reports** tend to have: 28 | 29 | * A quick summary and/or background 30 | * Steps to reproduce 31 | * Be specific! 32 | * Give sample code if you can. 33 | * What you expected to happen 34 | * What actually happens 35 | * Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) 36 | 37 | We <3 thorough bug reports. 38 | 39 | ## How to contribute code 40 | 41 | We use GitHub to host code, to track issues and feature requests, as well as accept pull requests. 42 | 43 | Pull requests are the best way to propose changes to the codebase. We use a loose version of Git Flow 44 | and actively welcome your pull requests: 45 | 46 | 1. Create a new branch based on `develop`: 47 | * Feature branches should start with `feature/` 48 | * Bugfix branches should start with `bug/` 49 | 2. Implement your changes. 50 | 3. Add tests if applicable. 51 | 4. Make sure your code lints. 52 | 5. Issue that pull request! 53 | 54 | ### Any contributions you make will be under the MIT License 55 | 56 | When you submit code changes, your submissions are understood to be under the same [MIT](LICENSE) that covers the project. 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Carlos Lugones 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 6 | poetry run black . --check 7 | poetry run isort . --profile=black 8 | poetry run pre-commit run --all-files 9 | poetry run pytest --cov=./ --cov-report=xml 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python SDK for the QvaPay API 2 | 3 | ![Banner](https://raw.githubusercontent.com/lugodev/qvapay-python/main/banner.jpg) 4 | 5 | Non official, but friendly QvaPay library for the Python language. 6 | 7 | [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) 8 | [![Test](https://github.com/lugodev/qvapay-python/workflows/CI/badge.svg)](https://github.com/lugodev/qvapay-python/actions?query=workflow%3ACI) 9 | [![codecov](https://codecov.io/gh/lugodev/qvapay-python/branch/main/graph/badge.svg)](https://codecov.io/gh/lugodev/qvapay-python) 10 | [![Version](https://img.shields.io/pypi/v/qvapay?color=%2334D058&label=Version)](https://pypi.org/project/qvapay) 11 | [![Last commit](https://img.shields.io/github/last-commit/lugodev/qvapay-python.svg?style=flat)](https://github.com/lugodev/qvapay-python/commits) 12 | [![GitHub commit activity](https://img.shields.io/github/commit-activity/m/lugodev/qvapay-python)](https://github.com/lugodev/qvapay-python/commits) 13 | [![Github Stars](https://img.shields.io/github/stars/lugodev/qvapay-python?style=flat&logo=github)](https://github.com/lugodev/qvapay-python/stargazers) 14 | [![Github Forks](https://img.shields.io/github/forks/lugodev/qvapay-python?style=flat&logo=github)](https://github.com/lugodev/qvapay-python/network/members) 15 | [![Github Watchers](https://img.shields.io/github/watchers/lugodev/qvapay-python?style=flat&logo=github)](https://github.com/lugodev/qvapay-python) 16 | [![GitHub contributors](https://img.shields.io/github/contributors/lugodev/qvapay-python?label=code%20contributors)](https://github.com/lugodev/qvapay-python/graphs/contributors) 17 | [![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors-) 18 | 19 | 20 | ## Setup 21 | 22 | You can install this package by using the pip tool and installing: 23 | 24 | ```bash 25 | pip install qvapay 26 | ``` 27 | 28 | Or 29 | 30 | ```bash 31 | easy_install qvapay 32 | ``` 33 | 34 | ## Sign up on **QvaPay** 35 | 36 | Create your account to process payments through **QvaPay** at [qvapay.com/register](https://qvapay.com/register). 37 | 38 | ## Using the client 39 | 40 | First, import the `AsyncQvaPayClient` (or `SyncQvaPayClient`) class and create your **QvaPay** asynchronous (or synchronous) client using your app credentials. 41 | 42 | ```python 43 | from qvapay.v1 import AsyncQvaPayClient 44 | 45 | client = AsyncQvaPayClient(app_id, app_secret) 46 | ``` 47 | 48 | It is also possible to use the `QvaPayAuth` class (which by default obtains its properties from environment variables or from the content of the `.env` file) and the static method `AsyncQvaPayClient.from_auth` (or `SyncQvaPayClient.from_auth`) to initialize the client. 49 | 50 | ```python 51 | from qvapay.v1 import AsyncQvaPayClient, QvaPayAuth 52 | 53 | client = AsyncQvaPayClient.from_auth(QvaPayAuth()) 54 | ``` 55 | 56 | ### Use context manager 57 | 58 | The recommended way to use a client is as a context manager. For example: 59 | 60 | ```python 61 | async with AsyncQvaPayClient(...) as client: 62 | # Do anything you want 63 | ... 64 | ``` 65 | 66 | or 67 | 68 | ```python 69 | with SyncQvaPayClient(...) as client: 70 | # Do anything you want 71 | ... 72 | ``` 73 | 74 | ### Get your app info 75 | 76 | ```python 77 | # Use await when using AsyncQvaPayClient 78 | # With SyncQvaPayClient it is not necessary. 79 | info = await client.get_info() 80 | ``` 81 | 82 | ### Get your account balance 83 | 84 | ```python 85 | # Use await when using AsyncQvaPayClient 86 | # With SyncQvaPayClient it is not necessary. 87 | balance = await client.get_balance() 88 | ``` 89 | 90 | ### Create an invoice 91 | 92 | ```python 93 | # Use await when using AsyncQvaPayClient 94 | # With SyncQvaPayClient it is not necessary. 95 | transaction = await client.create_invoice( 96 | amount=10, 97 | description='Ebook', 98 | remote_id='EE-BOOk-123' # example remote invoice id 99 | ) 100 | ``` 101 | 102 | ### Get transaction 103 | 104 | ```python 105 | # Use await when using AsyncQvaPayClient 106 | # With SyncQvaPayClient it is not necessary. 107 | transaction = await client.get_transaction(id) 108 | ``` 109 | 110 | ### Get transactions 111 | 112 | ```python 113 | # Use await when using AsyncQvaPayClient 114 | # With SyncQvaPayClient it is not necessary. 115 | transactions = await client.get_transactions(page=1) 116 | ``` 117 | 118 | You can also read the **QvaPay API** documentation: [qvapay.com/docs](https://qvapay.com/docs). 119 | 120 | ## For developers 121 | 122 | The `_sync` folders were generated automatically executing the command `unasync qvapay tests`. 123 | 124 | The code that is added in the `_async` folders is automatically transformed. 125 | 126 | So every time to make a change you must run the command `unasync qvapay tests` to regenerate the folders `_sync` with the synchronous version of the implementation. 127 | 128 | Improve `tests` implementation and add `pre-commit` system to ensure format and style. 129 | 130 | ## Migration guide 131 | 132 | ### 0.2.0 -> 0.3.0 133 | 134 | - `QvaPayClient` was divided into two classes: `AsyncQvaPayClient` and `SyncQvaPayClient`. Both classes have the same methods and properties, with the difference that the methods in `AsyncQvaPayClient` are asynchronous and in `SyncQvaPayClient` are synchronous. 135 | 136 | ### 0.1.0 -> 0.2.0 137 | 138 | - `user_id` of `Transaction` model was removed 139 | - `paid_by_user_id` of `Transaction` model was removed 140 | 141 | ### 0.0.3 -> 0.1.0 142 | 143 | - `from qvapay.v1 import *` instead of `from qvapay import *` 144 | - `QvaPayClient` instead of `Client` 145 | - `client.get_info` instead of `client.info` 146 | - `client.get_balance` instead of `client.balance` 147 | - `client.get_transactions` instead of `client.transactions` 148 | 149 | ## Contributors ✨ 150 | 151 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 |

Carlos Lugones

💻

Ozkar L. Garcell

💻

Leynier Gutiérrez González

💻

Jorge Alejandro Jimenez Luna

💻

Reinier Hernández

🐛
165 | 166 | 167 | 168 | 169 | 170 | 171 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 172 | 173 |

174 | 175 |

176 | -------------------------------------------------------------------------------- /banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ragnarok22/qvapay-python/5f260c5d711ffbf961463c905764d292eadf1182/banner.jpg -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "anyio" 3 | version = "3.3.4" 4 | description = "High level compatibility layer for multiple asynchronous event loop implementations" 5 | category = "main" 6 | optional = false 7 | python-versions = ">=3.6.2" 8 | 9 | [package.dependencies] 10 | idna = ">=2.8" 11 | sniffio = ">=1.1" 12 | trio = {version = ">=0.16", optional = true, markers = "extra == \"trio\""} 13 | 14 | [package.extras] 15 | doc = ["sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"] 16 | test = ["coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "pytest (>=6.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "mock (>=4)", "uvloop (>=0.15)"] 17 | trio = ["trio (>=0.16)"] 18 | 19 | [[package]] 20 | name = "argcomplete" 21 | version = "1.12.3" 22 | description = "Bash tab completion for argparse" 23 | category = "dev" 24 | optional = false 25 | python-versions = "*" 26 | 27 | [package.extras] 28 | test = ["coverage", "flake8", "pexpect", "wheel"] 29 | 30 | [[package]] 31 | name = "async-generator" 32 | version = "1.10" 33 | description = "Async generators and context managers for Python 3.5+" 34 | category = "dev" 35 | optional = false 36 | python-versions = ">=3.5" 37 | 38 | [[package]] 39 | name = "atomicwrites" 40 | version = "1.4.0" 41 | description = "Atomic file writes." 42 | category = "dev" 43 | optional = false 44 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 45 | 46 | [[package]] 47 | name = "attrs" 48 | version = "21.2.0" 49 | description = "Classes Without Boilerplate" 50 | category = "dev" 51 | optional = false 52 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 53 | 54 | [package.extras] 55 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] 56 | docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] 57 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] 58 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] 59 | 60 | [[package]] 61 | name = "backports.entry-points-selectable" 62 | version = "1.1.0" 63 | description = "Compatibility shim providing selectable entry points for older implementations" 64 | category = "dev" 65 | optional = false 66 | python-versions = ">=2.7" 67 | 68 | [package.extras] 69 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 70 | testing = ["pytest (>=4.6)", "pytest-flake8", "pytest-cov", "pytest-black (>=0.3.7)", "pytest-mypy", "pytest-checkdocs (>=2.4)", "pytest-enabler (>=1.0.1)"] 71 | 72 | [[package]] 73 | name = "black" 74 | version = "21.9b0" 75 | description = "The uncompromising code formatter." 76 | category = "dev" 77 | optional = false 78 | python-versions = ">=3.6.2" 79 | 80 | [package.dependencies] 81 | click = ">=7.1.2" 82 | mypy-extensions = ">=0.4.3" 83 | pathspec = ">=0.9.0,<1" 84 | platformdirs = ">=2" 85 | regex = ">=2020.1.8" 86 | tomli = ">=0.2.6,<2.0.0" 87 | typing-extensions = [ 88 | {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}, 89 | {version = "!=3.10.0.1", markers = "python_version >= \"3.10\""}, 90 | ] 91 | 92 | [package.extras] 93 | colorama = ["colorama (>=0.4.3)"] 94 | d = ["aiohttp (>=3.6.0)", "aiohttp-cors (>=0.4.0)"] 95 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] 96 | python2 = ["typed-ast (>=1.4.2)"] 97 | uvloop = ["uvloop (>=0.15.2)"] 98 | 99 | [[package]] 100 | name = "certifi" 101 | version = "2021.10.8" 102 | description = "Python package for providing Mozilla's CA Bundle." 103 | category = "main" 104 | optional = false 105 | python-versions = "*" 106 | 107 | [[package]] 108 | name = "cffi" 109 | version = "1.15.0" 110 | description = "Foreign Function Interface for Python calling C code." 111 | category = "dev" 112 | optional = false 113 | python-versions = "*" 114 | 115 | [package.dependencies] 116 | pycparser = "*" 117 | 118 | [[package]] 119 | name = "cfgv" 120 | version = "3.3.1" 121 | description = "Validate configuration and produce human readable error messages." 122 | category = "dev" 123 | optional = false 124 | python-versions = ">=3.6.1" 125 | 126 | [[package]] 127 | name = "charset-normalizer" 128 | version = "2.0.7" 129 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 130 | category = "main" 131 | optional = false 132 | python-versions = ">=3.5.0" 133 | 134 | [package.extras] 135 | unicode_backport = ["unicodedata2"] 136 | 137 | [[package]] 138 | name = "click" 139 | version = "8.0.3" 140 | description = "Composable command line interface toolkit" 141 | category = "dev" 142 | optional = false 143 | python-versions = ">=3.6" 144 | 145 | [package.dependencies] 146 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 147 | 148 | [[package]] 149 | name = "colorama" 150 | version = "0.4.4" 151 | description = "Cross-platform colored terminal text." 152 | category = "dev" 153 | optional = false 154 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 155 | 156 | [[package]] 157 | name = "commitizen" 158 | version = "2.20.0" 159 | description = "Python commitizen client tool" 160 | category = "dev" 161 | optional = false 162 | python-versions = ">=3.6.1,<4.0.0" 163 | 164 | [package.dependencies] 165 | argcomplete = ">=1.12.1,<2.0.0" 166 | colorama = ">=0.4.1,<0.5.0" 167 | decli = ">=0.5.2,<0.6.0" 168 | jinja2 = ">=2.10.3" 169 | packaging = ">=19,<22" 170 | pyyaml = ">=3.08" 171 | questionary = ">=1.4.0,<2.0.0" 172 | termcolor = ">=1.1,<2.0" 173 | tomlkit = ">=0.5.3,<1.0.0" 174 | 175 | [[package]] 176 | name = "coverage" 177 | version = "6.0.2" 178 | description = "Code coverage measurement for Python" 179 | category = "dev" 180 | optional = false 181 | python-versions = ">=3.6" 182 | 183 | [package.dependencies] 184 | tomli = {version = "*", optional = true, markers = "extra == \"toml\""} 185 | 186 | [package.extras] 187 | toml = ["tomli"] 188 | 189 | [[package]] 190 | name = "decli" 191 | version = "0.5.2" 192 | description = "Minimal, easy-to-use, declarative cli tool" 193 | category = "dev" 194 | optional = false 195 | python-versions = ">=3.6" 196 | 197 | [[package]] 198 | name = "distlib" 199 | version = "0.3.3" 200 | description = "Distribution utilities" 201 | category = "dev" 202 | optional = false 203 | python-versions = "*" 204 | 205 | [[package]] 206 | name = "filelock" 207 | version = "3.3.1" 208 | description = "A platform independent file lock." 209 | category = "dev" 210 | optional = false 211 | python-versions = ">=3.6" 212 | 213 | [package.extras] 214 | docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] 215 | testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] 216 | 217 | [[package]] 218 | name = "flake8" 219 | version = "4.0.1" 220 | description = "the modular source code checker: pep8 pyflakes and co" 221 | category = "dev" 222 | optional = false 223 | python-versions = ">=3.6" 224 | 225 | [package.dependencies] 226 | mccabe = ">=0.6.0,<0.7.0" 227 | pycodestyle = ">=2.8.0,<2.9.0" 228 | pyflakes = ">=2.4.0,<2.5.0" 229 | 230 | [[package]] 231 | name = "h11" 232 | version = "0.12.0" 233 | description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" 234 | category = "main" 235 | optional = false 236 | python-versions = ">=3.6" 237 | 238 | [[package]] 239 | name = "httpcore" 240 | version = "0.13.7" 241 | description = "A minimal low-level HTTP client." 242 | category = "main" 243 | optional = false 244 | python-versions = ">=3.6" 245 | 246 | [package.dependencies] 247 | anyio = ">=3.0.0,<4.0.0" 248 | h11 = ">=0.11,<0.13" 249 | sniffio = ">=1.0.0,<2.0.0" 250 | 251 | [package.extras] 252 | http2 = ["h2 (>=3,<5)"] 253 | 254 | [[package]] 255 | name = "httpx" 256 | version = "0.20.0" 257 | description = "The next generation HTTP client." 258 | category = "main" 259 | optional = false 260 | python-versions = ">=3.6" 261 | 262 | [package.dependencies] 263 | certifi = "*" 264 | charset-normalizer = "*" 265 | httpcore = ">=0.13.3,<0.14.0" 266 | rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} 267 | sniffio = "*" 268 | 269 | [package.extras] 270 | brotli = ["brotlicffi", "brotli"] 271 | cli = ["click (>=8.0.0,<9.0.0)", "rich (>=10.0.0,<11.0.0)", "pygments (>=2.0.0,<3.0.0)"] 272 | http2 = ["h2 (>=3,<5)"] 273 | 274 | [[package]] 275 | name = "identify" 276 | version = "2.3.0" 277 | description = "File identification library for Python" 278 | category = "dev" 279 | optional = false 280 | python-versions = ">=3.6.1" 281 | 282 | [package.extras] 283 | license = ["editdistance-s"] 284 | 285 | [[package]] 286 | name = "idna" 287 | version = "3.3" 288 | description = "Internationalized Domain Names in Applications (IDNA)" 289 | category = "main" 290 | optional = false 291 | python-versions = ">=3.5" 292 | 293 | [[package]] 294 | name = "iniconfig" 295 | version = "1.1.1" 296 | description = "iniconfig: brain-dead simple config-ini parsing" 297 | category = "dev" 298 | optional = false 299 | python-versions = "*" 300 | 301 | [[package]] 302 | name = "isort" 303 | version = "5.9.3" 304 | description = "A Python utility / library to sort Python imports." 305 | category = "dev" 306 | optional = false 307 | python-versions = ">=3.6.1,<4.0" 308 | 309 | [package.extras] 310 | pipfile_deprecated_finder = ["pipreqs", "requirementslib"] 311 | requirements_deprecated_finder = ["pipreqs", "pip-api"] 312 | colors = ["colorama (>=0.4.3,<0.5.0)"] 313 | plugins = ["setuptools"] 314 | 315 | [[package]] 316 | name = "jinja2" 317 | version = "3.0.2" 318 | description = "A very fast and expressive template engine." 319 | category = "dev" 320 | optional = false 321 | python-versions = ">=3.6" 322 | 323 | [package.dependencies] 324 | MarkupSafe = ">=2.0" 325 | 326 | [package.extras] 327 | i18n = ["Babel (>=2.7)"] 328 | 329 | [[package]] 330 | name = "markupsafe" 331 | version = "2.0.1" 332 | description = "Safely add untrusted strings to HTML/XML markup." 333 | category = "dev" 334 | optional = false 335 | python-versions = ">=3.6" 336 | 337 | [[package]] 338 | name = "mccabe" 339 | version = "0.6.1" 340 | description = "McCabe checker, plugin for flake8" 341 | category = "dev" 342 | optional = false 343 | python-versions = "*" 344 | 345 | [[package]] 346 | name = "mypy-extensions" 347 | version = "0.4.3" 348 | description = "Experimental type system extensions for programs checked with the mypy typechecker." 349 | category = "dev" 350 | optional = false 351 | python-versions = "*" 352 | 353 | [[package]] 354 | name = "nodeenv" 355 | version = "1.6.0" 356 | description = "Node.js virtual environment builder" 357 | category = "dev" 358 | optional = false 359 | python-versions = "*" 360 | 361 | [[package]] 362 | name = "outcome" 363 | version = "1.1.0" 364 | description = "Capture the outcome of Python function calls." 365 | category = "dev" 366 | optional = false 367 | python-versions = ">=3.6" 368 | 369 | [package.dependencies] 370 | attrs = ">=19.2.0" 371 | 372 | [[package]] 373 | name = "packaging" 374 | version = "21.0" 375 | description = "Core utilities for Python packages" 376 | category = "dev" 377 | optional = false 378 | python-versions = ">=3.6" 379 | 380 | [package.dependencies] 381 | pyparsing = ">=2.0.2" 382 | 383 | [[package]] 384 | name = "pathspec" 385 | version = "0.9.0" 386 | description = "Utility library for gitignore style pattern matching of file paths." 387 | category = "dev" 388 | optional = false 389 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 390 | 391 | [[package]] 392 | name = "platformdirs" 393 | version = "2.4.0" 394 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 395 | category = "dev" 396 | optional = false 397 | python-versions = ">=3.6" 398 | 399 | [package.extras] 400 | docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] 401 | test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] 402 | 403 | [[package]] 404 | name = "pluggy" 405 | version = "1.0.0" 406 | description = "plugin and hook calling mechanisms for python" 407 | category = "dev" 408 | optional = false 409 | python-versions = ">=3.6" 410 | 411 | [package.extras] 412 | dev = ["pre-commit", "tox"] 413 | testing = ["pytest", "pytest-benchmark"] 414 | 415 | [[package]] 416 | name = "pre-commit" 417 | version = "2.15.0" 418 | description = "A framework for managing and maintaining multi-language pre-commit hooks." 419 | category = "dev" 420 | optional = false 421 | python-versions = ">=3.6.1" 422 | 423 | [package.dependencies] 424 | cfgv = ">=2.0.0" 425 | identify = ">=1.0.0" 426 | nodeenv = ">=0.11.1" 427 | pyyaml = ">=5.1" 428 | toml = "*" 429 | virtualenv = ">=20.0.8" 430 | 431 | [[package]] 432 | name = "prompt-toolkit" 433 | version = "3.0.20" 434 | description = "Library for building powerful interactive command lines in Python" 435 | category = "dev" 436 | optional = false 437 | python-versions = ">=3.6.2" 438 | 439 | [package.dependencies] 440 | wcwidth = "*" 441 | 442 | [[package]] 443 | name = "py" 444 | version = "1.10.0" 445 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 446 | category = "dev" 447 | optional = false 448 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 449 | 450 | [[package]] 451 | name = "pycodestyle" 452 | version = "2.8.0" 453 | description = "Python style guide checker" 454 | category = "dev" 455 | optional = false 456 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 457 | 458 | [[package]] 459 | name = "pycparser" 460 | version = "2.20" 461 | description = "C parser in Python" 462 | category = "dev" 463 | optional = false 464 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 465 | 466 | [[package]] 467 | name = "pyflakes" 468 | version = "2.4.0" 469 | description = "passive checker of Python programs" 470 | category = "dev" 471 | optional = false 472 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 473 | 474 | [[package]] 475 | name = "pyparsing" 476 | version = "2.4.7" 477 | description = "Python parsing module" 478 | category = "dev" 479 | optional = false 480 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 481 | 482 | [[package]] 483 | name = "pytest" 484 | version = "6.2.5" 485 | description = "pytest: simple powerful testing with Python" 486 | category = "dev" 487 | optional = false 488 | python-versions = ">=3.6" 489 | 490 | [package.dependencies] 491 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 492 | attrs = ">=19.2.0" 493 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 494 | iniconfig = "*" 495 | packaging = "*" 496 | pluggy = ">=0.12,<2.0" 497 | py = ">=1.8.2" 498 | toml = "*" 499 | 500 | [package.extras] 501 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 502 | 503 | [[package]] 504 | name = "pytest-cov" 505 | version = "3.0.0" 506 | description = "Pytest plugin for measuring coverage." 507 | category = "dev" 508 | optional = false 509 | python-versions = ">=3.6" 510 | 511 | [package.dependencies] 512 | coverage = {version = ">=5.2.1", extras = ["toml"]} 513 | pytest = ">=4.6" 514 | 515 | [package.extras] 516 | testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] 517 | 518 | [[package]] 519 | name = "python-dateutil" 520 | version = "2.8.2" 521 | description = "Extensions to the standard Python datetime module" 522 | category = "main" 523 | optional = false 524 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 525 | 526 | [package.dependencies] 527 | six = ">=1.5" 528 | 529 | [[package]] 530 | name = "python-dotenv" 531 | version = "0.19.1" 532 | description = "Read key-value pairs from a .env file and set them as environment variables" 533 | category = "main" 534 | optional = false 535 | python-versions = ">=3.5" 536 | 537 | [package.extras] 538 | cli = ["click (>=5.0)"] 539 | 540 | [[package]] 541 | name = "pyyaml" 542 | version = "6.0" 543 | description = "YAML parser and emitter for Python" 544 | category = "dev" 545 | optional = false 546 | python-versions = ">=3.6" 547 | 548 | [[package]] 549 | name = "questionary" 550 | version = "1.10.0" 551 | description = "Python library to build pretty command line user prompts ⭐️" 552 | category = "dev" 553 | optional = false 554 | python-versions = ">=3.6,<4.0" 555 | 556 | [package.dependencies] 557 | prompt_toolkit = ">=2.0,<4.0" 558 | 559 | [package.extras] 560 | docs = ["Sphinx (>=3.3,<4.0)", "sphinx-rtd-theme (>=0.5.0,<0.6.0)", "sphinx-autobuild (>=2020.9.1,<2021.0.0)", "sphinx-copybutton (>=0.3.1,<0.4.0)", "sphinx-autodoc-typehints (>=1.11.1,<2.0.0)"] 561 | 562 | [[package]] 563 | name = "regex" 564 | version = "2021.10.8" 565 | description = "Alternative regular expression module, to replace re." 566 | category = "dev" 567 | optional = false 568 | python-versions = "*" 569 | 570 | [[package]] 571 | name = "rfc3986" 572 | version = "1.5.0" 573 | description = "Validating URI References per RFC 3986" 574 | category = "main" 575 | optional = false 576 | python-versions = "*" 577 | 578 | [package.dependencies] 579 | idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} 580 | 581 | [package.extras] 582 | idna2008 = ["idna"] 583 | 584 | [[package]] 585 | name = "six" 586 | version = "1.16.0" 587 | description = "Python 2 and 3 compatibility utilities" 588 | category = "main" 589 | optional = false 590 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 591 | 592 | [[package]] 593 | name = "sniffio" 594 | version = "1.2.0" 595 | description = "Sniff out which async library your code is running under" 596 | category = "main" 597 | optional = false 598 | python-versions = ">=3.5" 599 | 600 | [[package]] 601 | name = "sortedcontainers" 602 | version = "2.4.0" 603 | description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" 604 | category = "dev" 605 | optional = false 606 | python-versions = "*" 607 | 608 | [[package]] 609 | name = "termcolor" 610 | version = "1.1.0" 611 | description = "ANSII Color formatting for output in terminal." 612 | category = "dev" 613 | optional = false 614 | python-versions = "*" 615 | 616 | [[package]] 617 | name = "toml" 618 | version = "0.10.2" 619 | description = "Python Library for Tom's Obvious, Minimal Language" 620 | category = "dev" 621 | optional = false 622 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 623 | 624 | [[package]] 625 | name = "tomli" 626 | version = "1.2.1" 627 | description = "A lil' TOML parser" 628 | category = "dev" 629 | optional = false 630 | python-versions = ">=3.6" 631 | 632 | [[package]] 633 | name = "tomlkit" 634 | version = "0.7.2" 635 | description = "Style preserving TOML library" 636 | category = "dev" 637 | optional = false 638 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 639 | 640 | [[package]] 641 | name = "trio" 642 | version = "0.19.0" 643 | description = "A friendly Python library for async concurrency and I/O" 644 | category = "dev" 645 | optional = false 646 | python-versions = ">=3.6" 647 | 648 | [package.dependencies] 649 | async-generator = ">=1.9" 650 | attrs = ">=19.2.0" 651 | cffi = {version = ">=1.14", markers = "os_name == \"nt\" and implementation_name != \"pypy\""} 652 | idna = "*" 653 | outcome = "*" 654 | sniffio = "*" 655 | sortedcontainers = "*" 656 | 657 | [[package]] 658 | name = "typer" 659 | version = "0.4.0" 660 | description = "Typer, build great CLIs. Easy to code. Based on Python type hints." 661 | category = "dev" 662 | optional = false 663 | python-versions = ">=3.6" 664 | 665 | [package.dependencies] 666 | click = ">=7.1.1,<9.0.0" 667 | 668 | [package.extras] 669 | all = ["colorama (>=0.4.3,<0.5.0)", "shellingham (>=1.3.0,<2.0.0)"] 670 | dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)"] 671 | doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=5.4.0,<6.0.0)", "markdown-include (>=0.5.1,<0.6.0)"] 672 | test = ["shellingham (>=1.3.0,<2.0.0)", "pytest (>=4.4.0,<5.4.0)", "pytest-cov (>=2.10.0,<3.0.0)", "coverage (>=5.2,<6.0)", "pytest-xdist (>=1.32.0,<2.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "mypy (==0.910)", "black (>=19.10b0,<20.0b0)", "isort (>=5.0.6,<6.0.0)"] 673 | 674 | [[package]] 675 | name = "typing-extensions" 676 | version = "3.10.0.2" 677 | description = "Backported and Experimental Type Hints for Python 3.5+" 678 | category = "dev" 679 | optional = false 680 | python-versions = "*" 681 | 682 | [[package]] 683 | name = "unasync" 684 | version = "0.5.0" 685 | description = "The async transformation code." 686 | category = "dev" 687 | optional = false 688 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 689 | 690 | [[package]] 691 | name = "unasync-cli" 692 | version = "0.0.5" 693 | description = "Command line interface for unasync" 694 | category = "dev" 695 | optional = false 696 | python-versions = ">=3.6.14,<4.0.0" 697 | 698 | [package.dependencies] 699 | typer = ">=0.4.0,<0.5.0" 700 | unasync = ">=0.5.0,<0.6.0" 701 | 702 | [[package]] 703 | name = "virtualenv" 704 | version = "20.8.1" 705 | description = "Virtual Python Environment builder" 706 | category = "dev" 707 | optional = false 708 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 709 | 710 | [package.dependencies] 711 | "backports.entry-points-selectable" = ">=1.0.4" 712 | distlib = ">=0.3.1,<1" 713 | filelock = ">=3.0.0,<4" 714 | platformdirs = ">=2,<3" 715 | six = ">=1.9.0,<2" 716 | 717 | [package.extras] 718 | docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"] 719 | testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] 720 | 721 | [[package]] 722 | name = "wcwidth" 723 | version = "0.2.5" 724 | description = "Measures the displayed width of unicode strings in a terminal" 725 | category = "dev" 726 | optional = false 727 | python-versions = "*" 728 | 729 | [metadata] 730 | lock-version = "1.1" 731 | python-versions = "^3.8" 732 | content-hash = "bdea58177c1d77dabaf211b9e5f9bf18c6c141099be9cdf0b8ffb33694ea0eaa" 733 | 734 | [metadata.files] 735 | anyio = [ 736 | {file = "anyio-3.3.4-py3-none-any.whl", hash = "sha256:4fd09a25ab7fa01d34512b7249e366cd10358cdafc95022c7ff8c8f8a5026d66"}, 737 | {file = "anyio-3.3.4.tar.gz", hash = "sha256:67da67b5b21f96b9d3d65daa6ea99f5d5282cb09f50eb4456f8fb51dffefc3ff"}, 738 | ] 739 | argcomplete = [ 740 | {file = "argcomplete-1.12.3-py2.py3-none-any.whl", hash = "sha256:291f0beca7fd49ce285d2f10e4c1c77e9460cf823eef2de54df0c0fec88b0d81"}, 741 | {file = "argcomplete-1.12.3.tar.gz", hash = "sha256:2c7dbffd8c045ea534921e63b0be6fe65e88599990d8dc408ac8c542b72a5445"}, 742 | ] 743 | async-generator = [ 744 | {file = "async_generator-1.10-py3-none-any.whl", hash = "sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b"}, 745 | {file = "async_generator-1.10.tar.gz", hash = "sha256:6ebb3d106c12920aaae42ccb6f787ef5eefdcdd166ea3d628fa8476abe712144"}, 746 | ] 747 | atomicwrites = [ 748 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 749 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 750 | ] 751 | attrs = [ 752 | {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, 753 | {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, 754 | ] 755 | "backports.entry-points-selectable" = [ 756 | {file = "backports.entry_points_selectable-1.1.0-py2.py3-none-any.whl", hash = "sha256:a6d9a871cde5e15b4c4a53e3d43ba890cc6861ec1332c9c2428c92f977192acc"}, 757 | {file = "backports.entry_points_selectable-1.1.0.tar.gz", hash = "sha256:988468260ec1c196dab6ae1149260e2f5472c9110334e5d51adcb77867361f6a"}, 758 | ] 759 | black = [ 760 | {file = "black-21.9b0-py3-none-any.whl", hash = "sha256:380f1b5da05e5a1429225676655dddb96f5ae8c75bdf91e53d798871b902a115"}, 761 | {file = "black-21.9b0.tar.gz", hash = "sha256:7de4cfc7eb6b710de325712d40125689101d21d25283eed7e9998722cf10eb91"}, 762 | ] 763 | certifi = [ 764 | {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, 765 | {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, 766 | ] 767 | cffi = [ 768 | {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"}, 769 | {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"}, 770 | {file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"}, 771 | {file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"}, 772 | {file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"}, 773 | {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"}, 774 | {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"}, 775 | {file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"}, 776 | {file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"}, 777 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"}, 778 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"}, 779 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"}, 780 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"}, 781 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"}, 782 | {file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"}, 783 | {file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"}, 784 | {file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"}, 785 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"}, 786 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"}, 787 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"}, 788 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"}, 789 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"}, 790 | {file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"}, 791 | {file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"}, 792 | {file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"}, 793 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"}, 794 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"}, 795 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"}, 796 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"}, 797 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"}, 798 | {file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"}, 799 | {file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"}, 800 | {file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"}, 801 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"}, 802 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"}, 803 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"}, 804 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"}, 805 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"}, 806 | {file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"}, 807 | {file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"}, 808 | {file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"}, 809 | {file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"}, 810 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"}, 811 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"}, 812 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"}, 813 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"}, 814 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"}, 815 | {file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"}, 816 | {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"}, 817 | {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, 818 | ] 819 | cfgv = [ 820 | {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, 821 | {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, 822 | ] 823 | charset-normalizer = [ 824 | {file = "charset-normalizer-2.0.7.tar.gz", hash = "sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0"}, 825 | {file = "charset_normalizer-2.0.7-py3-none-any.whl", hash = "sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b"}, 826 | ] 827 | click = [ 828 | {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, 829 | {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, 830 | ] 831 | colorama = [ 832 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 833 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 834 | ] 835 | commitizen = [ 836 | {file = "commitizen-2.20.0-py3-none-any.whl", hash = "sha256:a8c9f75718f0507d703c3b3aeef43bebc3ed0979c8995f9214185956a1bc1c05"}, 837 | {file = "commitizen-2.20.0.tar.gz", hash = "sha256:b52eb35ffbe8281fc3187e648fae2bdd75ed1d17d31c8a0592909ccb7278292f"}, 838 | ] 839 | coverage = [ 840 | {file = "coverage-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1549e1d08ce38259de2bc3e9a0d5f3642ff4a8f500ffc1b2df73fd621a6cdfc0"}, 841 | {file = "coverage-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcae10fccb27ca2a5f456bf64d84110a5a74144be3136a5e598f9d9fb48c0caa"}, 842 | {file = "coverage-6.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:53a294dc53cfb39c74758edaa6305193fb4258a30b1f6af24b360a6c8bd0ffa7"}, 843 | {file = "coverage-6.0.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8251b37be1f2cd9c0e5ccd9ae0380909c24d2a5ed2162a41fcdbafaf59a85ebd"}, 844 | {file = "coverage-6.0.2-cp310-cp310-win32.whl", hash = "sha256:db42baa892cba723326284490283a68d4de516bfb5aaba369b4e3b2787a778b7"}, 845 | {file = "coverage-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:bbffde2a68398682623d9dd8c0ca3f46fda074709b26fcf08ae7a4c431a6ab2d"}, 846 | {file = "coverage-6.0.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:60e51a3dd55540bec686d7fff61b05048ca31e804c1f32cbb44533e6372d9cc3"}, 847 | {file = "coverage-6.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a6a9409223a27d5ef3cca57dd7cd4dfcb64aadf2fad5c3b787830ac9223e01a"}, 848 | {file = "coverage-6.0.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4b34ae4f51bbfa5f96b758b55a163d502be3dcb24f505d0227858c2b3f94f5b9"}, 849 | {file = "coverage-6.0.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3bbda1b550e70fa6ac40533d3f23acd4f4e9cb4e6e77251ce77fdf41b3309fb2"}, 850 | {file = "coverage-6.0.2-cp36-cp36m-win32.whl", hash = "sha256:4e28d2a195c533b58fc94a12826f4431726d8eb029ac21d874345f943530c122"}, 851 | {file = "coverage-6.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a82d79586a0a4f5fd1cf153e647464ced402938fbccb3ffc358c7babd4da1dd9"}, 852 | {file = "coverage-6.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3be1206dc09fb6298de3fce70593e27436862331a85daee36270b6d0e1c251c4"}, 853 | {file = "coverage-6.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9cd3828bbe1a40070c11fe16a51df733fd2f0cb0d745fb83b7b5c1f05967df7"}, 854 | {file = "coverage-6.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d036dc1ed8e1388e995833c62325df3f996675779541f682677efc6af71e96cc"}, 855 | {file = "coverage-6.0.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:04560539c19ec26995ecfb3d9307ff154fbb9a172cb57e3b3cfc4ced673103d1"}, 856 | {file = "coverage-6.0.2-cp37-cp37m-win32.whl", hash = "sha256:e4fb7ced4d9dec77d6cf533acfbf8e1415fe799430366affb18d69ee8a3c6330"}, 857 | {file = "coverage-6.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:77b1da5767ed2f44611bc9bc019bc93c03fa495728ec389759b6e9e5039ac6b1"}, 858 | {file = "coverage-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:61b598cbdbaae22d9e34e3f675997194342f866bb1d781da5d0be54783dce1ff"}, 859 | {file = "coverage-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36e9040a43d2017f2787b28d365a4bb33fcd792c7ff46a047a04094dc0e2a30d"}, 860 | {file = "coverage-6.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9f1627e162e3864a596486774876415a7410021f4b67fd2d9efdf93ade681afc"}, 861 | {file = "coverage-6.0.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e7a0b42db2a47ecb488cde14e0f6c7679a2c5a9f44814393b162ff6397fcdfbb"}, 862 | {file = "coverage-6.0.2-cp38-cp38-win32.whl", hash = "sha256:a1b73c7c4d2a42b9d37dd43199c5711d91424ff3c6c22681bc132db4a4afec6f"}, 863 | {file = "coverage-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:1db67c497688fd4ba85b373b37cc52c50d437fd7267520ecd77bddbd89ea22c9"}, 864 | {file = "coverage-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f2f184bf38e74f152eed7f87e345b51f3ab0b703842f447c22efe35e59942c24"}, 865 | {file = "coverage-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd1cf1deb3d5544bd942356364a2fdc8959bad2b6cf6eb17f47d301ea34ae822"}, 866 | {file = "coverage-6.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ad9b8c1206ae41d46ec7380b78ba735ebb77758a650643e841dd3894966c31d0"}, 867 | {file = "coverage-6.0.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:381d773d896cc7f8ba4ff3b92dee4ed740fb88dfe33b6e42efc5e8ab6dfa1cfe"}, 868 | {file = "coverage-6.0.2-cp39-cp39-win32.whl", hash = "sha256:424c44f65e8be58b54e2b0bd1515e434b940679624b1b72726147cfc6a9fc7ce"}, 869 | {file = "coverage-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:abbff240f77347d17306d3201e14431519bf64495648ca5a49571f988f88dee9"}, 870 | {file = "coverage-6.0.2-pp36-none-any.whl", hash = "sha256:7092eab374346121805fb637572483270324407bf150c30a3b161fc0c4ca5164"}, 871 | {file = "coverage-6.0.2-pp37-none-any.whl", hash = "sha256:30922626ce6f7a5a30bdba984ad21021529d3d05a68b4f71ea3b16bda35b8895"}, 872 | {file = "coverage-6.0.2.tar.gz", hash = "sha256:6807947a09510dc31fa86f43595bf3a14017cd60bf633cc746d52141bfa6b149"}, 873 | ] 874 | decli = [ 875 | {file = "decli-0.5.2-py3-none-any.whl", hash = "sha256:d3207bc02d0169bf6ed74ccca09ce62edca0eb25b0ebf8bf4ae3fb8333e15ca0"}, 876 | {file = "decli-0.5.2.tar.gz", hash = "sha256:f2cde55034a75c819c630c7655a844c612f2598c42c21299160465df6ad463ad"}, 877 | ] 878 | distlib = [ 879 | {file = "distlib-0.3.3-py2.py3-none-any.whl", hash = "sha256:c8b54e8454e5bf6237cc84c20e8264c3e991e824ef27e8f1e81049867d861e31"}, 880 | {file = "distlib-0.3.3.zip", hash = "sha256:d982d0751ff6eaaab5e2ec8e691d949ee80eddf01a62eaa96ddb11531fe16b05"}, 881 | ] 882 | filelock = [ 883 | {file = "filelock-3.3.1-py3-none-any.whl", hash = "sha256:2b5eb3589e7fdda14599e7eb1a50e09b4cc14f34ed98b8ba56d33bfaafcbef2f"}, 884 | {file = "filelock-3.3.1.tar.gz", hash = "sha256:34a9f35f95c441e7b38209775d6e0337f9a3759f3565f6c5798f19618527c76f"}, 885 | ] 886 | flake8 = [ 887 | {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, 888 | {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, 889 | ] 890 | h11 = [ 891 | {file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"}, 892 | {file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"}, 893 | ] 894 | httpcore = [ 895 | {file = "httpcore-0.13.7-py3-none-any.whl", hash = "sha256:369aa481b014cf046f7067fddd67d00560f2f00426e79569d99cb11245134af0"}, 896 | {file = "httpcore-0.13.7.tar.gz", hash = "sha256:036f960468759e633574d7c121afba48af6419615d36ab8ede979f1ad6276fa3"}, 897 | ] 898 | httpx = [ 899 | {file = "httpx-0.20.0-py3-none-any.whl", hash = "sha256:33af5aad9bdc82ef1fc89219c1e36f5693bf9cd0ebe330884df563445682c0f8"}, 900 | {file = "httpx-0.20.0.tar.gz", hash = "sha256:09606d630f070d07f9ff28104fbcea429ea0014c1e89ac90b4d8de8286c40e7b"}, 901 | ] 902 | identify = [ 903 | {file = "identify-2.3.0-py2.py3-none-any.whl", hash = "sha256:d1e82c83d063571bb88087676f81261a4eae913c492dafde184067c584bc7c05"}, 904 | {file = "identify-2.3.0.tar.gz", hash = "sha256:fd08c97f23ceee72784081f1ce5125c8f53a02d3f2716dde79a6ab8f1039fea5"}, 905 | ] 906 | idna = [ 907 | {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, 908 | {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, 909 | ] 910 | iniconfig = [ 911 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 912 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 913 | ] 914 | isort = [ 915 | {file = "isort-5.9.3-py3-none-any.whl", hash = "sha256:e17d6e2b81095c9db0a03a8025a957f334d6ea30b26f9ec70805411e5c7c81f2"}, 916 | {file = "isort-5.9.3.tar.gz", hash = "sha256:9c2ea1e62d871267b78307fe511c0838ba0da28698c5732d54e2790bf3ba9899"}, 917 | ] 918 | jinja2 = [ 919 | {file = "Jinja2-3.0.2-py3-none-any.whl", hash = "sha256:8569982d3f0889eed11dd620c706d39b60c36d6d25843961f33f77fb6bc6b20c"}, 920 | {file = "Jinja2-3.0.2.tar.gz", hash = "sha256:827a0e32839ab1600d4eb1c4c33ec5a8edfbc5cb42dafa13b81f182f97784b45"}, 921 | ] 922 | markupsafe = [ 923 | {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, 924 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, 925 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, 926 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, 927 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, 928 | {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, 929 | {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, 930 | {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, 931 | {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, 932 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"}, 933 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"}, 934 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, 935 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, 936 | {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, 937 | {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, 938 | {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, 939 | {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, 940 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, 941 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, 942 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, 943 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, 944 | {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, 945 | {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, 946 | {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, 947 | {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, 948 | {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"}, 949 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"}, 950 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"}, 951 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, 952 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, 953 | {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, 954 | {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, 955 | {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, 956 | {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, 957 | ] 958 | mccabe = [ 959 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 960 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 961 | ] 962 | mypy-extensions = [ 963 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 964 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 965 | ] 966 | nodeenv = [ 967 | {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"}, 968 | {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, 969 | ] 970 | outcome = [ 971 | {file = "outcome-1.1.0-py2.py3-none-any.whl", hash = "sha256:c7dd9375cfd3c12db9801d080a3b63d4b0a261aa996c4c13152380587288d958"}, 972 | {file = "outcome-1.1.0.tar.gz", hash = "sha256:e862f01d4e626e63e8f92c38d1f8d5546d3f9cce989263c521b2e7990d186967"}, 973 | ] 974 | packaging = [ 975 | {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, 976 | {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, 977 | ] 978 | pathspec = [ 979 | {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, 980 | {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, 981 | ] 982 | platformdirs = [ 983 | {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"}, 984 | {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"}, 985 | ] 986 | pluggy = [ 987 | {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, 988 | {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, 989 | ] 990 | pre-commit = [ 991 | {file = "pre_commit-2.15.0-py2.py3-none-any.whl", hash = "sha256:a4ed01000afcb484d9eb8d504272e642c4c4099bbad3a6b27e519bd6a3e928a6"}, 992 | {file = "pre_commit-2.15.0.tar.gz", hash = "sha256:3c25add78dbdfb6a28a651780d5c311ac40dd17f160eb3954a0c59da40a505a7"}, 993 | ] 994 | prompt-toolkit = [ 995 | {file = "prompt_toolkit-3.0.20-py3-none-any.whl", hash = "sha256:6076e46efae19b1e0ca1ec003ed37a933dc94b4d20f486235d436e64771dcd5c"}, 996 | {file = "prompt_toolkit-3.0.20.tar.gz", hash = "sha256:eb71d5a6b72ce6db177af4a7d4d7085b99756bf656d98ffcc4fecd36850eea6c"}, 997 | ] 998 | py = [ 999 | {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, 1000 | {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, 1001 | ] 1002 | pycodestyle = [ 1003 | {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, 1004 | {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, 1005 | ] 1006 | pycparser = [ 1007 | {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, 1008 | {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, 1009 | ] 1010 | pyflakes = [ 1011 | {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, 1012 | {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, 1013 | ] 1014 | pyparsing = [ 1015 | {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, 1016 | {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, 1017 | ] 1018 | pytest = [ 1019 | {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, 1020 | {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, 1021 | ] 1022 | pytest-cov = [ 1023 | {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, 1024 | {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, 1025 | ] 1026 | python-dateutil = [ 1027 | {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, 1028 | {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, 1029 | ] 1030 | python-dotenv = [ 1031 | {file = "python-dotenv-0.19.1.tar.gz", hash = "sha256:14f8185cc8d494662683e6914addcb7e95374771e707601dfc70166946b4c4b8"}, 1032 | {file = "python_dotenv-0.19.1-py2.py3-none-any.whl", hash = "sha256:bbd3da593fc49c249397cbfbcc449cf36cb02e75afc8157fcc6a81df6fb7750a"}, 1033 | ] 1034 | pyyaml = [ 1035 | {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, 1036 | {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, 1037 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, 1038 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, 1039 | {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"}, 1040 | {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, 1041 | {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, 1042 | {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, 1043 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, 1044 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, 1045 | {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"}, 1046 | {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, 1047 | {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, 1048 | {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, 1049 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, 1050 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, 1051 | {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"}, 1052 | {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, 1053 | {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, 1054 | {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, 1055 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, 1056 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, 1057 | {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"}, 1058 | {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, 1059 | {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, 1060 | {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, 1061 | {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, 1062 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, 1063 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, 1064 | {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"}, 1065 | {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, 1066 | {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, 1067 | {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, 1068 | ] 1069 | questionary = [ 1070 | {file = "questionary-1.10.0-py3-none-any.whl", hash = "sha256:fecfcc8cca110fda9d561cb83f1e97ecbb93c613ff857f655818839dac74ce90"}, 1071 | {file = "questionary-1.10.0.tar.gz", hash = "sha256:600d3aefecce26d48d97eee936fdb66e4bc27f934c3ab6dd1e292c4f43946d90"}, 1072 | ] 1073 | regex = [ 1074 | {file = "regex-2021.10.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:981c786293a3115bc14c103086ae54e5ee50ca57f4c02ce7cf1b60318d1e8072"}, 1075 | {file = "regex-2021.10.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51feefd58ac38eb91a21921b047da8644155e5678e9066af7bcb30ee0dca7361"}, 1076 | {file = "regex-2021.10.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea8de658d7db5987b11097445f2b1f134400e2232cb40e614e5f7b6f5428710e"}, 1077 | {file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1ce02f420a7ec3b2480fe6746d756530f69769292eca363218c2291d0b116a01"}, 1078 | {file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39079ebf54156be6e6902f5c70c078f453350616cfe7bfd2dd15bdb3eac20ccc"}, 1079 | {file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ff24897f6b2001c38a805d53b6ae72267025878d35ea225aa24675fbff2dba7f"}, 1080 | {file = "regex-2021.10.8-cp310-cp310-win32.whl", hash = "sha256:c6569ba7b948c3d61d27f04e2b08ebee24fec9ff8e9ea154d8d1e975b175bfa7"}, 1081 | {file = "regex-2021.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:45cb0f7ff782ef51bc79e227a87e4e8f24bc68192f8de4f18aae60b1d60bc152"}, 1082 | {file = "regex-2021.10.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fab3ab8aedfb443abb36729410403f0fe7f60ad860c19a979d47fb3eb98ef820"}, 1083 | {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74e55f8d66f1b41d44bc44c891bcf2c7fad252f8f323ee86fba99d71fd1ad5e3"}, 1084 | {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d52c5e089edbdb6083391faffbe70329b804652a53c2fdca3533e99ab0580d9"}, 1085 | {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1abbd95cbe9e2467cac65c77b6abd9223df717c7ae91a628502de67c73bf6838"}, 1086 | {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9b5c215f3870aa9b011c00daeb7be7e1ae4ecd628e9beb6d7e6107e07d81287"}, 1087 | {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f540f153c4f5617bc4ba6433534f8916d96366a08797cbbe4132c37b70403e92"}, 1088 | {file = "regex-2021.10.8-cp36-cp36m-win32.whl", hash = "sha256:1f51926db492440e66c89cd2be042f2396cf91e5b05383acd7372b8cb7da373f"}, 1089 | {file = "regex-2021.10.8-cp36-cp36m-win_amd64.whl", hash = "sha256:5f55c4804797ef7381518e683249310f7f9646da271b71cb6b3552416c7894ee"}, 1090 | {file = "regex-2021.10.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb2baff66b7d2267e07ef71e17d01283b55b3cc51a81b54cc385e721ae172ba4"}, 1091 | {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e527ab1c4c7cf2643d93406c04e1d289a9d12966529381ce8163c4d2abe4faf"}, 1092 | {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c98b013273e9da5790ff6002ab326e3f81072b4616fd95f06c8fa733d2745f"}, 1093 | {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:55ef044899706c10bc0aa052f2fc2e58551e2510694d6aae13f37c50f3f6ff61"}, 1094 | {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0ab3530a279a3b7f50f852f1bab41bc304f098350b03e30a3876b7dd89840e"}, 1095 | {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a37305eb3199d8f0d8125ec2fb143ba94ff6d6d92554c4b8d4a8435795a6eccd"}, 1096 | {file = "regex-2021.10.8-cp37-cp37m-win32.whl", hash = "sha256:2efd47704bbb016136fe34dfb74c805b1ef5c7313aef3ce6dcb5ff844299f432"}, 1097 | {file = "regex-2021.10.8-cp37-cp37m-win_amd64.whl", hash = "sha256:924079d5590979c0e961681507eb1773a142553564ccae18d36f1de7324e71ca"}, 1098 | {file = "regex-2021.10.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b09d3904bf312d11308d9a2867427479d277365b1617e48ad09696fa7dfcdf59"}, 1099 | {file = "regex-2021.10.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f125fce0a0ae4fd5c3388d369d7a7d78f185f904c90dd235f7ecf8fe13fa741"}, 1100 | {file = "regex-2021.10.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f199419a81c1016e0560c39773c12f0bd924c37715bffc64b97140d2c314354"}, 1101 | {file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:09e1031e2059abd91177c302da392a7b6859ceda038be9e015b522a182c89e4f"}, 1102 | {file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c070d5895ac6aeb665bd3cd79f673775caf8d33a0b569e98ac434617ecea57d"}, 1103 | {file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:176796cb7f82a7098b0c436d6daac82f57b9101bb17b8e8119c36eecf06a60a3"}, 1104 | {file = "regex-2021.10.8-cp38-cp38-win32.whl", hash = "sha256:5e5796d2f36d3c48875514c5cd9e4325a1ca172fc6c78b469faa8ddd3d770593"}, 1105 | {file = "regex-2021.10.8-cp38-cp38-win_amd64.whl", hash = "sha256:e4204708fa116dd03436a337e8e84261bc8051d058221ec63535c9403a1582a1"}, 1106 | {file = "regex-2021.10.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b8b6ee6555b6fbae578f1468b3f685cdfe7940a65675611365a7ea1f8d724991"}, 1107 | {file = "regex-2021.10.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:973499dac63625a5ef9dfa4c791aa33a502ddb7615d992bdc89cf2cc2285daa3"}, 1108 | {file = "regex-2021.10.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88dc3c1acd3f0ecfde5f95c32fcb9beda709dbdf5012acdcf66acbc4794468eb"}, 1109 | {file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4786dae85c1f0624ac77cb3813ed99267c9adb72e59fdc7297e1cf4d6036d493"}, 1110 | {file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe6ce4f3d3c48f9f402da1ceb571548133d3322003ce01b20d960a82251695d2"}, 1111 | {file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9e3e2cea8f1993f476a6833ef157f5d9e8c75a59a8d8b0395a9a6887a097243b"}, 1112 | {file = "regex-2021.10.8-cp39-cp39-win32.whl", hash = "sha256:82cfb97a36b1a53de32b642482c6c46b6ce80803854445e19bc49993655ebf3b"}, 1113 | {file = "regex-2021.10.8-cp39-cp39-win_amd64.whl", hash = "sha256:b04e512eb628ea82ed86eb31c0f7fc6842b46bf2601b66b1356a7008327f7700"}, 1114 | {file = "regex-2021.10.8.tar.gz", hash = "sha256:26895d7c9bbda5c52b3635ce5991caa90fbb1ddfac9c9ff1c7ce505e2282fb2a"}, 1115 | ] 1116 | rfc3986 = [ 1117 | {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, 1118 | {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, 1119 | ] 1120 | six = [ 1121 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 1122 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 1123 | ] 1124 | sniffio = [ 1125 | {file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"}, 1126 | {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"}, 1127 | ] 1128 | sortedcontainers = [ 1129 | {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, 1130 | {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, 1131 | ] 1132 | termcolor = [ 1133 | {file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"}, 1134 | ] 1135 | toml = [ 1136 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 1137 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 1138 | ] 1139 | tomli = [ 1140 | {file = "tomli-1.2.1-py3-none-any.whl", hash = "sha256:8dd0e9524d6f386271a36b41dbf6c57d8e32fd96fd22b6584679dc569d20899f"}, 1141 | {file = "tomli-1.2.1.tar.gz", hash = "sha256:a5b75cb6f3968abb47af1b40c1819dc519ea82bcc065776a866e8d74c5ca9442"}, 1142 | ] 1143 | tomlkit = [ 1144 | {file = "tomlkit-0.7.2-py2.py3-none-any.whl", hash = "sha256:173ad840fa5d2aac140528ca1933c29791b79a374a0861a80347f42ec9328117"}, 1145 | {file = "tomlkit-0.7.2.tar.gz", hash = "sha256:d7a454f319a7e9bd2e249f239168729327e4dd2d27b17dc68be264ad1ce36754"}, 1146 | ] 1147 | trio = [ 1148 | {file = "trio-0.19.0-py3-none-any.whl", hash = "sha256:c27c231e66336183c484fbfe080fa6cc954149366c15dc21db8b7290081ec7b8"}, 1149 | {file = "trio-0.19.0.tar.gz", hash = "sha256:895e318e5ec5e8cea9f60b473b6edb95b215e82d99556a03eb2d20c5e027efe1"}, 1150 | ] 1151 | typer = [ 1152 | {file = "typer-0.4.0-py3-none-any.whl", hash = "sha256:d81169725140423d072df464cad1ff25ee154ef381aaf5b8225352ea187ca338"}, 1153 | {file = "typer-0.4.0.tar.gz", hash = "sha256:63c3aeab0549750ffe40da79a1b524f60e08a2cbc3126c520ebf2eeaf507f5dd"}, 1154 | ] 1155 | typing-extensions = [ 1156 | {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, 1157 | {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, 1158 | {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, 1159 | ] 1160 | unasync = [ 1161 | {file = "unasync-0.5.0-py3-none-any.whl", hash = "sha256:8d4536dae85e87b8751dfcc776f7656fd0baf54bb022a7889440dc1b9dc3becb"}, 1162 | {file = "unasync-0.5.0.tar.gz", hash = "sha256:b675d87cf56da68bd065d3b7a67ac71df85591978d84c53083c20d79a7e5096d"}, 1163 | ] 1164 | unasync-cli = [ 1165 | {file = "unasync-cli-0.0.5.tar.gz", hash = "sha256:5c5704e42d451192a8618da8317375b7643dfa032d97a2c10644a381c85a6828"}, 1166 | {file = "unasync_cli-0.0.5-py3-none-any.whl", hash = "sha256:355dd9c72dbc573db7cd37b6fcdb6a449ea952806d17022e71491151a8bcfae9"}, 1167 | ] 1168 | virtualenv = [ 1169 | {file = "virtualenv-20.8.1-py2.py3-none-any.whl", hash = "sha256:10062e34c204b5e4ec5f62e6ef2473f8ba76513a9a617e873f1f8fb4a519d300"}, 1170 | {file = "virtualenv-20.8.1.tar.gz", hash = "sha256:bcc17f0b3a29670dd777d6f0755a4c04f28815395bca279cdcb213b97199a6b8"}, 1171 | ] 1172 | wcwidth = [ 1173 | {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, 1174 | {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, 1175 | ] 1176 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "qvapay" 3 | version = "0.3.0" 4 | description = "Python SDK for the QvaPay API" 5 | authors = [ 6 | "Carlos Lugones ", 7 | "Ozkar L. Garcell ", 8 | "Leynier Gutiérrez González " 9 | ] 10 | homepage = "https://github.com/lugodev/qvapay-python" 11 | repository = "https://github.com/lugodev/qvapay-python" 12 | documentation = "https://github.com/lugodev/qvapay-python" 13 | readme = "README.md" 14 | license = "MIT" 15 | keywords = ["QvaPay", "api", "payments"] 16 | classifiers = [ 17 | "Development Status :: 4 - Beta", 18 | "Intended Audience :: Developers", 19 | "License :: OSI Approved :: MIT License", 20 | "Programming Language :: Python :: 2", 21 | "Programming Language :: Python :: 3", 22 | ] 23 | 24 | [tool.poetry.urls] 25 | "Tracker" = "https://github.com/lugodev/qvapay-python/issues" 26 | 27 | [tool.poetry.dependencies] 28 | python = "^3.8" 29 | httpx = "^0.20.0" 30 | python-dateutil = "^2.8.2" 31 | python-dotenv = "^0.19.1" 32 | 33 | [tool.poetry.dev-dependencies] 34 | pytest = "^6.2.5" 35 | flake8 = "^4.0.1" 36 | black = "^21.9b0" 37 | isort = "^5.9.3" 38 | pytest-cov = "^3.0.0" 39 | commitizen = "^2.20.0" 40 | anyio = {extras = ["trio"], version = "^3.3.4"} 41 | unasync-cli = "^0.0.5" 42 | pre-commit = "^2.15.0" 43 | 44 | [build-system] 45 | requires = ["poetry-core>=1.0.0"] 46 | build-backend = "poetry.core.masonry.api" 47 | -------------------------------------------------------------------------------- /qvapay/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ragnarok22/qvapay-python/5f260c5d711ffbf961463c905764d292eadf1182/qvapay/__init__.py -------------------------------------------------------------------------------- /qvapay/v1/__init__.py: -------------------------------------------------------------------------------- 1 | from ._async.client import AsyncQvaPayClient # noqa: F401 2 | from ._sync.client import SyncQvaPayClient # noqa: F401 3 | from .auth import QvaPayAuth # noqa: F401 4 | from .errors import QvaPayError # noqa: F401 5 | from .models.info import Info # noqa: F401 6 | from .models.invoice import Invoice # noqa: F401 7 | from .models.owner import Owner # noqa: F401 8 | from .models.paginated_transactions import PaginatedTransactions # noqa: F401 9 | from .models.paid_by import PaidBy # noqa: F401 10 | from .models.transaction import Transaction # noqa: F401 11 | from .models.transaction_detail import TransactionDetail # noqa: F401 12 | 13 | __version__ = "0.3.0" 14 | __author__ = "Carlos Lugones " 15 | __all__ = [] 16 | -------------------------------------------------------------------------------- /qvapay/v1/_async/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ragnarok22/qvapay-python/5f260c5d711ffbf961463c905764d292eadf1182/qvapay/v1/_async/__init__.py -------------------------------------------------------------------------------- /qvapay/v1/_async/client.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | from typing import Union 3 | from uuid import UUID 4 | 5 | from httpx._config import DEFAULT_TIMEOUT_CONFIG 6 | from httpx._types import TimeoutTypes 7 | 8 | from ..auth import QvaPayAuth 9 | from ..http_clients import AsyncClient 10 | from ..models.info import Info 11 | from ..models.invoice import Invoice 12 | from ..models.paginated_transactions import PaginatedTransactions 13 | from ..models.transaction_detail import TransactionDetail 14 | from ..utils import validate_response 15 | 16 | 17 | @dataclass 18 | class AsyncQvaPayClient: 19 | """ 20 | Creates a QvaPay client. 21 | * app_id: QvaPay app id. 22 | * app_secret: QvaPay app secret. 23 | Get your app credentials at: https://qvapay.com/apps/create 24 | """ 25 | 26 | app_id: str 27 | app_secret: str 28 | timeout: TimeoutTypes = field(default=DEFAULT_TIMEOUT_CONFIG) 29 | 30 | def __post_init__(self): 31 | self.auth_params = {"app_id": self.app_id, "app_secret": self.app_secret} 32 | self.base_url = "https://qvapay.com/api/v1" 33 | self.http_client = AsyncClient( 34 | base_url=self.base_url, 35 | params=self.auth_params, 36 | timeout=self.timeout, 37 | ) 38 | 39 | async def __aenter__(self) -> "AsyncQvaPayClient": 40 | return self 41 | 42 | async def __aexit__(self, exc_t, exc_v, 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( 50 | auth: QvaPayAuth, 51 | timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, 52 | ) -> "AsyncQvaPayClient": 53 | return AsyncQvaPayClient(auth.qvapay_app_id, auth.qvapay_app_secret, timeout) 54 | 55 | async def get_info(self) -> Info: 56 | """ 57 | Get info relating to your QvaPay app. 58 | https://qvapay.com/docs/1.0/app_info 59 | """ 60 | response = await self.http_client.get("info") 61 | validate_response(response) 62 | return Info.from_json(response.json()) 63 | 64 | async def get_balance(self) -> float: 65 | """ 66 | Get your QvaPay balance. 67 | https://qvapay.com/docs/1.0/balance 68 | """ 69 | response = await self.http_client.get("balance") 70 | validate_response(response) 71 | return float(response.json()) 72 | 73 | async def get_transactions(self, page: int = 1) -> PaginatedTransactions: 74 | """ 75 | Gets transactions list, paginated by 50 items per request. 76 | * page: Page to be fetched. 77 | https://qvapay.com/docs/1.0/transactions 78 | """ 79 | response = await self.http_client.get("transactions", params={"page": page}) 80 | validate_response(response) 81 | return PaginatedTransactions.from_json(response.json()) 82 | 83 | async def get_transaction(self, id: Union[str, UUID]) -> TransactionDetail: 84 | """ 85 | Gets a transaction by its id (uuid). 86 | * id: Transaction uuid returned by QvaPay when created. 87 | https://qvapay.com/docs/1.0/transaction 88 | """ 89 | response = await self.http_client.get(f"transaction/{id}") 90 | validate_response(response) 91 | return TransactionDetail.from_json(response.json()) 92 | 93 | async def create_invoice( 94 | self, 95 | amount: float, 96 | description: str, 97 | remote_id: str, 98 | signed: bool = False, 99 | ) -> Invoice: 100 | """ 101 | Creates an invoice. 102 | * amount: Amount of money to receive to your wallet, expressed in dollars with 103 | two decimals. 104 | * description: Description of the invoice to be created, useful to show info to 105 | the user who pays. Max 300 chars. 106 | * remote_id: Invoice ID on your side (example: in your e-commerce store). 107 | Optional. 108 | * signed: Generates a signed URL, valid for 30 minutes. Useful to increase 109 | security, introducing an expiration datetime. Optional. 110 | https://qvapay.com/docs/1.0/create_invoice 111 | """ 112 | params = { 113 | "amount": amount, 114 | "description": description, 115 | "remote_id": remote_id, 116 | "signed": int(signed), 117 | } 118 | response = await self.http_client.get("create_invoice", params=params) 119 | validate_response(response) 120 | return Invoice.from_json(response.json()) 121 | -------------------------------------------------------------------------------- /qvapay/v1/_sync/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ragnarok22/qvapay-python/5f260c5d711ffbf961463c905764d292eadf1182/qvapay/v1/_sync/__init__.py -------------------------------------------------------------------------------- /qvapay/v1/_sync/client.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | from typing import Union 3 | from uuid import UUID 4 | 5 | from httpx._config import DEFAULT_TIMEOUT_CONFIG 6 | from httpx._types import TimeoutTypes 7 | 8 | from ..auth import QvaPayAuth 9 | from ..http_clients import SyncClient 10 | from ..models.info import Info 11 | from ..models.invoice import Invoice 12 | from ..models.paginated_transactions import PaginatedTransactions 13 | from ..models.transaction_detail import TransactionDetail 14 | from ..utils import validate_response 15 | 16 | 17 | @dataclass 18 | class SyncQvaPayClient: 19 | """ 20 | Creates a QvaPay client. 21 | * app_id: QvaPay app id. 22 | * app_secret: QvaPay app secret. 23 | Get your app credentials at: https://qvapay.com/apps/create 24 | """ 25 | 26 | app_id: str 27 | app_secret: str 28 | timeout: TimeoutTypes = field(default=DEFAULT_TIMEOUT_CONFIG) 29 | 30 | def __post_init__(self): 31 | self.auth_params = {"app_id": self.app_id, "app_secret": self.app_secret} 32 | self.base_url = "https://qvapay.com/api/v1" 33 | self.http_client = SyncClient( 34 | base_url=self.base_url, 35 | params=self.auth_params, 36 | timeout=self.timeout, 37 | ) 38 | 39 | def __enter__(self) -> "SyncQvaPayClient": 40 | return self 41 | 42 | def __exit__(self, exc_t, exc_v, exc_tb) -> None: 43 | self.close() 44 | 45 | def close(self) -> None: 46 | self.http_client.aclose() 47 | 48 | @staticmethod 49 | def from_auth( 50 | auth: QvaPayAuth, 51 | timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, 52 | ) -> "SyncQvaPayClient": 53 | return SyncQvaPayClient(auth.qvapay_app_id, auth.qvapay_app_secret, timeout) 54 | 55 | def get_info(self) -> Info: 56 | """ 57 | Get info relating to your QvaPay app. 58 | https://qvapay.com/docs/1.0/app_info 59 | """ 60 | response = self.http_client.get("info") 61 | validate_response(response) 62 | return Info.from_json(response.json()) 63 | 64 | def get_balance(self) -> float: 65 | """ 66 | Get your QvaPay balance. 67 | https://qvapay.com/docs/1.0/balance 68 | """ 69 | response = self.http_client.get("balance") 70 | validate_response(response) 71 | return float(response.json()) 72 | 73 | def get_transactions(self, page: int = 1) -> PaginatedTransactions: 74 | """ 75 | Gets transactions list, paginated by 50 items per request. 76 | * page: Page to be fetched. 77 | https://qvapay.com/docs/1.0/transactions 78 | """ 79 | response = self.http_client.get("transactions", params={"page": page}) 80 | validate_response(response) 81 | return PaginatedTransactions.from_json(response.json()) 82 | 83 | def get_transaction(self, id: Union[str, UUID]) -> TransactionDetail: 84 | """ 85 | Gets a transaction by its id (uuid). 86 | * id: Transaction uuid returned by QvaPay when created. 87 | https://qvapay.com/docs/1.0/transaction 88 | """ 89 | response = self.http_client.get(f"transaction/{id}") 90 | validate_response(response) 91 | return TransactionDetail.from_json(response.json()) 92 | 93 | def create_invoice( 94 | self, 95 | amount: float, 96 | description: str, 97 | remote_id: str, 98 | signed: bool = False, 99 | ) -> Invoice: 100 | """ 101 | Creates an invoice. 102 | * amount: Amount of money to receive to your wallet, expressed in dollars with 103 | two decimals. 104 | * description: Description of the invoice to be created, useful to show info to 105 | the user who pays. Max 300 chars. 106 | * remote_id: Invoice ID on your side (example: in your e-commerce store). 107 | Optional. 108 | * signed: Generates a signed URL, valid for 30 minutes. Useful to increase 109 | security, introducing an expiration datetime. Optional. 110 | https://qvapay.com/docs/1.0/create_invoice 111 | """ 112 | params = { 113 | "amount": amount, 114 | "description": description, 115 | "remote_id": remote_id, 116 | "signed": int(signed), 117 | } 118 | response = self.http_client.get("create_invoice", params=params) 119 | validate_response(response) 120 | return Invoice.from_json(response.json()) 121 | -------------------------------------------------------------------------------- /qvapay/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 QvaPayError 7 | 8 | load_dotenv() 9 | 10 | 11 | @dataclass 12 | class QvaPayAuth: 13 | qvapay_app_id: str = field(default=environ["QVAPAY_APP_ID"]) 14 | qvapay_app_secret: str = field(default=environ["QVAPAY_APP_SECRET"]) 15 | 16 | def __post_init__(self): 17 | if not self.qvapay_app_id and not self.qvapay_app_secret: 18 | raise QvaPayError(0, "QVAPAY_APP_ID and QVAPAY_APP_SECRET are not setted") 19 | elif not self.qvapay_app_id: 20 | raise QvaPayError(0, "QVAPAY_APP_ID is not setted") 21 | elif not self.qvapay_app_secret: 22 | raise QvaPayError(0, "QVAPAY_APP_SECRET is not setted") 23 | -------------------------------------------------------------------------------- /qvapay/v1/errors.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | from typing import Optional 3 | 4 | 5 | @dataclass 6 | class QvaPayError(Exception): 7 | status_code: int 8 | status_message: Optional[str] = field(default=None) 9 | -------------------------------------------------------------------------------- /qvapay/v1/http_clients.py: -------------------------------------------------------------------------------- 1 | from httpx import AsyncClient # noqa: F401 2 | from httpx import Client as BaseClient # noqa: F401 3 | 4 | 5 | class SyncClient(BaseClient): 6 | def aclose(self) -> None: 7 | self.close() 8 | -------------------------------------------------------------------------------- /qvapay/v1/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ragnarok22/qvapay-python/5f260c5d711ffbf961463c905764d292eadf1182/qvapay/v1/models/__init__.py -------------------------------------------------------------------------------- /qvapay/v1/models/info.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from datetime import datetime 3 | from typing import Any 4 | from uuid import UUID 5 | 6 | from dateutil.parser import parse 7 | 8 | from ..utils import parse_json 9 | 10 | 11 | @dataclass 12 | class Info: 13 | """ 14 | QvaPay app info 15 | """ 16 | 17 | id: UUID # alias: uuid 18 | user_id: int 19 | name: str 20 | url: str # AnyUrl 21 | description: str # alias: desc 22 | callback: str 23 | success_url: str # AnyUrl 24 | cancel_url: str # AnyUrl 25 | logo: str 26 | active: bool 27 | enabled: bool 28 | card: int 29 | created_at: datetime 30 | updated_at: datetime 31 | 32 | def __post_init__(self): 33 | self.id = UUID(str(self.id)) 34 | self.user_id = int(str(self.user_id)) 35 | self.name = str(self.name) 36 | self.url = str(self.url) 37 | self.description = str(self.description) 38 | self.callback = str(self.callback) 39 | self.success_url = str(self.success_url) 40 | self.cancel_url = str(self.cancel_url) 41 | self.logo = str(self.logo) 42 | self.active = bool(str(self.active)) 43 | self.enabled = bool(str(self.enabled)) 44 | self.card = int(str(self.card)) 45 | self.created_at = parse(str(self.created_at)) 46 | self.updated_at = parse(str(self.updated_at)) 47 | 48 | @classmethod 49 | def from_json(cls, json: Any) -> "Info": 50 | json["id"] = json["uuid"] 51 | json["description"] = json["desc"] 52 | del json["uuid"] 53 | del json["desc"] 54 | return parse_json(cls, **json) 55 | -------------------------------------------------------------------------------- /qvapay/v1/models/invoice.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Any 3 | from uuid import UUID 4 | 5 | from ..utils import parse_json 6 | 7 | 8 | @dataclass 9 | class Invoice: 10 | """ 11 | QvaPay invoice 12 | """ 13 | 14 | app_id: UUID 15 | transation_id: UUID # alias: transation_uuid 16 | amount: float 17 | description: str 18 | remote_id: str 19 | signed: int 20 | url: str # AnyUrl 21 | signed_url: str # AnyUrl alias: signedUrl 22 | 23 | def __post_init__(self): 24 | self.app_id = UUID(str(self.app_id)) 25 | self.transation_id = UUID(str(self.transation_id)) 26 | self.amount = float(str(self.amount)) 27 | self.description = str(self.description) 28 | self.remote_id = str(self.remote_id) 29 | self.signed = int(str(self.signed)) 30 | self.url = str(self.url) 31 | self.signed_url = str(self.signed_url) 32 | 33 | @classmethod 34 | def from_json(cls, json: Any) -> "Invoice": 35 | json["transation_id"] = json["transation_uuid"] 36 | json["signed_url"] = json["signedUrl"] 37 | del json["transation_uuid"] 38 | del json["signedUrl"] 39 | return parse_json(cls, **json) 40 | -------------------------------------------------------------------------------- /qvapay/v1/models/link.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Any, Optional 3 | 4 | from ..utils import parse_json 5 | 6 | 7 | @dataclass 8 | class Link: 9 | url: Optional[str] # Optional[AnyUrl] 10 | label: str 11 | active: bool 12 | 13 | def __post_init__(self): 14 | self.url = str(self.url) if self.url is not None else None 15 | self.label = str(self.label) 16 | self.active = bool(str(self.active)) 17 | 18 | @classmethod 19 | def from_json(cls, json: Any) -> "Link": 20 | return parse_json(cls, **json) 21 | -------------------------------------------------------------------------------- /qvapay/v1/models/owner.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Any, Optional 3 | from uuid import UUID 4 | 5 | from ..utils import parse_json 6 | 7 | 8 | @dataclass 9 | class Owner: 10 | id: UUID # alias: uuid 11 | username: str 12 | lastname: str 13 | name: str 14 | logo: str 15 | kyc: bool 16 | bio: Optional[str] 17 | 18 | def __post_init__(self): 19 | self.id = UUID(str(self.id)) 20 | self.username = str(self.username) 21 | self.lastname = str(self.lastname) 22 | self.name = str(self.name) 23 | self.logo = str(self.logo) 24 | self.kyc = bool(str(self.kyc)) 25 | self.bio = str(self.bio) if self.bio is not None else None 26 | 27 | @classmethod 28 | def from_json(cls, json: Any) -> "Owner": 29 | json["id"] = json["uuid"] 30 | del json["uuid"] 31 | return parse_json(cls, **json) 32 | -------------------------------------------------------------------------------- /qvapay/v1/models/paginated_transactions.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Any, List, Optional 3 | 4 | from ..utils import parse_json 5 | from .link import Link 6 | from .transaction import Transaction 7 | 8 | 9 | @dataclass 10 | class PaginatedTransactions: 11 | current_page: int 12 | last_page: int 13 | from_index: int # alias: from 14 | to_index: int # alias: to 15 | per_page: int 16 | total: int 17 | first_page_url: str # AnyUrl 18 | last_page_url: str # AnyUrl 19 | prev_page_url: Optional[str] # Optional[AnyUrl] 20 | next_page_url: Optional[str] # Optional[AnyUrl] 21 | path: str 22 | links: List[Link] 23 | data: List[Transaction] 24 | 25 | def __post_init__(self): 26 | self.current_page = int(str(self.current_page)) 27 | self.last_page = int(str(self.last_page)) 28 | self.from_index = int(str(self.from_index)) 29 | self.to_index = int(str(self.to_index)) 30 | self.per_page = int(str(self.per_page)) 31 | self.total = int(str(self.total)) 32 | self.first_page_url = str(self.first_page_url) 33 | self.last_page_url = str(self.last_page_url) 34 | self.prev_page_url = ( 35 | str(self.prev_page_url) if self.prev_page_url is not None else None 36 | ) 37 | self.next_page_url = ( 38 | str(self.next_page_url) if self.next_page_url is not None else None 39 | ) 40 | self.path = str(self.path) 41 | for link in self.links: 42 | link.__post_init__() 43 | for item in self.data: 44 | item.__post_init__() 45 | 46 | @classmethod 47 | def from_json(cls, json: Any) -> "PaginatedTransactions": 48 | json["from_index"] = json["from"] 49 | json["to_index"] = json["to"] 50 | del json["from"] 51 | del json["to"] 52 | links: List[Link] = [] 53 | data: List[Transaction] = [] 54 | for item in json["links"]: 55 | links.append(Link.from_json(item)) 56 | for item in json["data"]: 57 | data.append(Transaction.from_json(item)) 58 | del json["links"] 59 | del json["data"] 60 | return parse_json(cls, **json, links=links, data=data) 61 | -------------------------------------------------------------------------------- /qvapay/v1/models/paid_by.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Any 3 | 4 | from ..utils import parse_json 5 | 6 | 7 | @dataclass 8 | class PaidBy: 9 | username: str 10 | name: str 11 | logo: str 12 | 13 | def __post_init__(self): 14 | self.username = str(self.username) 15 | self.name = str(self.name) 16 | self.logo = str(self.logo) 17 | 18 | @classmethod 19 | def from_json(cls, json: Any) -> "PaidBy": 20 | return parse_json(cls, **json) 21 | -------------------------------------------------------------------------------- /qvapay/v1/models/transaction.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from datetime import datetime 3 | from typing import Any, Optional 4 | from uuid import UUID 5 | 6 | from dateutil.parser import parse 7 | 8 | from ..utils import parse_json 9 | 10 | 11 | @dataclass 12 | class Transaction: 13 | """ 14 | QvaPay transaction 15 | """ 16 | 17 | id: UUID # alias: uuid 18 | app_id: int 19 | amount: float 20 | description: str 21 | remote_id: str 22 | status: str 23 | created_at: datetime 24 | updated_at: datetime 25 | signed: Optional[int] 26 | 27 | def __post_init__(self): 28 | self.id = UUID(str(self.id)) 29 | self.app_id = int(str(self.app_id)) 30 | self.amount = float(str(self.amount)) 31 | self.description = str(self.description) 32 | self.remote_id = str(self.remote_id) 33 | self.status = str(self.status) 34 | self.created_at = parse(str(self.created_at)) 35 | self.updated_at = parse(str(self.updated_at)) 36 | self.signed = int(str(self.signed)) if self.signed is not None else None 37 | 38 | @classmethod 39 | def from_json(cls, json: Any) -> "Transaction": 40 | json["id"] = json["uuid"] 41 | del json["uuid"] 42 | signed = json["signed"] if "signed" in json else None 43 | return parse_json(cls, **json, signed=signed) 44 | -------------------------------------------------------------------------------- /qvapay/v1/models/transaction_detail.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Any 3 | 4 | from .info import Info 5 | from .owner import Owner 6 | from .paid_by import PaidBy 7 | from .transaction import Transaction 8 | 9 | 10 | @dataclass 11 | class TransactionDetail(Transaction): 12 | """ 13 | QvaPay transaction 14 | """ 15 | 16 | paid_by: PaidBy 17 | app: Info 18 | owner: Owner 19 | 20 | def __post_init__(self): 21 | self.paid_by.__post_init__() 22 | self.app.__post_init__() 23 | self.owner.__post_init__() 24 | return super().__post_init__() 25 | 26 | @classmethod 27 | def from_json(cls, json: Any) -> "TransactionDetail": 28 | paid_by = PaidBy.from_json(json["paid_by"]) 29 | app = Info.from_json(json["app"]) 30 | owner = Owner.from_json(json["owner"]) 31 | del json["paid_by"] 32 | del json["app"] 33 | del json["owner"] 34 | base = Transaction.from_json(json) 35 | return TransactionDetail( 36 | id=base.id, 37 | app_id=base.app_id, 38 | amount=base.amount, 39 | description=base.description, 40 | remote_id=base.remote_id, 41 | status=base.status, 42 | created_at=base.created_at, 43 | updated_at=base.updated_at, 44 | signed=base.signed, 45 | paid_by=paid_by, 46 | app=app, 47 | owner=owner, 48 | ) 49 | -------------------------------------------------------------------------------- /qvapay/v1/utils.py: -------------------------------------------------------------------------------- 1 | from inspect import signature 2 | from typing import Any, Type, TypeVar 3 | 4 | from httpx import Response 5 | 6 | from .errors import QvaPayError 7 | 8 | T = TypeVar("T") 9 | 10 | 11 | def validate_response(response: Response) -> None: 12 | if response.status_code != 200: 13 | raise QvaPayError(response.status_code) 14 | 15 | 16 | def parse_json(cls: Type[T], **json: Any) -> T: 17 | cls_fields = {field for field in signature(cls).parameters} 18 | native_args, new_args = {}, {} 19 | for name, val in json.items(): 20 | if name in cls_fields: 21 | native_args[name] = val 22 | else: 23 | new_args[name] = val 24 | ret = cls(**native_args) 25 | for new_name, new_val in new_args.items(): 26 | setattr(ret, new_name, new_val) 27 | return ret 28 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ragnarok22/qvapay-python/5f260c5d711ffbf961463c905764d292eadf1182/tests/__init__.py -------------------------------------------------------------------------------- /tests/v1/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ragnarok22/qvapay-python/5f260c5d711ffbf961463c905764d292eadf1182/tests/v1/__init__.py -------------------------------------------------------------------------------- /tests/v1/_async/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ragnarok22/qvapay-python/5f260c5d711ffbf961463c905764d292eadf1182/tests/v1/_async/__init__.py -------------------------------------------------------------------------------- /tests/v1/_async/test_client.py: -------------------------------------------------------------------------------- 1 | from random import random 2 | from uuid import uuid4 3 | 4 | from httpx import Timeout 5 | from pytest import fixture 6 | from pytest import mark as pytest_mark 7 | 8 | from qvapay.v1 import AsyncQvaPayClient, QvaPayAuth, QvaPayError 9 | 10 | TIMEOUT = 20 11 | 12 | 13 | @fixture(name="client") 14 | async def create_client(): 15 | client = AsyncQvaPayClient.from_auth(QvaPayAuth(), timeout=Timeout(TIMEOUT)) 16 | yield client 17 | await client.close() 18 | 19 | 20 | @pytest_mark.anyio 21 | async def test_error(): 22 | client = AsyncQvaPayClient("", "") 23 | try: 24 | await client.get_info() 25 | assert False 26 | except QvaPayError: 27 | assert True 28 | 29 | 30 | @pytest_mark.anyio 31 | async def test_get_info(client: AsyncQvaPayClient): 32 | await client.get_info() 33 | 34 | 35 | @pytest_mark.anyio 36 | async def test_get_balance(client: AsyncQvaPayClient): 37 | await client.get_balance() 38 | 39 | 40 | @pytest_mark.anyio 41 | async def test_create_invoice(client: AsyncQvaPayClient): 42 | await client.create_invoice(random(), "Invoice for testing", str(uuid4())) 43 | 44 | 45 | @pytest_mark.anyio 46 | async def test_get_transactions(client: AsyncQvaPayClient): 47 | result = await client.get_transactions() 48 | if result.data: 49 | item = result.data[0] 50 | await client.get_transaction(item.id) 51 | -------------------------------------------------------------------------------- /tests/v1/_sync/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ragnarok22/qvapay-python/5f260c5d711ffbf961463c905764d292eadf1182/tests/v1/_sync/__init__.py -------------------------------------------------------------------------------- /tests/v1/_sync/test_client.py: -------------------------------------------------------------------------------- 1 | from random import random 2 | from uuid import uuid4 3 | 4 | from httpx import Timeout 5 | from pytest import fixture 6 | from pytest import mark as pytest_mark 7 | 8 | from qvapay.v1 import QvaPayAuth, QvaPayError, SyncQvaPayClient 9 | 10 | TIMEOUT = 20 11 | 12 | 13 | @fixture(name="client") 14 | def create_client(): 15 | client = SyncQvaPayClient.from_auth(QvaPayAuth(), timeout=Timeout(TIMEOUT)) 16 | yield client 17 | client.close() 18 | 19 | 20 | @pytest_mark.anyio 21 | def test_error(): 22 | client = SyncQvaPayClient("", "") 23 | try: 24 | client.get_info() 25 | assert False 26 | except QvaPayError: 27 | assert True 28 | 29 | 30 | @pytest_mark.anyio 31 | def test_get_info(client: SyncQvaPayClient): 32 | client.get_info() 33 | 34 | 35 | @pytest_mark.anyio 36 | def test_get_balance(client: SyncQvaPayClient): 37 | client.get_balance() 38 | 39 | 40 | @pytest_mark.anyio 41 | def test_create_invoice(client: SyncQvaPayClient): 42 | client.create_invoice(random(), "Invoice for testing", str(uuid4())) 43 | 44 | 45 | @pytest_mark.anyio 46 | def test_get_transactions(client: SyncQvaPayClient): 47 | result = client.get_transactions() 48 | if result.data: 49 | item = result.data[0] 50 | client.get_transaction(item.id) 51 | --------------------------------------------------------------------------------