├── .bumpversion.cfg ├── .github └── workflows │ ├── python-publish.yml │ ├── test-python-publish.yml │ └── tests.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── Pipfile ├── Pipfile.lock ├── README.md ├── examples ├── .gitkeep ├── cidconfig_example.json ├── config.py ├── deals.py ├── logs.py ├── storage_info.py ├── storage_jobs.py ├── store_retrieve.py ├── testfile.txt ├── user_create.py ├── version.py └── wallet.py ├── logging.ini ├── pygate_grpc ├── admin │ ├── __init__.py │ ├── storage_jobs.py │ ├── users.py │ └── wallet.py ├── client.py ├── config.py ├── data.py ├── deals.py ├── decorators.py ├── errors.py ├── exceptions.py ├── storage_info.py ├── storage_jobs.py ├── types.py └── wallet.py ├── setup.cfg ├── setup.py └── tests └── integration ├── admin ├── test_jobs.py ├── test_users.py └── test_wallets.py ├── assets └── cidconfig_example.json ├── conftest.py ├── docker └── resource_limits.yaml ├── test_config.py ├── test_data.py ├── test_deals.py ├── test_storage_info.py ├── test_version.py └── test_wallet.py /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 2.0.0 3 | commit = True 4 | tag = True 5 | tag_name = v{new_version} 6 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to PyPi 2 | 3 | on: 4 | release: 5 | types: # This configuration does not affect the page_build event above 6 | - created 7 | 8 | 9 | jobs: 10 | publish: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Set up Python 17 | uses: actions/setup-python@v2 18 | with: 19 | python-version: '3.7' 20 | - name: Install dependencies 21 | run: | 22 | python -m pip install --upgrade pip 23 | pip install pipenv 24 | pipenv install --dev 25 | - name: Build and publish 26 | env: 27 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 28 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 29 | run: | 30 | pipenv run build 31 | pipenv run publish 32 | -------------------------------------------------------------------------------- /.github/workflows/test-python-publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to PyPi (Test) 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | jobs: 9 | publish: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Set up Python 16 | uses: actions/setup-python@v2 17 | with: 18 | python-version: '3.7' 19 | - name: Install dependencies 20 | run: | 21 | python -m pip install --upgrade pip 22 | pip install pipenv 23 | pipenv install --dev 24 | - name: Build and publish 25 | env: 26 | TWINE_USERNAME: ${{ secrets.TEST_PYPI_USERNAME }} 27 | TWINE_PASSWORD: ${{ secrets.TEST_PYPI_PASSWORD }} 28 | run: | 29 | pipenv run build 30 | pipenv run publish --repository testpypi 31 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | integration: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | with: 19 | ref: ${{ github.event.pull_request.head.sha }} 20 | - name: Set up Python 21 | uses: actions/setup-python@v2 22 | with: 23 | python-version: '3.7' 24 | - name: Install CI dependencies 25 | run: | 26 | python -m pip install --upgrade pip 27 | pip install pipenv 28 | pipenv install --dev 29 | - name: Run Integration Tests 30 | run: | 31 | pipenv run integration-test 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | cover/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | .pybuilder/ 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | # For a library or package, you might want to ignore these files since the code is 89 | # intended to run in multiple environments; otherwise, check them in: 90 | # .python-version 91 | 92 | # pipenv 93 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 94 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 95 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 96 | # install all needed dependencies. 97 | #Pipfile.lock 98 | 99 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 100 | __pypackages__/ 101 | 102 | # Celery stuff 103 | celerybeat-schedule 104 | celerybeat.pid 105 | 106 | # SageMath parsed files 107 | *.sage.py 108 | 109 | # Environments 110 | .env 111 | .venv 112 | env/ 113 | venv/ 114 | ENV/ 115 | env.bak/ 116 | venv.bak/ 117 | 118 | # Spyder project settings 119 | .spyderproject 120 | .spyproject 121 | 122 | # Rope project settings 123 | .ropeproject 124 | 125 | # mkdocs documentation 126 | /site 127 | 128 | # mypy 129 | .mypy_cache/ 130 | .dmypy.json 131 | dmypy.json 132 | 133 | # Pyre type checker 134 | .pyre/ 135 | 136 | # pytype static type analyzer 137 | .pytype/ 138 | 139 | # Cython debug symbols 140 | cython_debug/ 141 | 142 | .vscode 143 | 144 | examples/testfile_copy.txt -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to pygate-grpc 2 | We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's: 3 | 4 | - Reporting a bug 5 | - Discussing the current state of the code 6 | - Submitting a fix 7 | - Proposing new features 8 | - Becoming a maintainer 9 | 10 | ## We Develop with Github 11 | We use github to host code, to track issues and feature requests, as well as accept pull requests. 12 | 13 | ## We Use [Github Flow](https://guides.github.com/introduction/flow/index.html), So All Code Changes Happen Through Pull Requests 14 | Pull requests are the best way to propose changes to the codebase (we use [Github Flow](https://guides.github.com/introduction/flow/index.html)). We actively welcome your pull requests: 15 | 16 | 1. Fork the repo and create your branch from `main`. 17 | 2. If you've added code that should be tested, add tests. (We only have an integration tests atm which run against localnet. Add tests there if applicable) 18 | 3. If you've added a client operation add a docstring. 19 | 4. Ensure that integration tests pass. 20 | 5. Run black formatter (`pipenv run format`) 21 | 6. Issue that pull request! 22 | 23 | ## Any contributions you make will be under the MIT Software License 24 | In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern. 25 | 26 | ## Report bugs using Github's [issues](https://github.com/pygate/pygate-grpc/issues) 27 | We use GitHub issues to track public bugs. Report a bug by [opening a new issue](); it's that easy! 28 | 29 | ## Write bug reports with detail, background, and sample code 30 | **Great Bug Reports** tend to have: 31 | 32 | - A quick summary and/or background 33 | - Steps to reproduce 34 | - Be specific! 35 | - Give sample code if you can. 36 | - What you expected would happen 37 | - What actually happens 38 | - Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) 39 | 40 | People *love* thorough bug reports. I'm not even kidding. 41 | 42 | ## Use a Consistent Coding Style 43 | Again, you can run `pipenv run format` for that. 44 | 45 | ## License 46 | By contributing, you agree that your contributions will be licensed under its MIT License. 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | 4 | Copyright (c) 2020 Antreas Pogiatzis, Wang Ge, Peter Van Garderen, Aaron Sutula 5 | 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | bump2version = "*" 8 | black = "==19.10b0" 9 | pipenv-setup = "*" 10 | setuptools = "*" 11 | wheel = "*" 12 | twine = "*" 13 | pytest = "*" 14 | docker = "*" 15 | gitpython = "*" 16 | pytest-docker = "*" 17 | secretstorage = {markers = "sys_platform == 'linux'"} 18 | keyring = "==19.1.0" 19 | flake8 = "*" 20 | isort = "*" 21 | pylint = "*" 22 | mypy = "*" 23 | pygate-grpc = {editable = true,path = "."} 24 | 25 | [requires] 26 | python_version = "3.7" 27 | 28 | [scripts] 29 | build = "python setup.py sdist bdist_wheel" 30 | publish = "twine upload dist/*" 31 | format = "bash -c \"python -m isort setup.py pygate_grpc tests && python -m black $(git ls-files '*.py')\"" 32 | lint = "bash -c \"python -m flake8\"" 33 | integration-test = "python -m pytest tests/integration/" 34 | 35 | [packages] 36 | grpc-powergate-client = "==2.1.0" 37 | mypy-extensions = "*" 38 | deprecated = "*" 39 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "48cebcabc9242f20205f86ba74f4de0771846ebed4078560762b583a465c7753" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.7" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "deprecated": { 20 | "hashes": [ 21 | "sha256:471ec32b2755172046e28102cd46c481f21c6036a0ec027521eba8521aa4ef35", 22 | "sha256:924b6921f822b64ec54f49be6700a126bab0640cfafca78f22c9d429ed590560" 23 | ], 24 | "index": "pypi", 25 | "version": "==1.2.11" 26 | }, 27 | "grpc-powergate-client": { 28 | "hashes": [ 29 | "sha256:30b825d3438c03171589e5185b71f71ce9a6e52b07d653645e2558ecaf402753", 30 | "sha256:90f2f58452008af1944303313187d08b55683ec234b62db77d213e95fc31f821" 31 | ], 32 | "index": "pypi", 33 | "version": "==2.1.0" 34 | }, 35 | "grpcio": { 36 | "hashes": [ 37 | "sha256:0072ec4563ab4268c4c32e936955085c2d41ea175b662363496daedd2273372c", 38 | "sha256:048c01d1eb5c2ae7cba2254b98938d2fc81f6dc10d172d9261d65266adb0fdb3", 39 | "sha256:088c8bea0f6b596937fefacf2c8df97712e7a3dd49496975049cc95dbf02af1a", 40 | "sha256:0f714e261e1d63615476cda4ee808a79cca62f8f09e2943c136c2f87ec5347b1", 41 | "sha256:16fd33030944672e49e0530dec2c60cd4089659ccdf327e99569b3b29246a0b6", 42 | "sha256:1757e81c09132851e85495b802fe4d4fbef3547e77fa422a62fb4f7d51785be0", 43 | "sha256:17940a7dc461066f28816df48be44f24d3b9f150db344308ee2aeae033e1af0b", 44 | "sha256:18ad7644e23757420ea839ac476ef861e4f4841c8566269b7c91c100ca1943b3", 45 | "sha256:1aa53f82362c7f2791fe0cdd9a3b3aec325c11d8f0dfde600f91907dfaa8546b", 46 | "sha256:22edfc278070d54f3ab7f741904e09155a272fe934e842babbf84476868a50de", 47 | "sha256:2f8e8d35d4799aa1627a212dbe8546594abf4064056415c31bd1b3b8f2a62027", 48 | "sha256:35b72884e09cbc46c564091f4545a39fa66d132c5676d1a6e827517fff47f2c1", 49 | "sha256:399ee377b312ac652b07ef4365bbbba009da361fa7708c4d3d4ce383a1534ea7", 50 | "sha256:3e7d4428ed752fdfe2dddf2a404c93d3a2f62bf4b9109c0c10a850c698948891", 51 | "sha256:44aaa6148d18a8e836f99dadcdec17b27bc7ec0995b2cc12c94e61826040ec90", 52 | "sha256:6ba3d7acf70acde9ce27e22921db921b84a71be578b32739536c32377b65041a", 53 | "sha256:75ea903edc42a8c6ec61dbc5f453febd79d8bdec0e1bad6df7088c34282e8c42", 54 | "sha256:764b50ba1a15a2074cdd1a841238f2dead0a06529c495a46821fae84cb9c7342", 55 | "sha256:7ae408780b79c9b9b91a2592abd1d7abecd05675d988ea75038580f420966b59", 56 | "sha256:7bd0ebbb14dde78bf66a1162efd29d3393e4e943952e2f339757aa48a184645c", 57 | "sha256:7ee7d54da9d176d3c9a0f47c04d7ff6fdc6ee1c17643caff8c33d6c8a70678a4", 58 | "sha256:859a0ceb23d7189362cc06fe7e906e9ed5c7a8f3ac960cc04ce13fe5847d0b62", 59 | "sha256:87147b1b306c88fe7dca7e3dff8aefd1e63d6aed86e224f9374ddf283f17d7f1", 60 | "sha256:8a29a26b9f39701ce15aa1d5aa5e96e0b5f7028efe94f95341a4ed8dbe4bed78", 61 | "sha256:8d08f90d72a8e8d9af087476337da76d26749617b0a092caff4e684ce267af21", 62 | "sha256:94c3b81089a86d3c5877d22b07ebc66b5ed1d84771e24b001844e29a5b6178dd", 63 | "sha256:95cc4d2067deced18dc807442cf8062a93389a86abf8d40741120054389d3f29", 64 | "sha256:9e503eaf853199804a954dc628c5207e67d6c7848dcba42a997fbe718618a2b1", 65 | "sha256:9f0da13b215068e7434b161a35d0b4e92140ffcfa33ddda9c458199ea1d7ce45", 66 | "sha256:a36151c335280b09afd5123f3b25085027ae2b10682087a4342fb6f635b928fb", 67 | "sha256:aca45d2ccb693c9227fbf21144891422a42dc4b76b52af8dd1d4e43afebe321d", 68 | "sha256:acb489b7aafdcf960f1a0000a1f22b45e5b6ccdf8dba48f97617d627f4133195", 69 | "sha256:aea3d592a7ece84739b92d212cd16037c51d84a259414f64b51c14e946611f3d", 70 | "sha256:b180a3ec4a5d6f96d3840c83e5f8ab49afac9fa942921e361b451d7a024efb00", 71 | "sha256:b2985f73611b637271b00d9c4f177e65cc3193269bc9760f16262b1a12757265", 72 | "sha256:c8d0a6a58a42275c6cb616e7cb9f9fcf5eba1e809996546e561cd818b8f7cff7", 73 | "sha256:d186a0ce291f4386e28a7042ec31c85250b0c2e25d2794b87fa3c15ff473c46c", 74 | "sha256:da44bf613eed5d9e8df0785463e502a416de1be6e4ac31edbe99c9111abaed5f", 75 | "sha256:dc2589370ef84eb1cc53530070d658a7011d2ee65f18806581809c11cd016136", 76 | "sha256:dfecb2acd3acb8bb50e9aa31472c6e57171d97c1098ee67cd283a6fe7d56a926", 77 | "sha256:e163c27d2062cd3eb07057f23f8d1330925beaba16802312b51b4bad33d74098", 78 | "sha256:e87e55fba98ebd7b4c614dcef9940dc2a7e057ad8bba5f91554934d47319a35b", 79 | "sha256:efb3d67405eb8030db6f27920b4be023fabfb5d4e09c34deab094a7c473a5472", 80 | "sha256:efd896e8ca7adb2654cf014479a5e1f74e4f776b6b2c0fbf95a6c92787a6631a", 81 | "sha256:f0c27fd16582a303e5baf6cffd9345c9ac5f855d69a51232664a0b888a77ba80", 82 | "sha256:f3654a52f72ba28953dbe2e93208099f4903f4b3c07dc7ff4db671c92968111d" 83 | ], 84 | "version": "==1.35.0" 85 | }, 86 | "mypy-extensions": { 87 | "hashes": [ 88 | "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", 89 | "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" 90 | ], 91 | "index": "pypi", 92 | "version": "==0.4.3" 93 | }, 94 | "protobuf": { 95 | "hashes": [ 96 | "sha256:03f6ee325710eb164bd85741721fbd4326c399b0ecf49dddba9172df9149c124", 97 | "sha256:0644b70bc9d36329438de0da619e3337ab4eade784a9acc6ba8e5ed22f2e9e50", 98 | "sha256:0938b13c2a5ad0ce2b75c19dc0c2082f721a61b97d3f11d73ee4412dfb6e06eb", 99 | "sha256:165071fdfaf4d7ff7a70d2197bba048fb301c7b957095eedf4bf8379d904adb1", 100 | "sha256:17a26d5a7757211ce60032f0111dd426d4e5f44145ac6e86fa241e0cffe9df17", 101 | "sha256:28daf1c44cf11c70f3852bd13f8fc6f7f1f211abbf068ffbeb25f8e4e2f6c98b", 102 | "sha256:3188af446c544df79525d66e2d987490053262b81226fc6fa3f00556135f7e8a", 103 | "sha256:509fba6d57f0c1dc483f91754a33a5d8632da1bf75d87b6c127bcf0e3966fa44", 104 | "sha256:5810e9e3851ab8aa28624bdc947f9236ce7ec2be2f63b88b373fdc92791fbf86", 105 | "sha256:60fd96bc77293d9770d133cdbf3af9ff2373ce11d2055d2ca581db2330fe6805", 106 | "sha256:763a9444bafd2204cdeb29be54147ce7cfae04df805161507426c215a461ae6e", 107 | "sha256:824dbae3390fcc3ea1bf96748e6da951a601802894cf7e1465e72b4732538cab", 108 | "sha256:a8cccf2d2df2675f10a47f963f8010516f6aff09db7d134b0b0e57422ce07f78", 109 | "sha256:c70647b71385302efb615d25c643f1b92784201f7b4ed2d9ff472e4c869ccad5", 110 | "sha256:d3797255e8fbf234477332864f0304222b2492dfd91e95e6314326dbf0e235e2", 111 | "sha256:d52494780f89d1277f982c209197ce1da91d416c27ba9f4495d339ac6a3bac02", 112 | "sha256:d7576c8b59288c5feea161d9ed74925d26759963b51f850d8eadd7a88b4e0ddf", 113 | "sha256:de2e543ffb1701ea8fe7077ba571dbaa1980876d1817f6a70b984064dcb20e6f", 114 | "sha256:edae67da507393f377555531cb7afa1714c75a84404f3541ef5df36ce3637768", 115 | "sha256:f49a1721f2a3d72466aa19f095cc3fe2883b5e1868f4a1e9f51043df8ecb0140" 116 | ], 117 | "version": "==3.15.1" 118 | }, 119 | "six": { 120 | "hashes": [ 121 | "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", 122 | "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" 123 | ], 124 | "version": "==1.15.0" 125 | }, 126 | "wrapt": { 127 | "hashes": [ 128 | "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7" 129 | ], 130 | "version": "==1.12.1" 131 | } 132 | }, 133 | "develop": { 134 | "appdirs": { 135 | "hashes": [ 136 | "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", 137 | "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" 138 | ], 139 | "version": "==1.4.4" 140 | }, 141 | "astroid": { 142 | "hashes": [ 143 | "sha256:87ae7f2398b8a0ae5638ddecf9987f081b756e0e9fc071aeebdca525671fc4dc", 144 | "sha256:b31c92f545517dcc452f284bc9c044050862fbe6d93d2b3de4a215a6b384bf0d" 145 | ], 146 | "version": "==2.5.0" 147 | }, 148 | "attrs": { 149 | "hashes": [ 150 | "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", 151 | "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" 152 | ], 153 | "version": "==20.3.0" 154 | }, 155 | "bcrypt": { 156 | "hashes": [ 157 | "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29", 158 | "sha256:63d4e3ff96188e5898779b6057878fecf3f11cfe6ec3b313ea09955d587ec7a7", 159 | "sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34", 160 | "sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55", 161 | "sha256:c95d4cbebffafcdd28bd28bb4e25b31c50f6da605c81ffd9ad8a3d1b2ab7b1b6", 162 | "sha256:cd1ea2ff3038509ea95f687256c46b79f5fc382ad0aa3664d200047546d511d1", 163 | "sha256:cdcdcb3972027f83fe24a48b1e90ea4b584d35f1cc279d76de6fc4b13376239d" 164 | ], 165 | "version": "==3.2.0" 166 | }, 167 | "black": { 168 | "hashes": [ 169 | "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b", 170 | "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539" 171 | ], 172 | "index": "pypi", 173 | "version": "==19.10b0" 174 | }, 175 | "bleach": { 176 | "hashes": [ 177 | "sha256:6123ddc1052673e52bab52cdc955bcb57a015264a1c57d37bea2f6b817af0125", 178 | "sha256:98b3170739e5e83dd9dc19633f074727ad848cbedb6026708c8ac2d3b697a433" 179 | ], 180 | "version": "==3.3.0" 181 | }, 182 | "bump2version": { 183 | "hashes": [ 184 | "sha256:37f927ea17cde7ae2d7baf832f8e80ce3777624554a653006c9144f8017fe410", 185 | "sha256:762cb2bfad61f4ec8e2bdf452c7c267416f8c70dd9ecb1653fd0bbb01fa936e6" 186 | ], 187 | "index": "pypi", 188 | "version": "==1.0.1" 189 | }, 190 | "cached-property": { 191 | "hashes": [ 192 | "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130", 193 | "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0" 194 | ], 195 | "version": "==1.5.2" 196 | }, 197 | "cerberus": { 198 | "hashes": [ 199 | "sha256:302e6694f206dd85cb63f13fd5025b31ab6d38c99c50c6d769f8fa0b0f299589" 200 | ], 201 | "version": "==1.3.2" 202 | }, 203 | "certifi": { 204 | "hashes": [ 205 | "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c", 206 | "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830" 207 | ], 208 | "version": "==2020.12.5" 209 | }, 210 | "cffi": { 211 | "hashes": [ 212 | "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813", 213 | "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06", 214 | "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea", 215 | "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee", 216 | "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396", 217 | "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73", 218 | "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315", 219 | "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1", 220 | "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49", 221 | "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892", 222 | "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482", 223 | "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058", 224 | "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5", 225 | "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53", 226 | "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045", 227 | "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3", 228 | "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5", 229 | "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e", 230 | "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c", 231 | "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369", 232 | "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827", 233 | "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053", 234 | "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa", 235 | "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4", 236 | "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322", 237 | "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132", 238 | "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62", 239 | "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa", 240 | "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0", 241 | "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396", 242 | "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e", 243 | "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991", 244 | "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6", 245 | "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1", 246 | "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406", 247 | "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d", 248 | "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c" 249 | ], 250 | "version": "==1.14.5" 251 | }, 252 | "chardet": { 253 | "hashes": [ 254 | "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", 255 | "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" 256 | ], 257 | "version": "==4.0.0" 258 | }, 259 | "click": { 260 | "hashes": [ 261 | "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", 262 | "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" 263 | ], 264 | "version": "==7.1.2" 265 | }, 266 | "colorama": { 267 | "hashes": [ 268 | "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b", 269 | "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2" 270 | ], 271 | "version": "==0.4.4" 272 | }, 273 | "cryptography": { 274 | "hashes": [ 275 | "sha256:066bc53f052dfeda2f2d7c195cf16fb3e5ff13e1b6b7415b468514b40b381a5b", 276 | "sha256:0923ba600d00718d63a3976f23cab19aef10c1765038945628cd9be047ad0336", 277 | "sha256:2d32223e5b0ee02943f32b19245b61a62db83a882f0e76cc564e1cec60d48f87", 278 | "sha256:4169a27b818de4a1860720108b55a2801f32b6ae79e7f99c00d79f2a2822eeb7", 279 | "sha256:57ad77d32917bc55299b16d3b996ffa42a1c73c6cfa829b14043c561288d2799", 280 | "sha256:5ecf2bcb34d17415e89b546dbb44e73080f747e504273e4d4987630493cded1b", 281 | "sha256:600cf9bfe75e96d965509a4c0b2b183f74a4fa6f5331dcb40fb7b77b7c2484df", 282 | "sha256:66b57a9ca4b3221d51b237094b0303843b914b7d5afd4349970bb26518e350b0", 283 | "sha256:93cfe5b7ff006de13e1e89830810ecbd014791b042cbe5eec253be11ac2b28f3", 284 | "sha256:9e98b452132963678e3ac6c73f7010fe53adf72209a32854d55690acac3f6724", 285 | "sha256:df186fcbf86dc1ce56305becb8434e4b6b7504bc724b71ad7a3239e0c9d14ef2", 286 | "sha256:fec7fb46b10da10d9e1d078d1ff8ed9e05ae14f431fdbd11145edd0550b9a964" 287 | ], 288 | "version": "==3.4.6" 289 | }, 290 | "deprecated": { 291 | "hashes": [ 292 | "sha256:471ec32b2755172046e28102cd46c481f21c6036a0ec027521eba8521aa4ef35", 293 | "sha256:924b6921f822b64ec54f49be6700a126bab0640cfafca78f22c9d429ed590560" 294 | ], 295 | "index": "pypi", 296 | "version": "==1.2.11" 297 | }, 298 | "distlib": { 299 | "hashes": [ 300 | "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb", 301 | "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1" 302 | ], 303 | "version": "==0.3.1" 304 | }, 305 | "distro": { 306 | "hashes": [ 307 | "sha256:0e58756ae38fbd8fc3020d54badb8eae17c5b9dcbed388b17bb55b8a5928df92", 308 | "sha256:df74eed763e18d10d0da624258524ae80486432cd17392d9c3d96f5e83cd2799" 309 | ], 310 | "version": "==1.5.0" 311 | }, 312 | "docker": { 313 | "extras": [ 314 | "ssh" 315 | ], 316 | "hashes": [ 317 | "sha256:d4625e70e3d5a12d7cbf1fd68cef2e081ac86b83889e00e5466d975f90e50dad", 318 | "sha256:de5753b7f6486dd541a98393e423e387579b8974a5068748b83f852cc76a89d6" 319 | ], 320 | "index": "pypi", 321 | "version": "==4.4.3" 322 | }, 323 | "docker-compose": { 324 | "hashes": [ 325 | "sha256:681aca74e70e238ae43c810a62f471b645942f0ce97b6a0ca375fcb64f3aca85", 326 | "sha256:92375b30ab7134e8c32470b621e7cf9a3c0771ce2c20de7e1f11cd71f83a088e" 327 | ], 328 | "version": "==1.28.4" 329 | }, 330 | "dockerpty": { 331 | "hashes": [ 332 | "sha256:69a9d69d573a0daa31bcd1c0774eeed5c15c295fe719c61aca550ed1393156ce" 333 | ], 334 | "version": "==0.4.1" 335 | }, 336 | "docopt": { 337 | "hashes": [ 338 | "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491" 339 | ], 340 | "version": "==0.6.2" 341 | }, 342 | "docutils": { 343 | "hashes": [ 344 | "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af", 345 | "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc" 346 | ], 347 | "version": "==0.16" 348 | }, 349 | "entrypoints": { 350 | "hashes": [ 351 | "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", 352 | "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451" 353 | ], 354 | "version": "==0.3" 355 | }, 356 | "flake8": { 357 | "hashes": [ 358 | "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839", 359 | "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b" 360 | ], 361 | "index": "pypi", 362 | "version": "==3.8.4" 363 | }, 364 | "gitdb": { 365 | "hashes": [ 366 | "sha256:91f36bfb1ab7949b3b40e23736db18231bf7593edada2ba5c3a174a7b23657ac", 367 | "sha256:c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9" 368 | ], 369 | "version": "==4.0.5" 370 | }, 371 | "gitpython": { 372 | "hashes": [ 373 | "sha256:8621a7e777e276a5ec838b59280ba5272dd144a18169c36c903d8b38b99f750a", 374 | "sha256:c5347c81d232d9b8e7f47b68a83e5dc92e7952127133c5f2df9133f2c75a1b29" 375 | ], 376 | "index": "pypi", 377 | "version": "==3.1.13" 378 | }, 379 | "grpc-powergate-client": { 380 | "hashes": [ 381 | "sha256:30b825d3438c03171589e5185b71f71ce9a6e52b07d653645e2558ecaf402753", 382 | "sha256:90f2f58452008af1944303313187d08b55683ec234b62db77d213e95fc31f821" 383 | ], 384 | "index": "pypi", 385 | "version": "==2.1.0" 386 | }, 387 | "grpcio": { 388 | "hashes": [ 389 | "sha256:0072ec4563ab4268c4c32e936955085c2d41ea175b662363496daedd2273372c", 390 | "sha256:048c01d1eb5c2ae7cba2254b98938d2fc81f6dc10d172d9261d65266adb0fdb3", 391 | "sha256:088c8bea0f6b596937fefacf2c8df97712e7a3dd49496975049cc95dbf02af1a", 392 | "sha256:0f714e261e1d63615476cda4ee808a79cca62f8f09e2943c136c2f87ec5347b1", 393 | "sha256:16fd33030944672e49e0530dec2c60cd4089659ccdf327e99569b3b29246a0b6", 394 | "sha256:1757e81c09132851e85495b802fe4d4fbef3547e77fa422a62fb4f7d51785be0", 395 | "sha256:17940a7dc461066f28816df48be44f24d3b9f150db344308ee2aeae033e1af0b", 396 | "sha256:18ad7644e23757420ea839ac476ef861e4f4841c8566269b7c91c100ca1943b3", 397 | "sha256:1aa53f82362c7f2791fe0cdd9a3b3aec325c11d8f0dfde600f91907dfaa8546b", 398 | "sha256:22edfc278070d54f3ab7f741904e09155a272fe934e842babbf84476868a50de", 399 | "sha256:2f8e8d35d4799aa1627a212dbe8546594abf4064056415c31bd1b3b8f2a62027", 400 | "sha256:35b72884e09cbc46c564091f4545a39fa66d132c5676d1a6e827517fff47f2c1", 401 | "sha256:399ee377b312ac652b07ef4365bbbba009da361fa7708c4d3d4ce383a1534ea7", 402 | "sha256:3e7d4428ed752fdfe2dddf2a404c93d3a2f62bf4b9109c0c10a850c698948891", 403 | "sha256:44aaa6148d18a8e836f99dadcdec17b27bc7ec0995b2cc12c94e61826040ec90", 404 | "sha256:6ba3d7acf70acde9ce27e22921db921b84a71be578b32739536c32377b65041a", 405 | "sha256:75ea903edc42a8c6ec61dbc5f453febd79d8bdec0e1bad6df7088c34282e8c42", 406 | "sha256:764b50ba1a15a2074cdd1a841238f2dead0a06529c495a46821fae84cb9c7342", 407 | "sha256:7ae408780b79c9b9b91a2592abd1d7abecd05675d988ea75038580f420966b59", 408 | "sha256:7bd0ebbb14dde78bf66a1162efd29d3393e4e943952e2f339757aa48a184645c", 409 | "sha256:7ee7d54da9d176d3c9a0f47c04d7ff6fdc6ee1c17643caff8c33d6c8a70678a4", 410 | "sha256:859a0ceb23d7189362cc06fe7e906e9ed5c7a8f3ac960cc04ce13fe5847d0b62", 411 | "sha256:87147b1b306c88fe7dca7e3dff8aefd1e63d6aed86e224f9374ddf283f17d7f1", 412 | "sha256:8a29a26b9f39701ce15aa1d5aa5e96e0b5f7028efe94f95341a4ed8dbe4bed78", 413 | "sha256:8d08f90d72a8e8d9af087476337da76d26749617b0a092caff4e684ce267af21", 414 | "sha256:94c3b81089a86d3c5877d22b07ebc66b5ed1d84771e24b001844e29a5b6178dd", 415 | "sha256:95cc4d2067deced18dc807442cf8062a93389a86abf8d40741120054389d3f29", 416 | "sha256:9e503eaf853199804a954dc628c5207e67d6c7848dcba42a997fbe718618a2b1", 417 | "sha256:9f0da13b215068e7434b161a35d0b4e92140ffcfa33ddda9c458199ea1d7ce45", 418 | "sha256:a36151c335280b09afd5123f3b25085027ae2b10682087a4342fb6f635b928fb", 419 | "sha256:aca45d2ccb693c9227fbf21144891422a42dc4b76b52af8dd1d4e43afebe321d", 420 | "sha256:acb489b7aafdcf960f1a0000a1f22b45e5b6ccdf8dba48f97617d627f4133195", 421 | "sha256:aea3d592a7ece84739b92d212cd16037c51d84a259414f64b51c14e946611f3d", 422 | "sha256:b180a3ec4a5d6f96d3840c83e5f8ab49afac9fa942921e361b451d7a024efb00", 423 | "sha256:b2985f73611b637271b00d9c4f177e65cc3193269bc9760f16262b1a12757265", 424 | "sha256:c8d0a6a58a42275c6cb616e7cb9f9fcf5eba1e809996546e561cd818b8f7cff7", 425 | "sha256:d186a0ce291f4386e28a7042ec31c85250b0c2e25d2794b87fa3c15ff473c46c", 426 | "sha256:da44bf613eed5d9e8df0785463e502a416de1be6e4ac31edbe99c9111abaed5f", 427 | "sha256:dc2589370ef84eb1cc53530070d658a7011d2ee65f18806581809c11cd016136", 428 | "sha256:dfecb2acd3acb8bb50e9aa31472c6e57171d97c1098ee67cd283a6fe7d56a926", 429 | "sha256:e163c27d2062cd3eb07057f23f8d1330925beaba16802312b51b4bad33d74098", 430 | "sha256:e87e55fba98ebd7b4c614dcef9940dc2a7e057ad8bba5f91554934d47319a35b", 431 | "sha256:efb3d67405eb8030db6f27920b4be023fabfb5d4e09c34deab094a7c473a5472", 432 | "sha256:efd896e8ca7adb2654cf014479a5e1f74e4f776b6b2c0fbf95a6c92787a6631a", 433 | "sha256:f0c27fd16582a303e5baf6cffd9345c9ac5f855d69a51232664a0b888a77ba80", 434 | "sha256:f3654a52f72ba28953dbe2e93208099f4903f4b3c07dc7ff4db671c92968111d" 435 | ], 436 | "version": "==1.35.0" 437 | }, 438 | "idna": { 439 | "hashes": [ 440 | "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", 441 | "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" 442 | ], 443 | "version": "==2.10" 444 | }, 445 | "importlib-metadata": { 446 | "hashes": [ 447 | "sha256:2a851a30a1fd847cedd68c7c6bfb90e765db6265852f574e9d88bb40f96924ab", 448 | "sha256:31c5c29f17d064cc71bf6a005a8b25b3332787ab2e7362d5d258800ac825f471" 449 | ], 450 | "markers": "python_version < '3.8'", 451 | "version": "==3.5.0" 452 | }, 453 | "iniconfig": { 454 | "hashes": [ 455 | "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", 456 | "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" 457 | ], 458 | "version": "==1.1.1" 459 | }, 460 | "isort": { 461 | "hashes": [ 462 | "sha256:c729845434366216d320e936b8ad6f9d681aab72dc7cbc2d51bedc3582f3ad1e", 463 | "sha256:fff4f0c04e1825522ce6949973e83110a6e907750cd92d128b0d14aaaadbffdc" 464 | ], 465 | "index": "pypi", 466 | "version": "==5.7.0" 467 | }, 468 | "jeepney": { 469 | "hashes": [ 470 | "sha256:7d59b6622675ca9e993a6bd38de845051d315f8b0c72cca3aef733a20b648657", 471 | "sha256:aec56c0eb1691a841795111e184e13cad504f7703b9a64f63020816afa79a8ae" 472 | ], 473 | "version": "==0.6.0" 474 | }, 475 | "jsonschema": { 476 | "hashes": [ 477 | "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163", 478 | "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a" 479 | ], 480 | "version": "==3.2.0" 481 | }, 482 | "keyring": { 483 | "hashes": [ 484 | "sha256:0ca298c5356446c0e6cd4e26210d77c5505e3ec725ca753409f39f1550256088", 485 | "sha256:6a445aec3ac9e7d1f9b17ac0a1ccf2110334f7a49eacced3f74d0ea07975d98d" 486 | ], 487 | "index": "pypi", 488 | "version": "==19.1.0" 489 | }, 490 | "lazy-object-proxy": { 491 | "hashes": [ 492 | "sha256:1d33d6f789697f401b75ce08e73b1de567b947740f768376631079290118ad39", 493 | "sha256:2f2de8f8ac0be3e40d17730e0600619d35c78c13a099ea91ef7fb4ad944ce694", 494 | "sha256:3782931963dc89e0e9a0ae4348b44762e868ea280e4f8c233b537852a8996ab9", 495 | "sha256:37d9c34b96cca6787fe014aeb651217944a967a5b165e2cacb6b858d2997ab84", 496 | "sha256:38c3865bd220bd983fcaa9aa11462619e84a71233bafd9c880f7b1cb753ca7fa", 497 | "sha256:429c4d1862f3fc37cd56304d880f2eae5bd0da83bdef889f3bd66458aac49128", 498 | "sha256:522b7c94b524389f4a4094c4bf04c2b02228454ddd17c1a9b2801fac1d754871", 499 | "sha256:57fb5c5504ddd45ed420b5b6461a78f58cbb0c1b0cbd9cd5a43ad30a4a3ee4d0", 500 | "sha256:5944a9b95e97de1980c65f03b79b356f30a43de48682b8bdd90aa5089f0ec1f4", 501 | "sha256:6f4e5e68b7af950ed7fdb594b3f19a0014a3ace0fedb86acb896e140ffb24302", 502 | "sha256:71a1ef23f22fa8437974b2d60fedb947c99a957ad625f83f43fd3de70f77f458", 503 | "sha256:8a44e9901c0555f95ac401377032f6e6af66d8fc1fbfad77a7a8b1a826e0b93c", 504 | "sha256:b6577f15d5516d7d209c1a8cde23062c0f10625f19e8dc9fb59268859778d7d7", 505 | "sha256:c8fe2d6ff0ff583784039d0255ea7da076efd08507f2be6f68583b0da32e3afb", 506 | "sha256:cadfa2c2cf54d35d13dc8d231253b7985b97d629ab9ca6e7d672c35539d38163", 507 | "sha256:cd1bdace1a8762534e9a36c073cd54e97d517a17d69a17985961265be6d22847", 508 | "sha256:ddbdcd10eb999d7ab292677f588b658372aadb9a52790f82484a37127a390108", 509 | "sha256:e7273c64bccfd9310e9601b8f4511d84730239516bada26a0c9846c9697617ef", 510 | "sha256:e7428977763150b4cf83255625a80a23dfdc94d43be7791ce90799d446b4e26f", 511 | "sha256:e960e8be509e8d6d618300a6c189555c24efde63e85acaf0b14b2cd1ac743315", 512 | "sha256:ecb5dd5990cec6e7f5c9c1124a37cb2c710c6d69b0c1a5c4aa4b35eba0ada068", 513 | "sha256:ef3f5e288aa57b73b034ce9c1f1ac753d968f9069cd0742d1d69c698a0167166", 514 | "sha256:fa5b2dee0e231fa4ad117be114251bdfe6afe39213bd629d43deb117b6a6c40a", 515 | "sha256:fa7fb7973c622b9e725bee1db569d2c2ee64d2f9a089201c5e8185d482c7352d" 516 | ], 517 | "version": "==1.5.2" 518 | }, 519 | "mccabe": { 520 | "hashes": [ 521 | "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", 522 | "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" 523 | ], 524 | "version": "==0.6.1" 525 | }, 526 | "mypy": { 527 | "hashes": [ 528 | "sha256:0d0a87c0e7e3a9becdfbe936c981d32e5ee0ccda3e0f07e1ef2c3d1a817cf73e", 529 | "sha256:25adde9b862f8f9aac9d2d11971f226bd4c8fbaa89fb76bdadb267ef22d10064", 530 | "sha256:28fb5479c494b1bab244620685e2eb3c3f988d71fd5d64cc753195e8ed53df7c", 531 | "sha256:2f9b3407c58347a452fc0736861593e105139b905cca7d097e413453a1d650b4", 532 | "sha256:33f159443db0829d16f0a8d83d94df3109bb6dd801975fe86bacb9bf71628e97", 533 | "sha256:3f2aca7f68580dc2508289c729bd49ee929a436208d2b2b6aab15745a70a57df", 534 | "sha256:499c798053cdebcaa916eef8cd733e5584b5909f789de856b482cd7d069bdad8", 535 | "sha256:4eec37370483331d13514c3f55f446fc5248d6373e7029a29ecb7b7494851e7a", 536 | "sha256:552a815579aa1e995f39fd05dde6cd378e191b063f031f2acfe73ce9fb7f9e56", 537 | "sha256:5873888fff1c7cf5b71efbe80e0e73153fe9212fafdf8e44adfe4c20ec9f82d7", 538 | "sha256:61a3d5b97955422964be6b3baf05ff2ce7f26f52c85dd88db11d5e03e146a3a6", 539 | "sha256:674e822aa665b9fd75130c6c5f5ed9564a38c6cea6a6432ce47eafb68ee578c5", 540 | "sha256:7ce3175801d0ae5fdfa79b4f0cfed08807af4d075b402b7e294e6aa72af9aa2a", 541 | "sha256:9743c91088d396c1a5a3c9978354b61b0382b4e3c440ce83cf77994a43e8c521", 542 | "sha256:9f94aac67a2045ec719ffe6111df543bac7874cee01f41928f6969756e030564", 543 | "sha256:a26f8ec704e5a7423c8824d425086705e381b4f1dfdef6e3a1edab7ba174ec49", 544 | "sha256:abf7e0c3cf117c44d9285cc6128856106183938c68fd4944763003decdcfeb66", 545 | "sha256:b09669bcda124e83708f34a94606e01b614fa71931d356c1f1a5297ba11f110a", 546 | "sha256:cd07039aa5df222037005b08fbbfd69b3ab0b0bd7a07d7906de75ae52c4e3119", 547 | "sha256:d23e0ea196702d918b60c8288561e722bf437d82cb7ef2edcd98cfa38905d506", 548 | "sha256:d65cc1df038ef55a99e617431f0553cd77763869eebdf9042403e16089fe746c", 549 | "sha256:d7da2e1d5f558c37d6e8c1246f1aec1e7349e4913d8fb3cb289a35de573fe2eb" 550 | ], 551 | "index": "pypi", 552 | "version": "==0.812" 553 | }, 554 | "mypy-extensions": { 555 | "hashes": [ 556 | "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", 557 | "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" 558 | ], 559 | "index": "pypi", 560 | "version": "==0.4.3" 561 | }, 562 | "orderedmultidict": { 563 | "hashes": [ 564 | "sha256:04070bbb5e87291cc9bfa51df413677faf2141c73c61d2a5f7b26bea3cd882ad", 565 | "sha256:43c839a17ee3cdd62234c47deca1a8508a3f2ca1d0678a3bf791c87cf84adbf3" 566 | ], 567 | "version": "==1.0.1" 568 | }, 569 | "packaging": { 570 | "hashes": [ 571 | "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5", 572 | "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a" 573 | ], 574 | "version": "==20.9" 575 | }, 576 | "paramiko": { 577 | "hashes": [ 578 | "sha256:4f3e316fef2ac628b05097a637af35685183111d4bc1b5979bd397c2ab7b5898", 579 | "sha256:7f36f4ba2c0d81d219f4595e35f70d56cc94f9ac40a6acdf51d6ca210ce65035" 580 | ], 581 | "version": "==2.7.2" 582 | }, 583 | "pathspec": { 584 | "hashes": [ 585 | "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd", 586 | "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d" 587 | ], 588 | "version": "==0.8.1" 589 | }, 590 | "pep517": { 591 | "hashes": [ 592 | "sha256:3985b91ebf576883efe5fa501f42a16de2607684f3797ddba7202b71b7d0da51", 593 | "sha256:aeb78601f2d1aa461960b43add204cc7955667687fbcf9cdb5170f00556f117f" 594 | ], 595 | "version": "==0.9.1" 596 | }, 597 | "pip-shims": { 598 | "hashes": [ 599 | "sha256:05b00ade9d1e686a98bb656dd9b0608a933897283dc21913fad6ea5409ff7e91", 600 | "sha256:16ca9f87485667b16b978b68a1aae4f9cc082c0fa018aed28567f9f34a590569" 601 | ], 602 | "version": "==0.5.3" 603 | }, 604 | "pipenv-setup": { 605 | "hashes": [ 606 | "sha256:8a439aff7b16e18d7e07702c9186fc5fe86156679eace90e10c2578a43bd7af1", 607 | "sha256:e1bfd55c1152024e762f1c17f6189fcb073166509e7c0228870f7ea160355648" 608 | ], 609 | "index": "pypi", 610 | "version": "==3.1.1" 611 | }, 612 | "pipfile": { 613 | "hashes": [ 614 | "sha256:f7d9f15de8b660986557eb3cc5391aa1a16207ac41bc378d03f414762d36c984" 615 | ], 616 | "version": "==0.0.2" 617 | }, 618 | "pkginfo": { 619 | "hashes": [ 620 | "sha256:029a70cb45c6171c329dfc890cde0879f8c52d6f3922794796e06f577bb03db4", 621 | "sha256:9fdbea6495622e022cc72c2e5e1b735218e4ffb2a2a69cde2694a6c1f16afb75" 622 | ], 623 | "version": "==1.7.0" 624 | }, 625 | "plette": { 626 | "extras": [ 627 | "validation" 628 | ], 629 | "hashes": [ 630 | "sha256:46402c03e36d6eadddad2a5125990e322dd74f98160c8f2dcd832b2291858a26", 631 | "sha256:d6c9b96981b347bddd333910b753b6091a2c1eb2ef85bb373b4a67c9d91dca16" 632 | ], 633 | "version": "==0.2.3" 634 | }, 635 | "pluggy": { 636 | "hashes": [ 637 | "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", 638 | "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" 639 | ], 640 | "version": "==0.13.1" 641 | }, 642 | "protobuf": { 643 | "hashes": [ 644 | "sha256:03f6ee325710eb164bd85741721fbd4326c399b0ecf49dddba9172df9149c124", 645 | "sha256:0644b70bc9d36329438de0da619e3337ab4eade784a9acc6ba8e5ed22f2e9e50", 646 | "sha256:0938b13c2a5ad0ce2b75c19dc0c2082f721a61b97d3f11d73ee4412dfb6e06eb", 647 | "sha256:165071fdfaf4d7ff7a70d2197bba048fb301c7b957095eedf4bf8379d904adb1", 648 | "sha256:17a26d5a7757211ce60032f0111dd426d4e5f44145ac6e86fa241e0cffe9df17", 649 | "sha256:28daf1c44cf11c70f3852bd13f8fc6f7f1f211abbf068ffbeb25f8e4e2f6c98b", 650 | "sha256:3188af446c544df79525d66e2d987490053262b81226fc6fa3f00556135f7e8a", 651 | "sha256:509fba6d57f0c1dc483f91754a33a5d8632da1bf75d87b6c127bcf0e3966fa44", 652 | "sha256:5810e9e3851ab8aa28624bdc947f9236ce7ec2be2f63b88b373fdc92791fbf86", 653 | "sha256:60fd96bc77293d9770d133cdbf3af9ff2373ce11d2055d2ca581db2330fe6805", 654 | "sha256:763a9444bafd2204cdeb29be54147ce7cfae04df805161507426c215a461ae6e", 655 | "sha256:824dbae3390fcc3ea1bf96748e6da951a601802894cf7e1465e72b4732538cab", 656 | "sha256:a8cccf2d2df2675f10a47f963f8010516f6aff09db7d134b0b0e57422ce07f78", 657 | "sha256:c70647b71385302efb615d25c643f1b92784201f7b4ed2d9ff472e4c869ccad5", 658 | "sha256:d3797255e8fbf234477332864f0304222b2492dfd91e95e6314326dbf0e235e2", 659 | "sha256:d52494780f89d1277f982c209197ce1da91d416c27ba9f4495d339ac6a3bac02", 660 | "sha256:d7576c8b59288c5feea161d9ed74925d26759963b51f850d8eadd7a88b4e0ddf", 661 | "sha256:de2e543ffb1701ea8fe7077ba571dbaa1980876d1817f6a70b984064dcb20e6f", 662 | "sha256:edae67da507393f377555531cb7afa1714c75a84404f3541ef5df36ce3637768", 663 | "sha256:f49a1721f2a3d72466aa19f095cc3fe2883b5e1868f4a1e9f51043df8ecb0140" 664 | ], 665 | "version": "==3.15.1" 666 | }, 667 | "py": { 668 | "hashes": [ 669 | "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3", 670 | "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a" 671 | ], 672 | "version": "==1.10.0" 673 | }, 674 | "pycodestyle": { 675 | "hashes": [ 676 | "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367", 677 | "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e" 678 | ], 679 | "version": "==2.6.0" 680 | }, 681 | "pycparser": { 682 | "hashes": [ 683 | "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", 684 | "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" 685 | ], 686 | "version": "==2.20" 687 | }, 688 | "pyflakes": { 689 | "hashes": [ 690 | "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92", 691 | "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8" 692 | ], 693 | "version": "==2.2.0" 694 | }, 695 | "pygate-grpc": { 696 | "editable": true, 697 | "path": "." 698 | }, 699 | "pygments": { 700 | "hashes": [ 701 | "sha256:37a13ba168a02ac54cc5891a42b1caec333e59b66addb7fa633ea8a6d73445c0", 702 | "sha256:b21b072d0ccdf29297a82a2363359d99623597b8a265b8081760e4d0f7153c88" 703 | ], 704 | "version": "==2.8.0" 705 | }, 706 | "pylint": { 707 | "hashes": [ 708 | "sha256:2e0c6749d809985e4f181c336a8f89b2b797340d8049160bf95f35a3f0ecf6fc", 709 | "sha256:3ea3926700db399765db1faf53860f11e4e981a090646e9eacd01ca78e020579" 710 | ], 711 | "index": "pypi", 712 | "version": "==2.7.0" 713 | }, 714 | "pynacl": { 715 | "hashes": [ 716 | "sha256:06cbb4d9b2c4bd3c8dc0d267416aaed79906e7b33f114ddbf0911969794b1cc4", 717 | "sha256:11335f09060af52c97137d4ac54285bcb7df0cef29014a1a4efe64ac065434c4", 718 | "sha256:2fe0fc5a2480361dcaf4e6e7cea00e078fcda07ba45f811b167e3f99e8cff574", 719 | "sha256:30f9b96db44e09b3304f9ea95079b1b7316b2b4f3744fe3aaecccd95d547063d", 720 | "sha256:4e10569f8cbed81cb7526ae137049759d2a8d57726d52c1a000a3ce366779634", 721 | "sha256:511d269ee845037b95c9781aa702f90ccc36036f95d0f31373a6a79bd8242e25", 722 | "sha256:537a7ccbea22905a0ab36ea58577b39d1fa9b1884869d173b5cf111f006f689f", 723 | "sha256:54e9a2c849c742006516ad56a88f5c74bf2ce92c9f67435187c3c5953b346505", 724 | "sha256:757250ddb3bff1eecd7e41e65f7f833a8405fede0194319f87899690624f2122", 725 | "sha256:7757ae33dae81c300487591c68790dfb5145c7d03324000433d9a2c141f82af7", 726 | "sha256:7c6092102219f59ff29788860ccb021e80fffd953920c4a8653889c029b2d420", 727 | "sha256:8122ba5f2a2169ca5da936b2e5a511740ffb73979381b4229d9188f6dcb22f1f", 728 | "sha256:9c4a7ea4fb81536c1b1f5cc44d54a296f96ae78c1ebd2311bd0b60be45a48d96", 729 | "sha256:c914f78da4953b33d4685e3cdc7ce63401247a21425c16a39760e282075ac4a6", 730 | "sha256:cd401ccbc2a249a47a3a1724c2918fcd04be1f7b54eb2a5a71ff915db0ac51c6", 731 | "sha256:d452a6746f0a7e11121e64625109bc4468fc3100452817001dbe018bb8b08514", 732 | "sha256:ea6841bc3a76fa4942ce00f3bda7d436fda21e2d91602b9e21b7ca9ecab8f3ff", 733 | "sha256:f8851ab9041756003119368c1e6cd0b9c631f46d686b3904b18c0139f4419f80" 734 | ], 735 | "version": "==1.4.0" 736 | }, 737 | "pyparsing": { 738 | "hashes": [ 739 | "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", 740 | "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" 741 | ], 742 | "version": "==2.4.7" 743 | }, 744 | "pyrsistent": { 745 | "hashes": [ 746 | "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e" 747 | ], 748 | "version": "==0.17.3" 749 | }, 750 | "pytest": { 751 | "hashes": [ 752 | "sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9", 753 | "sha256:b574b57423e818210672e07ca1fa90aaf194a4f63f3ab909a2c67ebb22913839" 754 | ], 755 | "index": "pypi", 756 | "version": "==6.2.2" 757 | }, 758 | "pytest-docker": { 759 | "hashes": [ 760 | "sha256:7a0544dc00c83fd7808907a55835b1b7c07e3ea2d22ee558b5a233247e462735", 761 | "sha256:9faa9c87e6e0920612005c8d187c58b3d336f3af49a878f358578de125963858" 762 | ], 763 | "index": "pypi", 764 | "version": "==0.10.1" 765 | }, 766 | "python-dateutil": { 767 | "hashes": [ 768 | "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", 769 | "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" 770 | ], 771 | "version": "==2.8.1" 772 | }, 773 | "python-dotenv": { 774 | "hashes": [ 775 | "sha256:0c8d1b80d1a1e91717ea7d526178e3882732420b03f08afea0406db6402e220e", 776 | "sha256:587825ed60b1711daea4832cf37524dfd404325b7db5e25ebe88c495c9f807a0" 777 | ], 778 | "version": "==0.15.0" 779 | }, 780 | "pyyaml": { 781 | "hashes": [ 782 | "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf", 783 | "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696", 784 | "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393", 785 | "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77", 786 | "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922", 787 | "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5", 788 | "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8", 789 | "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10", 790 | "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc", 791 | "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018", 792 | "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e", 793 | "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253", 794 | "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183", 795 | "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb", 796 | "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185", 797 | "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db", 798 | "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46", 799 | "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b", 800 | "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63", 801 | "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df", 802 | "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc" 803 | ], 804 | "version": "==5.4.1" 805 | }, 806 | "readme-renderer": { 807 | "hashes": [ 808 | "sha256:63b4075c6698fcfa78e584930f07f39e05d46f3ec97f65006e430b595ca6348c", 809 | "sha256:92fd5ac2bf8677f310f3303aa4bce5b9d5f9f2094ab98c29f13791d7b805a3db" 810 | ], 811 | "version": "==29.0" 812 | }, 813 | "regex": { 814 | "hashes": [ 815 | "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538", 816 | "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4", 817 | "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc", 818 | "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa", 819 | "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444", 820 | "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1", 821 | "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af", 822 | "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8", 823 | "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9", 824 | "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88", 825 | "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba", 826 | "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364", 827 | "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e", 828 | "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7", 829 | "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0", 830 | "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31", 831 | "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683", 832 | "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee", 833 | "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b", 834 | "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884", 835 | "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c", 836 | "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e", 837 | "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562", 838 | "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85", 839 | "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c", 840 | "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6", 841 | "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d", 842 | "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b", 843 | "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70", 844 | "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b", 845 | "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b", 846 | "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f", 847 | "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0", 848 | "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5", 849 | "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5", 850 | "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f", 851 | "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e", 852 | "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512", 853 | "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d", 854 | "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917", 855 | "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f" 856 | ], 857 | "version": "==2020.11.13" 858 | }, 859 | "requests": { 860 | "hashes": [ 861 | "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", 862 | "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" 863 | ], 864 | "version": "==2.25.1" 865 | }, 866 | "requests-toolbelt": { 867 | "hashes": [ 868 | "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f", 869 | "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0" 870 | ], 871 | "version": "==0.9.1" 872 | }, 873 | "requirementslib": { 874 | "hashes": [ 875 | "sha256:50d20f27e4515a2393695b0d886219598302163438ae054253147b2bad9b4a44", 876 | "sha256:9c1e8666ca4512724cdd1739adcc7df19ec7ad2ed21f0e748f9631ad6b54f321" 877 | ], 878 | "version": "==1.5.16" 879 | }, 880 | "rfc3986": { 881 | "hashes": [ 882 | "sha256:112398da31a3344dc25dbf477d8df6cb34f9278a94fee2625d89e4514be8bb9d", 883 | "sha256:af9147e9aceda37c91a05f4deb128d4b4b49d6b199775fd2d2927768abdc8f50" 884 | ], 885 | "version": "==1.4.0" 886 | }, 887 | "secretstorage": { 888 | "hashes": [ 889 | "sha256:422d82c36172d88d6a0ed5afdec956514b189ddbfb72fefab0c8a1cee4eaf71f", 890 | "sha256:fd666c51a6bf200643495a04abb261f83229dcb6fd8472ec393df7ffc8b6f195" 891 | ], 892 | "index": "pypi", 893 | "markers": "sys_platform == 'linux'", 894 | "version": "==3.3.1" 895 | }, 896 | "six": { 897 | "hashes": [ 898 | "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", 899 | "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" 900 | ], 901 | "version": "==1.15.0" 902 | }, 903 | "smmap": { 904 | "hashes": [ 905 | "sha256:7bfcf367828031dc893530a29cb35eb8c8f2d7c8f2d0989354d75d24c8573714", 906 | "sha256:84c2751ef3072d4f6b2785ec7ee40244c6f45eb934d9e543e2c51f1bd3d54c50" 907 | ], 908 | "version": "==3.0.5" 909 | }, 910 | "texttable": { 911 | "hashes": [ 912 | "sha256:ce0faf21aa77d806bbff22b107cc22cce68dc9438f97a2df32c93e9afa4ce436", 913 | "sha256:f802f2ef8459058736264210f716c757cbf85007a30886d8541aa8c3404f1dda" 914 | ], 915 | "version": "==1.6.3" 916 | }, 917 | "toml": { 918 | "hashes": [ 919 | "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", 920 | "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" 921 | ], 922 | "version": "==0.10.2" 923 | }, 924 | "tomlkit": { 925 | "hashes": [ 926 | "sha256:6babbd33b17d5c9691896b0e68159215a9387ebfa938aa3ac42f4a4beeb2b831", 927 | "sha256:ac57f29693fab3e309ea789252fcce3061e19110085aa31af5446ca749325618" 928 | ], 929 | "version": "==0.7.0" 930 | }, 931 | "tqdm": { 932 | "hashes": [ 933 | "sha256:65185676e9fdf20d154cffd1c5de8e39ef9696ff7e59fe0156b1b08e468736af", 934 | "sha256:70657337ec104eb4f3fb229285358f23f045433f6aea26846cdd55f0fd68945c" 935 | ], 936 | "version": "==4.57.0" 937 | }, 938 | "twine": { 939 | "hashes": [ 940 | "sha256:2f6942ec2a17417e19d2dd372fc4faa424c87ee9ce49b4e20c427eb00a0f3f41", 941 | "sha256:fcffa8fc37e8083a5be0728371f299598870ee1eccc94e9a25cef7b1dcfa8297" 942 | ], 943 | "index": "pypi", 944 | "version": "==3.3.0" 945 | }, 946 | "typed-ast": { 947 | "hashes": [ 948 | "sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1", 949 | "sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d", 950 | "sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6", 951 | "sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd", 952 | "sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37", 953 | "sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151", 954 | "sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07", 955 | "sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440", 956 | "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70", 957 | "sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496", 958 | "sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea", 959 | "sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400", 960 | "sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc", 961 | "sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606", 962 | "sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc", 963 | "sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581", 964 | "sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412", 965 | "sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a", 966 | "sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2", 967 | "sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787", 968 | "sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f", 969 | "sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937", 970 | "sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64", 971 | "sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487", 972 | "sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b", 973 | "sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41", 974 | "sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a", 975 | "sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3", 976 | "sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166", 977 | "sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10" 978 | ], 979 | "markers": "implementation_name == 'cpython' and python_version < '3.8'", 980 | "version": "==1.4.2" 981 | }, 982 | "typing-extensions": { 983 | "hashes": [ 984 | "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918", 985 | "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c", 986 | "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f" 987 | ], 988 | "markers": "python_version < '3.8'", 989 | "version": "==3.7.4.3" 990 | }, 991 | "urllib3": { 992 | "hashes": [ 993 | "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80", 994 | "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73" 995 | ], 996 | "version": "==1.26.3" 997 | }, 998 | "vistir": { 999 | "hashes": [ 1000 | "sha256:a37079cdbd85d31a41cdd18457fe521e15ec08b255811e81aa061fd5f48a20fb", 1001 | "sha256:eff1d19ef50c703a329ed294e5ec0b0fbb35b96c1b3ee6dcdb266dddbe1e935a" 1002 | ], 1003 | "version": "==0.5.2" 1004 | }, 1005 | "webencodings": { 1006 | "hashes": [ 1007 | "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", 1008 | "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" 1009 | ], 1010 | "version": "==0.5.1" 1011 | }, 1012 | "websocket-client": { 1013 | "hashes": [ 1014 | "sha256:0fc45c961324d79c781bab301359d5a1b00b13ad1b10415a4780229ef71a5549", 1015 | "sha256:d735b91d6d1692a6a181f2a8c9e0238e5f6373356f561bb9dc4c7af36f452010" 1016 | ], 1017 | "version": "==0.57.0" 1018 | }, 1019 | "wheel": { 1020 | "hashes": [ 1021 | "sha256:78b5b185f0e5763c26ca1e324373aadd49182ca90e825f7853f4b2509215dc0e", 1022 | "sha256:e11eefd162658ea59a60a0f6c7d493a7190ea4b9a85e335b33489d9f17e0245e" 1023 | ], 1024 | "index": "pypi", 1025 | "version": "==0.36.2" 1026 | }, 1027 | "wrapt": { 1028 | "hashes": [ 1029 | "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7" 1030 | ], 1031 | "version": "==1.12.1" 1032 | }, 1033 | "zipp": { 1034 | "hashes": [ 1035 | "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108", 1036 | "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb" 1037 | ], 1038 | "markers": "python_version < '3.8'", 1039 | "version": "==3.4.0" 1040 | } 1041 | } 1042 | } 1043 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pygate gRPC client 2 | 3 | [![CodeFactor](https://www.codefactor.io/repository/github/pygate/pygate-grpc/badge)](https://www.codefactor.io/repository/github/pygate/pygate-grpc) 4 | [![PyPI version](https://badge.fury.io/py/pygate-grpc.svg)](https://badge.fury.io/py/pygate-grpc) 5 | ![Tests](https://github.com/pygate/pygate-gRPC/workflows/Tests/badge.svg) 6 | [![Downloads](https://pepy.tech/badge/pygate-grpc)](https://pepy.tech/project/pygate-grpc) 7 | [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) 8 | 9 | ## Background 10 | 11 | A Python interface to [Textile](https://textile.io/)'s [Powergate](https://docs.textile.io/powergate/) [Filecoin](https://filecoin.io/) API. See the project [website](http://pygate.tech) for more details. 12 | 13 | ## Install 14 | 15 | You can get started using `pygate_grpc` by installing it through the PyPi repository. 16 | 17 | ``` 18 | pip install pygate_grpc 19 | ``` 20 | 21 | ## Usage 22 | 23 | The main component of the package is the `PowerGateClient` class. 24 | 25 | Here is a basic usage example of the pygate_grpc: 26 | 27 | ```python 28 | from pygate_grpc.client import PowerGateClient 29 | 30 | client = PowerGateClient("127.0.0.1:5002", is_secure=False) 31 | 32 | build_info = client.build_info() 33 | ``` 34 | 35 | Simple as that! 36 | 37 | Note: this examples assumes you have a Powergate server running with an API available at `127.0.0.1:5002`. See Textile's Powergate [Localnet](https://docs.textile.io/powergate/localnet/). The `is_secure=False` flag indicates that SSL is not enabled on this server. 38 | 39 | Examples of more elaborated usage can be found in the [examples](./examples/) folder. 40 | 41 | # Contributing 42 | 43 | Please read contribution [guidelines](CONTRIBUTING.md) before starting development. 44 | 45 | To setup your development environment make sure you have the following software: 46 | 47 | - [Git](https://git-scm.com/) 48 | - [Python](https://www.python.org/downloads/release/python-370/) 49 | - [pip](https://pip.pypa.io/en/stable/installing/) 50 | - [pipenv](https://pypi.org/project/pipenv/) ( or run `pip install pipenv`) 51 | 52 | ## Clone the repository 53 | ``` 54 | git clone https://github.com/pygate/pygate-gRPC.git 55 | ``` 56 | 57 | ## Install dependencies 58 | 59 | The runtime and development dependencies can be installed in a new virtual environment automatically by running the following command in the project root directory: 60 | 61 | ``` 62 | pipenv install --dev 63 | ``` 64 | 65 | NOTE: The `--dev` flag can be ommited if you only need runtime dependencies 66 | 67 | ### **Using the virtual environment** 68 | 69 | To run any command through pipenv's virtual environment you can spawn a new virtual environment shell by running: 70 | 71 | ``` 72 | pipenv shell 73 | ``` 74 | 75 | ## Code Style 76 | 77 | This project uses [black](https://pypi.org/project/black/) code formatter for consistency. Since the are not any precommit hooks defined in the repository yet please format your code before opening a pull request. 78 | 79 | Automatic formatting can be performed by running: 80 | ``` 81 | pipenv run format 82 | ``` 83 | 84 | ## Running the tests 85 | 86 | Currently the test suite is very minimal. Full Testing is in the project's roadmap but it will be developed only if the timeframe of the Hackathon allows to do so. 87 | 88 | ### **Integration Tests** 89 | 90 | Integration tests spin up a localnet using the official script from powergate repository and the test cases are run using that network. By implication, to run the test make sure you have the following dependencies installed: 91 | 92 | - docker-compose 93 | - docker 94 | - git 95 | 96 | To run the integration tests run: 97 | 98 | ``` 99 | pipenv run integration-test 100 | ``` 101 | 102 | ## Versioning 103 | 104 | We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/apogiatzis/powsolver/tags). 105 | 106 | To automatically bump the version of the package run: 107 | ``` 108 | bump2version major|minor|patch setup.py 109 | ``` 110 | 111 | Finally, to push the new version to git and trigger a new release action it is necessary to add the `--tags` flag at the time of pushing. i.e.: 112 | ``` 113 | git push origin main --tags 114 | ``` 115 | 116 | ## License 117 | 118 | [MIT © Antreas Pogiatzis, Wang Ge, Peter Van Garderen, Aaron Sutula](LICENSE) 119 | 120 | 121 | -------------------------------------------------------------------------------- /examples/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pygate/pygate-gRPC/257f996c476f5fef3e8d9f73586ffff76a09986e/examples/.gitkeep -------------------------------------------------------------------------------- /examples/cidconfig_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "hot": { 3 | "enabled": true, 4 | "ipfs": { 5 | "addTimeout": "30" 6 | } 7 | }, 8 | "cold": { 9 | "enabled": true, 10 | "filecoin": { 11 | "replicationFactor": "1", 12 | "dealMinDuration": "518400", 13 | "excludedMiners": ["t01101"], 14 | "countryCodes": ["ca", "nl"], 15 | "renew": { 16 | "enabled": true, 17 | "threshold": "3" 18 | }, 19 | "address": "f3ugglyobkqbsi7m7vgcnvpk7mswinrv5s6wm2abrmeq6m5zw2i2xwknjxg566konp4esxfnxjaefaa3uy3waa", 20 | "maxPrice": "5" 21 | } 22 | }, 23 | "repairable": true 24 | 25 | } -------------------------------------------------------------------------------- /examples/config.py: -------------------------------------------------------------------------------- 1 | from pygate_grpc.client import PowerGateClient 2 | 3 | import os 4 | import json 5 | from pathlib import Path 6 | 7 | 8 | client = PowerGateClient("127.0.0.1:5002", False) 9 | 10 | print("Creating a new user:") 11 | user = client.admin.users.create() 12 | tk = user.token 13 | print("Token: " + tk) 14 | print("Using the new user token to request the default config:") 15 | default_config = client.config.default(tk) 16 | 17 | wallets = client.wallet.addresses(token=tk) 18 | print("Addresses: {0}".format(default_config)) 19 | 20 | print("Loading new default config...") 21 | path = Path(os.path.abspath(__file__)) 22 | with open(path.parent / "cidconfig_example.json", "r") as f: 23 | config = json.load(f) 24 | config["cold"]["filecoin"]["address"] = wallets[0].address 25 | 26 | client.config.set_default(config, tk) 27 | 28 | default_config = client.config.default(tk) 29 | print("Updated default config:") 30 | print(default_config) 31 | 32 | test_bytes = b"These are some test bytes" 33 | staged_file = client.data.stage_bytes(test_bytes, user.token) 34 | print("StagedFile: ", staged_file) 35 | 36 | print("Disabling from hot and cold storage...") 37 | config["cold"]["enabled"] = False 38 | config["hot"]["enabled"] = False 39 | stage = client.config.apply( 40 | staged_file.cid, token=user.token, config=config, no_exec=False, import_deal_ids=[] 41 | ) 42 | 43 | print("Removing...") 44 | client.config.remove(staged_file.cid, token=user.token) 45 | -------------------------------------------------------------------------------- /examples/deals.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from pygate_grpc.client import PowerGateClient 4 | 5 | if __name__ == "__main__": 6 | 7 | hostName = "127.0.0.1:5002" 8 | 9 | # Create client 10 | client = PowerGateClient(hostName) 11 | 12 | # Create user 13 | user = client.admin.users.create() 14 | print("User created:") 15 | print(user) 16 | 17 | print("Applying storage config...") 18 | stage_res = client.data.stage_bytes( 19 | b"These are the contents of a test file", token=user.token 20 | ) 21 | apply_res = client.config.apply(stage_res.cid, token=user.token) 22 | 23 | # Check that cid is in the process of being stored by Powegate 24 | check = client.data.cid_info(stage_res.cid, user.token) 25 | print("Checking cid storage...") 26 | print(check) 27 | 28 | # Wait some time so that we can get some deals 29 | time.sleep(10) 30 | 31 | # Check information about the storage deal 32 | storage_deals = client.deals.storage_deal_records( 33 | include_pending=True, include_final=True, token=user.token 34 | ) 35 | print("Storage deals: ") 36 | for record in storage_deals: 37 | print(record) 38 | 39 | # Check information about the retrieval deals 40 | retrieval_deals = client.deals.retrieval_deal_records( 41 | include_pending=True, include_final=True, token=user.token 42 | ) 43 | print("Retrieval deals: ") 44 | for record in retrieval_deals: 45 | print(record) 46 | -------------------------------------------------------------------------------- /examples/logs.py: -------------------------------------------------------------------------------- 1 | from pygate_grpc.client import PowerGateClient 2 | from pygate_grpc.exceptions import GRPCTimeoutException 3 | 4 | client = PowerGateClient("127.0.0.1:5002", False) 5 | 6 | print("Creating a new user:") 7 | user = client.admin.users.create() 8 | print(user) 9 | 10 | 11 | stage_res = client.data.stage_bytes( 12 | b"These are the contents of a test file", user.token 13 | ) 14 | apply_res = client.config.apply(stage_res.cid, token=user.token) 15 | logs_res = client.data.watch_logs(stage_res.cid, user.token, history=True, timeout=5) 16 | 17 | logs = [] 18 | # iterating through the logs is a blocking operation, by using a timeout and 19 | # the following exception handling we can make sure that the operation exits 20 | # after the specified timeout. 21 | try: 22 | for f in logs_res: 23 | logs.append(f) 24 | except GRPCTimeoutException: 25 | pass 26 | 27 | print(logs) 28 | -------------------------------------------------------------------------------- /examples/storage_info.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | from pygate_grpc.client import PowerGateClient 4 | 5 | 6 | if __name__ == "__main__": 7 | 8 | hostName = "127.0.0.1:5002" 9 | 10 | # Create client 11 | c = PowerGateClient(hostName, False) 12 | 13 | # Create user 14 | user = c.admin.users.create() 15 | print("User created:") 16 | print(user) 17 | 18 | # Stage file 19 | print("Staging 'testfile.txt' to IPFS storage...") 20 | path = Path(os.path.abspath(__file__)) 21 | staged_file = c.data.stage_file(path.parent / "testfile.txt", user.token) 22 | print("IPFS CID: " + staged_file.cid) 23 | 24 | # Apply the default storage config to the given file 25 | print("Applying Filecoin storage config to CID...") 26 | job = c.config.apply(staged_file.cid, override=False, token=user.token) 27 | 28 | # Report back the Job ID for the successful Filecoin storage job 29 | print("File successfully added to Filecoin storage.") 30 | print("Job ID: " + job.jobId) 31 | 32 | storage_infos = c.storage_info.list(cids=[staged_file.cid], token=user.token) 33 | print(storage_infos) 34 | -------------------------------------------------------------------------------- /examples/storage_jobs.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | from pygate_grpc.client import PowerGateClient 4 | 5 | 6 | if __name__ == "__main__": 7 | 8 | hostName = "127.0.0.1:5002" 9 | 10 | # Create client 11 | c = PowerGateClient(hostName, False) 12 | 13 | # Create user 14 | user = c.admin.users.create() 15 | print("User created:") 16 | print(user) 17 | 18 | # Stage file 19 | print("Staging 'testfile.txt' to IPFS storage...") 20 | path = Path(os.path.abspath(__file__)) 21 | staged_file = c.data.stage_file(path.parent / "testfile.txt", user.token) 22 | print("IPFS CID: " + staged_file.cid) 23 | 24 | # Apply the default storage config to the given file 25 | print("Applying Filecoin storage config to CID...") 26 | job = c.config.apply(staged_file.cid, override=False, token=user.token) 27 | 28 | # Report back the Job ID for the successful Filecoin storage job 29 | print("File successfully added to Filecoin storage.") 30 | print("Job ID: " + job.jobId) 31 | 32 | storage_job = c.storage_jobs.storage_job(job.jobId, token=user.token) 33 | storage_config = c.storage_jobs.storage_config_for_job(job.jobId, token=user.token) 34 | jobs_list = c.storage_jobs.list(cid_filter=staged_file.cid, token=user.token) 35 | -------------------------------------------------------------------------------- /examples/store_retrieve.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | 4 | from pygate_grpc.client import PowerGateClient 5 | 6 | 7 | if __name__ == "__main__": 8 | 9 | hostName = "127.0.0.1:5002" 10 | 11 | # Create client 12 | c = PowerGateClient(hostName, False) 13 | 14 | # Create user 15 | user = c.admin.users.create() 16 | print("User created:") 17 | print(user) 18 | 19 | # Stage file 20 | print("Staging 'testfile.txt' to IPFS storage...") 21 | path = Path(os.path.abspath(__file__)) 22 | staged_file = c.data.stage_file(path.parent / "testfile.txt", user.token) 23 | print("IPFS CID: " + staged_file.cid) 24 | 25 | # Apply the default storage config to the given file 26 | print("Applying Filecoin storage config to CID...") 27 | job = c.config.apply(staged_file.cid, override=False, token=user.token) 28 | 29 | # Report back the Job ID for the successful Filecoin storage job 30 | print("File successfully added to Filecoin storage.") 31 | print("Job ID: " + job.jobId) 32 | 33 | # Override push with another config 34 | print("Applying new config settings to file...") 35 | addresses = c.wallet.addresses(user.token) 36 | wallet = addresses[0].address 37 | new_config = { 38 | "hot": {"enabled": True, "allowUnfreeze": True, "ipfs": {"addTimeout": 30}}, 39 | "cold": { 40 | "enabled": True, 41 | "filecoin": { 42 | "replicationFactor": 1, 43 | "dealMinDuration": 518400, 44 | "excludedMiners": ["t01101"], 45 | "trustedMiners": ["t01000", "t02000"], 46 | "countryCodes": ["ca", "nl"], 47 | "renew": {"enabled": True, "threshold": 3}, 48 | "address": wallet, 49 | "maxPrice": 50, 50 | }, 51 | }, 52 | "repairable": True, 53 | } 54 | 55 | c.config.apply(staged_file.cid, override=True, config=new_config, token=user.token) 56 | 57 | # Check that CID is stored 58 | check = c.data.cid_info(staged_file.cid, user.token) 59 | print("Checking CID storage...") 60 | print(check) 61 | 62 | # Get the data back 63 | print("Retrieving file " + staged_file.cid + "...") 64 | file_bytes = c.data.get(staged_file.cid, user.token) 65 | 66 | # Write to a file on disk 67 | with open(path.parent / "testfile_copy.txt", "wb") as f: 68 | f.write(file_bytes) 69 | print("Saved as 'testfile_copy.txt'") 70 | 71 | cid_summary = c.data.cid_summary(cids=[staged_file.cid], token=user.token) 72 | print("CID summary: ", cid_summary) 73 | -------------------------------------------------------------------------------- /examples/testfile.txt: -------------------------------------------------------------------------------- 1 | This is a test file -------------------------------------------------------------------------------- /examples/user_create.py: -------------------------------------------------------------------------------- 1 | from pygate_grpc.client import PowerGateClient 2 | 3 | client = PowerGateClient("127.0.0.1:5002", False) 4 | 5 | print("Creating a new user:") 6 | new_user = client.admin.users.create() 7 | print(new_user) 8 | -------------------------------------------------------------------------------- /examples/version.py: -------------------------------------------------------------------------------- 1 | from pygate_grpc.client import PowerGateClient 2 | 3 | client = PowerGateClient("127.0.0.1:5002", False) 4 | 5 | build_info = client.build_info() 6 | print(build_info) 7 | 8 | user = client.admin.users.create() 9 | 10 | user_id = client.user_id(user.token) 11 | print(user_id) 12 | -------------------------------------------------------------------------------- /examples/wallet.py: -------------------------------------------------------------------------------- 1 | import time 2 | from pygate_grpc.client import PowerGateClient 3 | 4 | 5 | client = PowerGateClient("127.0.0.1:5002", False) 6 | 7 | all_addresses = client.admin.wallet.addresses() 8 | print("All addresses:") 9 | print(all_addresses) 10 | 11 | 12 | print("Creating a new user:") 13 | user = client.admin.users.create() 14 | print(user) 15 | 16 | addresses = client.wallet.addresses(user.token) 17 | address = addresses[0].address 18 | print("User wallet address: " + address) 19 | 20 | print("Waiting for wallet to get funding...") 21 | time.sleep(2) 22 | 23 | balance = client.wallet.balance(address) 24 | print("Wallet balance: ", str(balance)) 25 | -------------------------------------------------------------------------------- /logging.ini: -------------------------------------------------------------------------------- 1 | [loggers] 2 | keys=root 3 | 4 | [handlers] 5 | keys=stream_handler 6 | 7 | [formatters] 8 | keys=formatter 9 | 10 | [logger_root] 11 | level=DEBUG 12 | handlers=stream_handler 13 | 14 | [handler_stream_handler] 15 | class=StreamHandler 16 | level=DEBUG 17 | formatter=formatter 18 | args=(sys.stderr,) 19 | 20 | [formatter_formatter] 21 | format=%(asctime)s %(name)-12s %(levelname)-8s %(message)s -------------------------------------------------------------------------------- /pygate_grpc/admin/__init__.py: -------------------------------------------------------------------------------- 1 | from pygate_grpc.admin import storage_jobs, users, wallet 2 | from pygate_grpc.errors import ErrorHandlerMeta 3 | 4 | 5 | class AdminClient(object, metaclass=ErrorHandlerMeta): 6 | def __init__(self, channel, get_metadata): 7 | self.users = users.UsersClient(channel, get_metadata) 8 | self.storage_jobs = storage_jobs.StorageJobsClient(channel, get_metadata) 9 | self.wallet = wallet.WalletClient(channel, get_metadata) 10 | -------------------------------------------------------------------------------- /pygate_grpc/admin/storage_jobs.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from powergate.admin.v1 import admin_pb2, admin_pb2_grpc 4 | 5 | from pygate_grpc.errors import ErrorHandlerMeta 6 | 7 | 8 | class StorageJobsClient(object, metaclass=ErrorHandlerMeta): 9 | def __init__(self, channel, get_metadata): 10 | self.client = admin_pb2_grpc.AdminServiceStub(channel) 11 | self.get_metadata = get_metadata 12 | 13 | def queued(self, user_id: str, cids: List[str], admin_token: str = None): 14 | req = admin_pb2.QueuedStorageJobsRequest(cids=cids, user_id=user_id) 15 | return self.client.QueuedStorageJobs( 16 | req, metadata=self.get_metadata(admin_token) 17 | ) 18 | 19 | def executing(self, user_id: str, cids: List[str], admin_token: str = None): 20 | req = admin_pb2.ExecutingStorageJobsRequest(cids=cids, user_id=user_id) 21 | return self.client.ExecutingStorageJobs( 22 | req, metadata=self.get_metadata(admin_token) 23 | ) 24 | 25 | def latest_final(self, user_id: str, cids: List[str], admin_token: str = None): 26 | req = admin_pb2.LatestFinalStorageJobsRequest(cids=cids, user_id=user_id) 27 | return self.client.LatestFinalStorageJobs( 28 | req, metadata=self.get_metadata(admin_token) 29 | ) 30 | 31 | def latest_successful(self, user_id: str, cids: List[str], admin_token: str = None): 32 | req = admin_pb2.LatestSuccessfulStorageJobsRequest(cids=cids, user_id=user_id) 33 | return self.client.LatestSuccessfulStorageJobs( 34 | req, metadata=self.get_metadata(admin_token) 35 | ) 36 | 37 | def summary(self, user_id: str, cids: List[str], admin_token: str = None): 38 | req = admin_pb2.StorageJobsSummaryRequest(cids=cids, user_id=user_id) 39 | return self.client.StorageJobsSummary( 40 | req, metadata=self.get_metadata(admin_token) 41 | ) 42 | -------------------------------------------------------------------------------- /pygate_grpc/admin/users.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from powergate.admin.v1 import admin_pb2, admin_pb2_grpc 4 | 5 | from pygate_grpc.decorators import unmarshal_with 6 | from pygate_grpc.errors import ErrorHandlerMeta 7 | from pygate_grpc.types import User 8 | 9 | 10 | class UsersClient(object, metaclass=ErrorHandlerMeta): 11 | def __init__(self, channel, get_metadata): 12 | self.client = admin_pb2_grpc.AdminServiceStub(channel) 13 | self.get_metadata = get_metadata 14 | 15 | @unmarshal_with(User) 16 | def create(self, admin_token: str = None) -> User: 17 | req = admin_pb2.CreateUserRequest() 18 | return self.client.CreateUser( 19 | req, metadata=self.get_metadata(None, admin_token) 20 | ).user 21 | 22 | @unmarshal_with(User, many=True) 23 | def list(self, admin_token: str = None) -> List[User]: 24 | req = admin_pb2.UsersRequest() 25 | return self.client.Users( 26 | req, metadata=self.get_metadata(None, admin_token) 27 | ).users 28 | -------------------------------------------------------------------------------- /pygate_grpc/admin/wallet.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from powergate.admin.v1 import admin_pb2, admin_pb2_grpc 4 | 5 | from pygate_grpc.errors import ErrorHandlerMeta 6 | 7 | 8 | class WalletClient(object, metaclass=ErrorHandlerMeta): 9 | def __init__(self, channel, get_metadata): 10 | self.client = admin_pb2_grpc.AdminServiceStub(channel) 11 | self.get_metadata = get_metadata 12 | 13 | # Type should be either `bls` or `secp256k1`. 14 | def new(self, address_type: str = "bls", admin_token: str = None,) -> str: 15 | self._check_address_type(address_type) 16 | req = admin_pb2.NewAddressRequest(address_type=address_type) 17 | return self.client.NewAddress( 18 | req, metadata=self.get_metadata(admin_token) 19 | ).address 20 | 21 | def addresses(self, admin_token: str = None) -> List[str]: 22 | return list( 23 | self.client.Addresses( 24 | admin_pb2.AddressesRequest(), metadata=self.get_metadata(admin_token), 25 | ).addresses 26 | ) 27 | 28 | def send(self, sender: str, receiver: str, amount: int, admin_token: str = None): 29 | if type(amount) == float: 30 | raise TypeError("amount should be an integer") 31 | # To avoid name collision since `from` is reserved in Python. 32 | kwargs = {"from": sender, "to": receiver, "amount": str(amount)} 33 | req = admin_pb2.SendFilRequest(**kwargs) 34 | return self.client.SendFil(req, metadata=self.get_metadata(admin_token)) 35 | 36 | def _check_address_type(self, wallet_type): 37 | acceptable_types = ["bls", "secp256k1"] 38 | if wallet_type not in acceptable_types: 39 | raise Exception("Type should be one of ", acceptable_types) 40 | -------------------------------------------------------------------------------- /pygate_grpc/client.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | 3 | import grpc 4 | from powergate.user.v1 import user_pb2, user_pb2_grpc 5 | 6 | from pygate_grpc.admin import AdminClient 7 | from pygate_grpc.config import ConfigClient 8 | from pygate_grpc.data import DataClient 9 | from pygate_grpc.deals import DealsClient 10 | from pygate_grpc.decorators import unmarshal_with 11 | from pygate_grpc.errors import ErrorHandlerMeta 12 | from pygate_grpc.storage_info import StorageInfoClient 13 | from pygate_grpc.storage_jobs import StorageJobsClient 14 | from pygate_grpc.types import BuildInfo 15 | from pygate_grpc.wallet import WalletClient 16 | 17 | TOKEN_KEY = "x-ffs-token" 18 | ADMIN_TOKEN_KEY = "X-pow-admin-token" 19 | 20 | 21 | class PowerGateClient(object, metaclass=ErrorHandlerMeta): 22 | def __init__(self, host_name, is_secure=False): 23 | channel = ( 24 | grpc.secure_channel(host_name, grpc.ssl_channel_credentials()) 25 | if is_secure 26 | else grpc.insecure_channel(host_name) 27 | ) 28 | self.client = user_pb2_grpc.UserServiceStub(channel) 29 | 30 | self.token = None 31 | self.admin_token = None 32 | 33 | self.admin = AdminClient(channel, self.get_metadata) 34 | self.data = DataClient(channel, self.get_metadata) 35 | self.deals = DealsClient(channel, self.get_metadata) 36 | self.config = ConfigClient(channel, self.get_metadata) 37 | self.storage_jobs = StorageJobsClient(channel, self.get_metadata) 38 | self.storage_info = StorageInfoClient(channel, self.get_metadata) 39 | self.wallet = WalletClient(channel, self.get_metadata) 40 | 41 | def set_token(self, token: str): 42 | self.token = token 43 | 44 | def set_admin_token(self, token: str): 45 | self.admin_token = token 46 | 47 | @unmarshal_with(BuildInfo) 48 | def build_info(self) -> BuildInfo: 49 | req = user_pb2.BuildInfoRequest() 50 | return self.client.BuildInfo(req) 51 | 52 | def user_id(self, token: str = None) -> str: 53 | req = user_pb2.UserIdentifierRequest() 54 | return self.client.UserIdentifier(req, metadata=self.get_metadata(token)).id 55 | 56 | # The metadata is set in here https://github.com/textileio/js-powergate-client/blob 57 | # /9d1ad04a7e1f2a6e18cc5627751f9cbddaf6fe05/src/util/grpc-helpers.ts#L7 Note that you can't have capital letter in 58 | # meta data field, see here: https://stackoverflow.com/questions/45071567/how-to-send-custom-header-metadata-with 59 | # -python-grpc 60 | def get_metadata( 61 | self, token: str = None, admin_token: str = None 62 | ) -> Tuple[Tuple[str, str]]: 63 | token_data: Tuple[Tuple[str, str]] = () 64 | admin_token_data: Tuple[Tuple[str, str]] = () 65 | 66 | final_token = token or self.token 67 | final_admin_token = admin_token or self.admin_token 68 | 69 | if final_token is not None: 70 | token_data = ((TOKEN_KEY, final_token),) 71 | if final_admin_token is not None: 72 | admin_token_data = ((ADMIN_TOKEN_KEY, final_admin_token),) 73 | 74 | return token_data + admin_token_data 75 | -------------------------------------------------------------------------------- /pygate_grpc/config.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from google.protobuf.json_format import Parse 4 | from powergate.user.v1 import user_pb2, user_pb2_grpc 5 | 6 | from pygate_grpc.decorators import unmarshal_with 7 | from pygate_grpc.errors import ErrorHandlerMeta 8 | from pygate_grpc.types import Job 9 | 10 | 11 | class ConfigClient(object, metaclass=ErrorHandlerMeta): 12 | def __init__(self, channel, get_metadata): 13 | self.client = user_pb2_grpc.UserServiceStub(channel) 14 | self.get_metadata = get_metadata 15 | 16 | @unmarshal_with() 17 | def default(self, token: str = None) -> dict: 18 | req = user_pb2.DefaultStorageConfigRequest() 19 | return self.client.DefaultStorageConfig( 20 | req, metadata=self.get_metadata(token) 21 | ).default_storage_config 22 | 23 | # Currently you need to pass in the user_pb2.DefaultConfig. However, this is not a good design. 24 | def set_default(self, config: str, token: str = None): 25 | if isinstance(config, dict): 26 | config = json.dumps(config) 27 | 28 | config = Parse(config, user_pb2.StorageConfig()) 29 | req = user_pb2.SetDefaultStorageConfigRequest(config=config) 30 | return self.client.SetDefaultStorageConfig( 31 | req, metadata=self.get_metadata(token) 32 | ) 33 | 34 | @unmarshal_with(Job) 35 | def apply( 36 | self, 37 | cid, 38 | token: str = None, 39 | override: bool = False, 40 | config: str = None, 41 | import_deal_ids=[], 42 | no_exec=False, 43 | ) -> Job: 44 | if isinstance(config, dict): 45 | config = json.dumps(config) 46 | 47 | if config: 48 | config = Parse(config, user_pb2.StorageConfig()) 49 | 50 | req = user_pb2.ApplyStorageConfigRequest( 51 | cid=cid, 52 | override_config=override, 53 | has_override_config=override, 54 | config=config, 55 | has_config=config is not None, 56 | import_deal_ids=import_deal_ids, 57 | no_exec=no_exec, 58 | ) 59 | return self.client.ApplyStorageConfig(req, metadata=self.get_metadata(token)) 60 | 61 | def remove(self, cid: str, token: str = None): 62 | req = user_pb2.RemoveRequest(cid=cid) 63 | return self.client.Remove(req, metadata=self.get_metadata(token)) 64 | -------------------------------------------------------------------------------- /pygate_grpc/data.py: -------------------------------------------------------------------------------- 1 | from itertools import islice 2 | from typing import Iterable, List 3 | 4 | from deprecated import deprecated 5 | from powergate.user.v1 import user_pb2, user_pb2_grpc 6 | 7 | from pygate_grpc.decorators import unmarshal_with 8 | from pygate_grpc.errors import ErrorHandlerMeta, future_error_handler 9 | from pygate_grpc.types import CidInfo, CidSummary, StagedFile 10 | 11 | CHUNK_SIZE = 1024 * 1024 # 1MB 12 | 13 | 14 | def _generate_chunks(chunks: Iterable[bytes]) -> Iterable[user_pb2.StageRequest]: 15 | for chunk in chunks: 16 | yield user_pb2.StageRequest(chunk=chunk) 17 | 18 | 19 | def chunks_to_bytes(chunks: Iterable[user_pb2.StageRequest]) -> Iterable[bytes]: 20 | for c in chunks: 21 | yield c.chunk 22 | 23 | 24 | @deprecated(version="1.0.0", reason="You should use byte_chunks_iter function instead") 25 | def bytes_to_chunks(bytes_iter: Iterable[bytes],) -> Iterable[user_pb2.StageRequest]: 26 | for b in bytes_iter: 27 | yield user_pb2.StageRequest(chunk=b) 28 | 29 | 30 | @deprecated( 31 | version="1.0.0", reason="You should use byte_chunks_iter_from_file function instead" 32 | ) 33 | def get_file_bytes(filename: str): 34 | with open(filename, "rb") as f: 35 | while True: 36 | piece = f.read(CHUNK_SIZE) 37 | if len(piece) == 0: 38 | return 39 | yield piece 40 | 41 | 42 | def byte_chunks_iter(bts: bytes, chunk_size: int = CHUNK_SIZE): 43 | it = iter(bts) 44 | while True: 45 | chunk = bytes(islice(it, chunk_size)) 46 | if not chunk: 47 | break 48 | yield user_pb2.StageRequest(chunk=chunk) 49 | 50 | 51 | def byte_chunks_iter_from_file(filepath: str, chunk_size: int = CHUNK_SIZE): 52 | with open(filepath, "rb") as f: 53 | while True: 54 | chunk = f.read(chunk_size) 55 | if len(chunk) == 0: 56 | return 57 | yield user_pb2.StageRequest(chunk=chunk) 58 | 59 | 60 | class DataClient(object, metaclass=ErrorHandlerMeta): 61 | def __init__(self, channel, get_metadata): 62 | self.client = user_pb2_grpc.UserServiceStub(channel) 63 | self.get_metadata = get_metadata 64 | 65 | @deprecated( 66 | version="1.0.0", 67 | reason="You should use `stage_file` or `stage_bytes` function instead", 68 | ) 69 | def stage(self, chunks_iter: Iterable[user_pb2.StageRequest], token: str = None): 70 | return self.client.Stage(chunks_iter, metadata=self.get_metadata(token)) 71 | 72 | @unmarshal_with(StagedFile) 73 | def stage_bytes( 74 | self, bts: bytes, token: str = None, chunk_size: int = CHUNK_SIZE 75 | ) -> StagedFile: 76 | chunks_iter = byte_chunks_iter(bts, chunk_size=chunk_size) 77 | return self.client.Stage(chunks_iter, metadata=self.get_metadata(token)) 78 | 79 | @unmarshal_with(StagedFile) 80 | def stage_file( 81 | self, filepath: str, token: str = None, chunk_size: int = CHUNK_SIZE 82 | ) -> StagedFile: 83 | chunks_iter = byte_chunks_iter_from_file(filepath, chunk_size=chunk_size) 84 | return self.client.Stage(chunks_iter, metadata=self.get_metadata(token)) 85 | 86 | def replace_data(self, cid1: str, cid2: str, token: str = None): 87 | req = user_pb2.ReplaceDataRequest(cid1=cid1, cid2=cid2) 88 | return self.client.ReplaceData(req, metadata=self.get_metadata(token)) 89 | 90 | def get(self, cid: str, token: str = None) -> bytes: 91 | req = user_pb2.GetRequest(cid=cid) 92 | chunks = self.client.Get(req, metadata=self.get_metadata(token)) 93 | return b"".join(map(lambda c: c.chunk, chunks)) 94 | 95 | @future_error_handler 96 | def watch_logs( 97 | self, cid, token: str = None, history: bool = False, timeout: int = None 98 | ): 99 | req = user_pb2.WatchLogsRequest(cid=cid, history=history) 100 | return self.client.WatchLogs( 101 | req, metadata=self.get_metadata(token), timeout=timeout 102 | ) 103 | 104 | @unmarshal_with(CidInfo) 105 | def cid_info(self, cid: str, token: str = None) -> CidInfo: 106 | req = user_pb2.CidInfoRequest(cid=cid) 107 | return self.client.CidInfo(req, metadata=self.get_metadata(token)).cid_info 108 | 109 | @unmarshal_with(CidSummary, many=True) 110 | def cid_summary(self, cids: List[str], token: str = None) -> CidSummary: 111 | req = user_pb2.CidSummaryRequest(cids=cids) 112 | return self.client.CidSummary( 113 | req, metadata=self.get_metadata(token) 114 | ).cid_summary 115 | -------------------------------------------------------------------------------- /pygate_grpc/deals.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from powergate.user.v1 import user_pb2, user_pb2_grpc 4 | 5 | from pygate_grpc.decorators import unmarshal_with 6 | from pygate_grpc.errors import ErrorHandlerMeta 7 | 8 | 9 | class DealsClient(object, metaclass=ErrorHandlerMeta): 10 | def __init__(self, channel, get_metadata): 11 | self.client = user_pb2_grpc.UserServiceStub(channel) 12 | self.get_metadata = get_metadata 13 | 14 | @unmarshal_with(many=True) 15 | def storage_deal_records( 16 | self, 17 | include_final=True, 18 | include_pending=False, 19 | from_addrs: List[str] = None, 20 | data_cids: List[str] = None, 21 | ascending: bool = False, 22 | token: str = None, 23 | ): 24 | deal_config = user_pb2.DealRecordsConfig( 25 | from_addrs=from_addrs, 26 | data_cids=data_cids, 27 | include_pending=include_pending, 28 | include_final=include_final, 29 | ascending=ascending, 30 | ) 31 | req = user_pb2.StorageDealRecordsRequest(config=deal_config) 32 | return self.client.StorageDealRecords( 33 | req, metadata=self.get_metadata(token) 34 | ).records 35 | 36 | @unmarshal_with(many=True) 37 | def retrieval_deal_records( 38 | self, 39 | include_final=True, 40 | include_pending=False, 41 | from_addrs: List[str] = None, 42 | data_cids: List[str] = None, 43 | ascending: bool = False, 44 | token: str = None, 45 | ): 46 | deal_config = user_pb2.DealRecordsConfig( 47 | from_addrs=from_addrs, 48 | data_cids=data_cids, 49 | include_pending=include_pending, 50 | include_final=include_final, 51 | ascending=ascending, 52 | ) 53 | req = user_pb2.RetrievalDealRecordsRequest(config=deal_config) 54 | return self.client.RetrievalDealRecords( 55 | req, metadata=self.get_metadata(token) 56 | ).records 57 | -------------------------------------------------------------------------------- /pygate_grpc/decorators.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | 3 | from google.protobuf.json_format import MessageToDict 4 | 5 | 6 | def unmarshal_with(cls=None, many=False): 7 | """ 8 | Unmarshals the protobuf response result to the given object 9 | """ 10 | 11 | def wrap(original_func): 12 | 13 | wraps(original_func) 14 | 15 | def wrapper(*args, **kwargs): 16 | if cls is None: 17 | if many: 18 | return [ 19 | MessageToDict(obj) for obj in original_func(*args, **kwargs) 20 | ] 21 | return MessageToDict(original_func(*args, **kwargs)) 22 | # cls is not None 23 | if many: 24 | return [ 25 | cls(**MessageToDict(obj)) for obj in original_func(*args, **kwargs) 26 | ] 27 | return cls(**MessageToDict(original_func(*args, **kwargs))) 28 | 29 | return wrapper 30 | 31 | return wrap 32 | -------------------------------------------------------------------------------- /pygate_grpc/errors.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from functools import wraps 3 | 4 | import grpc 5 | 6 | from pygate_grpc.exceptions import GRPCNotAvailableException, GRPCTimeoutException 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | def error_handler(func): 12 | """A decorator to handle errors""" 13 | 14 | wraps(func) 15 | 16 | def wrapper(*args, **kwargs): 17 | try: 18 | return func(*args, **kwargs) 19 | except grpc._channel._InactiveRpcError as e: 20 | err_to_raise = e 21 | if err_to_raise.code() == grpc.StatusCode.UNAVAILABLE: 22 | err_to_raise = GRPCNotAvailableException(e) 23 | # elif err_to_raise.code() == grpc.StatusCode.UNKNOWN: 24 | # err_to_raise = PyGateGenericException(e) 25 | except grpc._channel._MultiThreadedRendezvous as e: 26 | err_to_raise = e 27 | if err_to_raise.code() == grpc.StatusCode.DEADLINE_EXCEEDED: 28 | err_to_raise = GRPCTimeoutException( 29 | e, e._state, e._call, e._response_deserializer, e._deadline 30 | ) 31 | raise err_to_raise 32 | 33 | return wrapper 34 | 35 | 36 | def future_error_handler(func): 37 | 38 | wraps(func) 39 | 40 | def wrapper(*args, **kwargs): 41 | future = func(*args, **kwargs) 42 | if hasattr(future, "result") and callable(future.result): 43 | future.result = error_handler(future.result) 44 | if hasattr(future, "__next__") and callable(future.__next__): 45 | future.__next__ = error_handler(future.__next__) 46 | if hasattr(future, "_next") and callable(future._next): 47 | future._next = error_handler(future._next) 48 | return future 49 | 50 | return wrapper 51 | 52 | 53 | class ErrorHandlerMeta(type): 54 | """ 55 | A metaclass to embed a global error handler for class methods. 56 | 57 | Mainly used to abstract tout GRPC exceptions. 58 | """ 59 | 60 | def __new__(cls, classname, bases, classdict): 61 | 62 | for attr, item in classdict.items(): 63 | if callable(item): 64 | classdict[attr] = error_handler(item) # replace method by wrapper 65 | 66 | return type.__new__(cls, classname, bases, classdict) 67 | -------------------------------------------------------------------------------- /pygate_grpc/exceptions.py: -------------------------------------------------------------------------------- 1 | from grpc._channel import _MultiThreadedRendezvous 2 | 3 | 4 | class GRPCNotAvailableException(Exception): 5 | def __init__(self, err, *args, **kwargs): 6 | super().__init__(*args, **kwargs) 7 | self.original_err = err 8 | 9 | 10 | class GRPCTimeoutException(_MultiThreadedRendezvous): 11 | def __init__(self, err, *args, **kwargs): 12 | super().__init__(*args, **kwargs) 13 | self.original_err = err 14 | 15 | 16 | class PyGateGenericException(Exception): 17 | def __init__(self, err, *args, **kwargs): 18 | super().__init__(*args, **kwargs) 19 | self.original_err = err 20 | 21 | def __repr__(self): 22 | return self.original_err.__repr__() 23 | 24 | def __str__(self): 25 | return self.original_err.__str__() 26 | -------------------------------------------------------------------------------- /pygate_grpc/storage_info.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from powergate.user.v1 import user_pb2, user_pb2_grpc 4 | 5 | from pygate_grpc.decorators import unmarshal_with 6 | from pygate_grpc.errors import ErrorHandlerMeta 7 | from pygate_grpc.types import StorageInfo 8 | 9 | 10 | class StorageInfoClient(object, metaclass=ErrorHandlerMeta): 11 | def __init__(self, channel, get_metadata): 12 | self.client = user_pb2_grpc.UserServiceStub(channel) 13 | self.get_metadata = get_metadata 14 | 15 | @unmarshal_with(StorageInfo) 16 | def get(self, cid: str, token: str = None) -> StorageInfo: 17 | req = user_pb2.StorageInfoRequest(cid=cid) 18 | return self.client.StorageInfo( 19 | req, metadata=self.get_metadata(token) 20 | ).storage_info 21 | 22 | @unmarshal_with(StorageInfo, many=True) 23 | def list(self, cids: List[str], token: str = None) -> List[StorageInfo]: 24 | req = user_pb2.ListStorageInfoRequest(cids=cids) 25 | return self.client.ListStorageInfo( 26 | req, metadata=self.get_metadata(token) 27 | ).storage_info 28 | -------------------------------------------------------------------------------- /pygate_grpc/storage_jobs.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from powergate.user.v1 import user_pb2, user_pb2_grpc 4 | 5 | from pygate_grpc.errors import ErrorHandlerMeta, future_error_handler 6 | 7 | 8 | class StorageJobsClient(object, metaclass=ErrorHandlerMeta): 9 | def __init__(self, channel, get_metadata): 10 | self.client = user_pb2_grpc.UserServiceStub(channel) 11 | self.get_metadata = get_metadata 12 | 13 | def storage_job(self, job_id: str, token: str = None): 14 | req = user_pb2.StorageJobRequest(job_id=job_id) 15 | return self.client.StorageJob(req, metadata=self.get_metadata(token)) 16 | 17 | def storage_config_for_job(self, job_id: str, token: str = None): 18 | req = user_pb2.StorageConfigForJobRequest(job_id=job_id) 19 | return self.client.StorageConfigForJob(req, metadata=self.get_metadata(token)) 20 | 21 | def list( 22 | self, 23 | cid_filter: str, 24 | limit: int = None, 25 | ascending: bool = False, 26 | next_page_token=None, 27 | token: str = None, 28 | ): 29 | req = user_pb2.ListStorageJobsRequest( 30 | cid_filter=cid_filter, 31 | limit=limit, 32 | ascending=ascending, 33 | next_page_token=next_page_token, 34 | ) 35 | return self.client.ListStorageJobs(req, metadata=self.get_metadata(token)) 36 | 37 | def executing(self, cids: List[str], token: str = None): 38 | req = user_pb2.ExecutingStorageJobsRequest(cids=cids) 39 | return self.client.ExecutingStorageJobs(req, metadata=self.get_metadata(token)) 40 | 41 | def latest_final(self, cids: List[str], token: str = None): 42 | req = user_pb2.LatestFinalStorageJobsRequest(cids=cids) 43 | return self.client.LatestFinalStorageJobs( 44 | req, metadata=self.get_metadata(token) 45 | ) 46 | 47 | def latest_successful(self, cids: List[str], token: str = None): 48 | req = user_pb2.LatestSuccessfulStorageJobsRequest(cids=cids) 49 | return self.client.LatestSuccessfulStorageJobs( 50 | req, metadata=self.get_metadata(token) 51 | ) 52 | 53 | def summary(self, cids: List[str], token: str = None): 54 | req = user_pb2.StorageJobsSummaryRequest(cids=cids) 55 | return self.client.StorageJobsSummary(req, metadata=self.get_metadata(token)) 56 | 57 | @future_error_handler 58 | def watch(self, job_ids: List[str], token: str = None): 59 | req = user_pb2.WatchStorageJobsRequest(job_ids=job_ids) 60 | return self.client.WatchStorageJobs(req, metadata=self.get_metadata(token)) 61 | 62 | def cancel(self, job_id: str, token: str = None): 63 | req = user_pb2.CancelStorageJobRequest(job_id=job_id) 64 | return self.client.CancelStorageJob(req, metadata=self.get_metadata(token)) 65 | -------------------------------------------------------------------------------- /pygate_grpc/types.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | 3 | User = namedtuple("User", ["id", "token"]) 4 | 5 | StagedFile = namedtuple("StagedFile", ["cid"]) 6 | 7 | Address = namedtuple("Address", ["name", "address", "type", "balance"]) 8 | 9 | Job = namedtuple("Job", ["jobId"]) 10 | 11 | CidInfo = namedtuple( 12 | "CidInfo", 13 | [ 14 | "cid", 15 | "latestPushedStorageConfig", 16 | "executingStorageJob", 17 | "queuedStorageJobs", 18 | "currentStorageInfo", 19 | ], 20 | defaults=(None,) * 5, 21 | ) 22 | 23 | CidSummary = namedtuple( 24 | "CidSummary", ["cid", "stored", "queuedJobs", "executingJob"], defaults=(None,) * 4, 25 | ) 26 | 27 | StorageInfo = namedtuple("StorageInfo", ["job_id", "cid", "created", "hot", "cold"]) 28 | 29 | BuildInfo = namedtuple( 30 | "BuildInfo", 31 | ["gitCommit", "gitBranch", "gitState", "gitSummary", "buildDate", "version"], 32 | ) 33 | -------------------------------------------------------------------------------- /pygate_grpc/wallet.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from powergate.user.v1 import user_pb2, user_pb2_grpc 4 | 5 | from pygate_grpc.decorators import unmarshal_with 6 | from pygate_grpc.errors import ErrorHandlerMeta 7 | from pygate_grpc.types import Address 8 | 9 | 10 | class WalletClient(object, metaclass=ErrorHandlerMeta): 11 | def __init__(self, channel, get_metadata): 12 | self.client = user_pb2_grpc.UserServiceStub(channel) 13 | self.get_metadata = get_metadata 14 | 15 | def balance(self, address, token: str = None) -> int: 16 | req = user_pb2.BalanceRequest(address=address) 17 | return int(self.client.Balance(req, metadata=self.get_metadata(token)).balance) 18 | 19 | # Type should be either `bls` or `secp256k1`. 20 | def new_address( 21 | self, 22 | name: str, 23 | address_type: str = "bls", 24 | make_default: bool = False, 25 | token: str = None, 26 | ): 27 | self._check_address_type(address_type) 28 | req = user_pb2.NewAddressRequest( 29 | name=name, address_type=address_type, make_default=make_default 30 | ) 31 | return self.client.NewAddress(req, metadata=self.get_metadata(token)).address 32 | 33 | @unmarshal_with(Address, many=True) 34 | def addresses(self, token: str = None) -> List[Address]: 35 | return self.client.Addresses( 36 | user_pb2.AddressesRequest(), metadata=self.get_metadata(token) 37 | ).addresses 38 | 39 | def send_fil(self, sender: str, receiver: str, amount: str, token: str = None): 40 | # To avoid name collision since `from` is reserved in Python. 41 | kwargs = {"from": sender, "to": receiver, "amount": amount} 42 | req = user_pb2.SendFilRequest(**kwargs) 43 | return self.client.SendFil(req, metadata=self.get_metadata(token)) 44 | 45 | def sign_message(self, addr: str, msg: bytes, token: str = None): 46 | req = user_pb2.SignMessageRequest(addr=addr, msg=msg) 47 | return self.client.SignMessage(req, metadata=self.get_metadata(token)) 48 | 49 | def verify_message( 50 | self, addr: str, msg: bytes, signature: bytes, token: str = None 51 | ): 52 | req = user_pb2.VerifyMessageRequest(addr=addr, msg=msg, signature=signature) 53 | return self.client.VerifyMessage(req, metadata=self.get_metadata(token)) 54 | 55 | def _check_address_type(self, wallet_type): 56 | acceptable_types = ["bls", "secp256k1"] 57 | if wallet_type not in acceptable_types: 58 | raise Exception("Type should be one of ", acceptable_types) 59 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | 4 | [tool:pytest] 5 | addopts = -W ignore 6 | testpaths = 7 | tests 8 | 9 | [isort] 10 | multi_line_output = 3 11 | include_trailing_comma = True 12 | force_grid_wrap = 0 13 | use_parentheses = True 14 | ensure_newline_before_comments = True 15 | line_length = 120 16 | 17 | [flake8] 18 | max-line-length = 120 19 | extend-ignore = E203, W503 20 | exclude = .git -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # read the contents of your README file 2 | from os import path 3 | 4 | from setuptools import find_packages, setup 5 | 6 | this_directory = path.abspath(path.dirname(__file__)) 7 | with open(path.join(this_directory, "README.md"), encoding="utf-8") as f: 8 | long_description = f.read() 9 | 10 | setup( 11 | install_requires=[ 12 | "deprecated==1.2.11", 13 | "grpc-powergate-client==2.1.0", 14 | "grpcio==1.35.0", 15 | "mypy-extensions==0.4.3", 16 | "protobuf==3.15.1", 17 | "six==1.15.0", 18 | "wrapt==1.12.1", 19 | ], 20 | name="pygate_grpc", 21 | version="2.0.0", 22 | description="A Python interface to Textile's Powergate Filecoin API", 23 | url="https://github.com/pygate/pygate-gRPC", 24 | author="Pygate Team", 25 | author_email="info@pygate.com", 26 | license="MIT", 27 | packages=["pygate_grpc", "pygate_grpc.admin"], 28 | long_description=long_description, 29 | long_description_content_type="text/markdown", 30 | zip_safe=False, 31 | classifiers=[ 32 | # Chose either "3 - Alpha", "4 - Beta" or "5 - Production/Stable" as the current state of your package 33 | "Development Status :: 3 - Alpha", 34 | "Intended Audience :: Developers", # Define that your audience are developers 35 | "Intended Audience :: Information Technology", 36 | "Topic :: Software Development :: Build Tools", 37 | "License :: OSI Approved :: MIT License", # Again, pick a license 38 | "Programming Language :: Python :: 3", # Specify which pyhton versions that you want to support 39 | "Programming Language :: Python :: 3.4", 40 | "Programming Language :: Python :: 3.5", 41 | "Programming Language :: Python :: 3.6", 42 | ], 43 | ) 44 | -------------------------------------------------------------------------------- /tests/integration/admin/test_jobs.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.mark.skip(reason="Work in progress") 5 | def test_jobs(): 6 | pass 7 | -------------------------------------------------------------------------------- /tests/integration/admin/test_users.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import pytest 4 | 5 | from pygate_grpc.client import PowerGateClient 6 | from pygate_grpc.types import User 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | @pytest.fixture(scope="module") 12 | def user(pygate_client: PowerGateClient): 13 | return pygate_client.admin.users.create() 14 | 15 | 16 | def test_grpc_user_create(pygate_client: PowerGateClient): 17 | new_user = pygate_client.admin.users.create() 18 | 19 | assert type(new_user) == User 20 | assert new_user.id is not None 21 | assert new_user.token is not None 22 | 23 | 24 | def test_grpc_user_list(pygate_client: PowerGateClient, user: User): 25 | users = pygate_client.admin.users.list() 26 | 27 | assert type(users) == list 28 | assert users is not None 29 | assert user in users 30 | -------------------------------------------------------------------------------- /tests/integration/admin/test_wallets.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import time 3 | 4 | import pytest 5 | 6 | from pygate_grpc.client import PowerGateClient 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | @pytest.fixture(scope="module") 12 | def wallets(pygate_client: PowerGateClient): 13 | wallet1 = pygate_client.admin.wallet.new() 14 | wallet2 = pygate_client.admin.wallet.new() 15 | return wallet1, wallet2 16 | 17 | 18 | def test_grpc_wallet_create(pygate_client: PowerGateClient): 19 | wallet_addr = pygate_client.admin.wallet.new() 20 | 21 | assert type(wallet_addr) == str 22 | assert len(wallet_addr) > 0 23 | 24 | 25 | def test_grpc_wallet_list(pygate_client: PowerGateClient, wallets: tuple): 26 | wallet_addrs = pygate_client.admin.wallet.addresses() 27 | 28 | assert type(wallet_addrs) == list 29 | assert len(wallet_addrs) > 1 30 | assert wallets[0] in wallet_addrs 31 | assert wallets[1] in wallet_addrs 32 | 33 | 34 | def test_grpc_wallet_send(pygate_client: PowerGateClient, wallets: tuple): 35 | sender = wallets[0] 36 | receiver = wallets[1] 37 | # Wait until wallets are initialized 38 | timeout = 5 39 | start = int(time.time()) 40 | balance_sender, balance_receiver = ( 41 | pygate_client.wallet.balance(wallets[0]), 42 | pygate_client.wallet.balance(wallets[1]), 43 | ) 44 | while ( 45 | balance_sender != 250000000000000000 or balance_receiver != 250000000000000000 46 | ): 47 | time.sleep(1) 48 | balance_sender, balance_receiver = ( 49 | pygate_client.wallet.balance(wallets[0]), 50 | pygate_client.wallet.balance(wallets[1]), 51 | ) 52 | now = int(time.time()) 53 | if now - start > timeout: 54 | raise Exception("Waiting for wallets ot initialize timed out") 55 | 56 | amount = 100 57 | pygate_client.admin.wallet.send(sender, receiver, amount) 58 | 59 | time.sleep(3) 60 | 61 | assert balance_receiver + amount == pygate_client.wallet.balance(receiver) 62 | -------------------------------------------------------------------------------- /tests/integration/assets/cidconfig_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "hot": { 3 | "enabled": true, 4 | "ipfs": { 5 | "addTimeout": "30" 6 | } 7 | }, 8 | "cold": { 9 | "enabled": true, 10 | "filecoin": { 11 | "replicationFactor": "1", 12 | "dealMinDuration": "518400", 13 | "excludedMiners": ["t01101"], 14 | "countryCodes": ["ca", "nl"], 15 | "renew": { 16 | "enabled": true, 17 | "threshold": "3" 18 | }, 19 | "address": "f3ugglyobkqbsi7m7vgcnvpk7mswinrv5s6wm2abrmeq6m5zw2i2xwknjxg566konp4esxfnxjaefaa3uy3waa", 20 | "maxPrice": "50" 21 | } 22 | }, 23 | "repairable": true 24 | 25 | } -------------------------------------------------------------------------------- /tests/integration/conftest.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import shutil 4 | import subprocess 5 | from logging.config import fileConfig 6 | from time import sleep, time 7 | 8 | import docker 9 | import pytest 10 | import requests 11 | from git import Repo 12 | 13 | from pygate_grpc.client import PowerGateClient 14 | 15 | fileConfig("logging.ini") 16 | 17 | logger = logging.getLogger(__name__) 18 | 19 | REPO_LOCAL_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "repo") 20 | BASE_DIR = os.path.dirname(os.path.abspath(__file__)) 21 | 22 | POWERGATE_VERSION_TEST_TARGET = "v2.1.0" 23 | 24 | pytest_plugins = [] 25 | 26 | 27 | def is_docker_running(): 28 | """Checks if docker is running""" 29 | logger.debug("Checking if docker is running...") 30 | client = docker.from_env() 31 | is_running = True 32 | try: 33 | client.info() 34 | except Exception: 35 | is_running = False 36 | finally: 37 | client.close() 38 | return is_running 39 | 40 | 41 | def is_docker_compose_installed(): 42 | """Checks if docker composed is installed in the system""" 43 | logger.debug("Checking if docker-compose is installed...") 44 | res = subprocess.run(["docker-compose", "--version"]) 45 | return res.returncode == 0 46 | 47 | 48 | def is_ipfs_running(): 49 | """Checks if ipfs api is operational""" 50 | logger.debug("Checking if IPFS API is operational...") 51 | res = requests.post("http://localhost:5001/api/v0/swarm/peers") 52 | return res.status_code == 200 53 | 54 | 55 | def clone_powergate_repo(version="master"): 56 | """Clones official Powergate repo """ 57 | repo_url = "https://github.com/textileio/powergate" 58 | logger.debug(f"Cloning powergate repo from {repo_url}") 59 | Repo.clone_from(repo_url, REPO_LOCAL_PATH, branch=version) 60 | 61 | 62 | @pytest.fixture(scope="session") 63 | def docker_compose_file(pytestconfig): 64 | return [ 65 | os.path.join(REPO_LOCAL_PATH, "docker", "docker-compose-localnet.yaml"), 66 | os.path.join(REPO_LOCAL_PATH, "docker", "ipfs-image.yaml"), 67 | os.path.join(REPO_LOCAL_PATH, "docker", "powergate-build-context.yaml"), 68 | os.path.join(BASE_DIR, "docker", "resource_limits.yaml"), 69 | ] 70 | 71 | 72 | @pytest.fixture(scope="session") 73 | def docker_compose_project_name(): 74 | return "localnet" 75 | 76 | 77 | def pytest_configure(config): 78 | """Runs before all tests and makes sure that all the required files 79 | dependencies are installed in the system""" 80 | if not is_docker_running(): 81 | logger.error("Coulnd't initiate integration tests. Is Docker running?") 82 | pytest.exit(3) 83 | 84 | if not is_docker_compose_installed(): 85 | logger.error( 86 | "Coulnd't initiate integration tests. Is docker-compose installed?" 87 | ) 88 | pytest.exit(3) 89 | 90 | clone_powergate_repo(POWERGATE_VERSION_TEST_TARGET) 91 | 92 | 93 | def pytest_unconfigure(config): 94 | """Runs before test process exits. Cleans up any artifacts from configure""" 95 | try: 96 | shutil.rmtree(REPO_LOCAL_PATH) 97 | except OSError: 98 | logger.warning( 99 | "Couldn't delete powergate repository. Maybe it wasn't cloned in the first place" 100 | ) 101 | 102 | 103 | @pytest.fixture(scope="session", autouse=True) 104 | def localnet(docker_services): 105 | """Starts a cli container to interact with localnet""" 106 | client = docker.from_env() 107 | container = client.containers.run( 108 | "pygate/powergate-cli:v2.1.0", 109 | network_mode="host", 110 | auto_remove=True, 111 | detach=True, 112 | tty=True, 113 | ) 114 | start_time, timeout = time(), 600 115 | while True: 116 | if time() - start_time > timeout: 117 | logger.error("Setting up localnet timed out....") 118 | pytest.exit(3) 119 | 120 | sleep(5) 121 | 122 | try: 123 | result = container.exec_run("pow --version") 124 | if result.exit_code > 0: 125 | continue 126 | except docker.errors.ContainerError: 127 | continue 128 | 129 | break 130 | 131 | # Give it some time to initialize... 132 | sleep(10) 133 | 134 | yield {"cli": container} 135 | 136 | logger.debug("Tearing down localnet...") 137 | container.stop() 138 | 139 | 140 | @pytest.fixture(scope="session") 141 | def pygate_client(): 142 | return PowerGateClient("127.0.0.1:5002", False) 143 | -------------------------------------------------------------------------------- /tests/integration/docker/resource_limits.yaml: -------------------------------------------------------------------------------- 1 | 2 | version: '3.7' 3 | 4 | services: 5 | 6 | ipfs: 7 | deploy: 8 | resources: 9 | limits: 10 | cpus: 0.50 11 | memory: 512M -------------------------------------------------------------------------------- /tests/integration/test_config.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | import os 4 | from pathlib import Path 5 | 6 | import pytest 7 | 8 | from pygate_grpc.client import PowerGateClient 9 | from pygate_grpc.types import Job, User 10 | 11 | logger = logging.getLogger(__name__) 12 | 13 | 14 | @pytest.fixture(scope="module") 15 | def user(pygate_client: PowerGateClient): 16 | return pygate_client.admin.users.create() 17 | 18 | 19 | def test_get_default_config(pygate_client: PowerGateClient, user: User): 20 | default_config = pygate_client.config.default(user.token) 21 | 22 | assert isinstance(default_config, dict) 23 | 24 | 25 | def test_replace_default_config(pygate_client: PowerGateClient, user: User): 26 | path = Path(os.path.abspath(__file__)) 27 | with open(path.parent / "assets" / "cidconfig_example.json", "r") as f: 28 | new_config = json.load(f) 29 | 30 | pygate_client.config.set_default(new_config, user.token) 31 | 32 | default_config = pygate_client.config.default(user.token) 33 | 34 | assert isinstance(default_config, dict) 35 | assert default_config == new_config 36 | 37 | 38 | def test_apply_config(pygate_client: PowerGateClient, user: User): 39 | config = pygate_client.config.default(user.token) 40 | 41 | config["cold"]["filecoin"]["address"] = pygate_client.wallet.addresses( 42 | token=user.token 43 | )[-1].address 44 | 45 | file_bytes = b"This is a test file for stagiiing" 46 | staged_file = pygate_client.data.stage_bytes(file_bytes, token=user.token) 47 | 48 | job = pygate_client.config.apply( 49 | staged_file.cid, 50 | token=user.token, 51 | config=config, 52 | override=True, 53 | no_exec=False, 54 | import_deal_ids=[], 55 | ) 56 | 57 | assert isinstance(job, Job) 58 | 59 | 60 | def test_remove(pygate_client: PowerGateClient, user: User): 61 | config = pygate_client.config.default(user.token) 62 | config["cold"]["enabled"] = False 63 | config["hot"]["enabled"] = False 64 | 65 | file_bytes = b"test files" 66 | staged_file = pygate_client.data.stage_bytes(file_bytes, token=user.token) 67 | 68 | pygate_client.config.apply(staged_file.cid, token=user.token, config=config) 69 | 70 | pygate_client.config.remove(staged_file.cid, token=user.token) 71 | -------------------------------------------------------------------------------- /tests/integration/test_data.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import subprocess 3 | 4 | import pytest 5 | 6 | from pygate_grpc.client import PowerGateClient 7 | from pygate_grpc.exceptions import GRPCTimeoutException 8 | from pygate_grpc.types import CidInfo, CidSummary, StagedFile, User 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | @pytest.fixture(scope="module") 14 | def user(pygate_client: PowerGateClient): 15 | return pygate_client.admin.users.create() 16 | 17 | 18 | @pytest.fixture(scope="module") 19 | def staged_file(pygate_client: PowerGateClient, user: User): 20 | original_file_contents = b"Some random bytes" 21 | staged_file = pygate_client.data.stage_bytes( 22 | original_file_contents, token=user.token 23 | ) 24 | pygate_client.config.apply(staged_file.cid, token=user.token, override=True) 25 | return staged_file 26 | 27 | 28 | def test_get_data(pygate_client: PowerGateClient, user: User): 29 | original_file_contents = b"Some random bytes" 30 | staged_file = pygate_client.data.stage_bytes( 31 | original_file_contents, token=user.token 32 | ) 33 | pygate_client.config.apply(staged_file.cid, token=user.token) 34 | retrieved_file_contents = pygate_client.data.get(staged_file.cid, token=user.token) 35 | 36 | assert original_file_contents == retrieved_file_contents 37 | 38 | 39 | def test_cid_info(pygate_client: PowerGateClient, user: User, staged_file: StagedFile): 40 | cid_info = pygate_client.data.cid_info(staged_file.cid, token=user.token) 41 | assert isinstance(cid_info, CidInfo) 42 | 43 | 44 | def test_cid_summary( 45 | pygate_client: PowerGateClient, user: User, staged_file: StagedFile 46 | ): 47 | cid_summary_list = pygate_client.data.cid_summary( 48 | cids=[staged_file.cid], token=user.token 49 | ) 50 | assert len(cid_summary_list) > 0 51 | assert isinstance(cid_summary_list[0], CidSummary) 52 | 53 | 54 | def test_logs(pygate_client: PowerGateClient, user: User, staged_file: StagedFile): 55 | logs_res = pygate_client.data.watch_logs( 56 | staged_file.cid, user.token, history=True, timeout=5 57 | ) 58 | 59 | logs = [] 60 | try: 61 | for f in logs_res: 62 | logs.append(f) 63 | except GRPCTimeoutException: 64 | pass 65 | 66 | assert len(logs) > 0 67 | -------------------------------------------------------------------------------- /tests/integration/test_deals.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import pytest 4 | 5 | from pygate_grpc.client import PowerGateClient 6 | from pygate_grpc.types import User 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | @pytest.fixture(scope="module") 12 | def user(pygate_client: PowerGateClient): 13 | return pygate_client.admin.users.create() 14 | 15 | 16 | @pytest.fixture(scope="module") 17 | def staged_file(pygate_client: PowerGateClient, user: User): 18 | original_file_contents = b"Another file for staging and testing" 19 | staged_file = pygate_client.data.stage_bytes( 20 | original_file_contents, token=user.token 21 | ) 22 | pygate_client.config.apply(staged_file.cid, token=user.token, override=True) 23 | return staged_file 24 | 25 | 26 | def test_storage_deals(pygate_client: PowerGateClient, user: User): 27 | records = pygate_client.deals.storage_deal_records( 28 | include_pending=True, include_final=True, token=user.token 29 | ) 30 | 31 | assert type(records) == list 32 | 33 | 34 | def test_retrieval_deals(pygate_client: PowerGateClient, user: User): 35 | records = pygate_client.deals.retrieval_deal_records( 36 | include_pending=True, include_final=True, token=user.token 37 | ) 38 | 39 | assert type(records) == list 40 | -------------------------------------------------------------------------------- /tests/integration/test_storage_info.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import pytest 4 | 5 | from pygate_grpc.client import PowerGateClient 6 | from pygate_grpc.exceptions import GRPCTimeoutException 7 | from pygate_grpc.types import CidInfo, StagedFile, User 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | @pytest.fixture(scope="module") 13 | def user(pygate_client: PowerGateClient): 14 | return pygate_client.admin.users.create() 15 | 16 | 17 | @pytest.fixture(scope="module") 18 | def staged_file(pygate_client: PowerGateClient, user: User): 19 | original_file_contents = b"Another file for staging and testing" 20 | staged_file = pygate_client.data.stage_bytes( 21 | original_file_contents, token=user.token 22 | ) 23 | pygate_client.config.apply(staged_file.cid, token=user.token, override=True) 24 | return staged_file 25 | 26 | 27 | def test_storage_info( 28 | pygate_client: PowerGateClient, user: User, staged_file: StagedFile 29 | ): 30 | storage_info_list = pygate_client.storage_info.list( 31 | cids=[staged_file.cid], token=user.token 32 | ) 33 | assert len(storage_info_list) == 0 34 | -------------------------------------------------------------------------------- /tests/integration/test_version.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from pygate_grpc.client import PowerGateClient 4 | from pygate_grpc.types import BuildInfo 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | 9 | def test_build_info(pygate_client: PowerGateClient): 10 | build_info = pygate_client.build_info() 11 | 12 | assert build_info is not None 13 | assert type(build_info) == BuildInfo 14 | 15 | 16 | def test_get_user_id(pygate_client: PowerGateClient): 17 | user = pygate_client.admin.users.create() 18 | 19 | user_id = pygate_client.user_id(user.token) 20 | 21 | assert user_id == user.id 22 | -------------------------------------------------------------------------------- /tests/integration/test_wallet.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import time 3 | 4 | import pytest 5 | 6 | from pygate_grpc.client import PowerGateClient 7 | from pygate_grpc.types import Address, User 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | @pytest.fixture(scope="module") 13 | def user(pygate_client: PowerGateClient): 14 | return pygate_client.admin.users.create() 15 | 16 | 17 | def test_wallet_list(pygate_client: PowerGateClient, user: User): 18 | addresses = pygate_client.wallet.addresses(user.token) 19 | 20 | assert addresses is not None 21 | assert len(addresses) >= 1 22 | assert type(addresses[0]) == Address 23 | 24 | 25 | def test_wallet_new(pygate_client: PowerGateClient, user: User): 26 | addr_name = "Test Address" 27 | addr_type = "secp256k1" 28 | address = pygate_client.wallet.new_address( 29 | name=addr_name, address_type=addr_type, token=user.token 30 | ) 31 | 32 | assert address is not None 33 | assert type(address) == str 34 | 35 | address_details = next( 36 | addr 37 | for addr in pygate_client.wallet.addresses(token=user.token) 38 | if addr.address == address 39 | ) 40 | assert address_details.name == addr_name 41 | assert address_details.type == addr_type 42 | 43 | 44 | def test_wallet_balance(pygate_client: PowerGateClient, user: User): 45 | user_addr = pygate_client.wallet.addresses(user.token)[0].address 46 | 47 | timeout = 5 48 | start = int(time.time()) 49 | balance = pygate_client.wallet.balance(user_addr) 50 | while balance != 250000000000000000: 51 | time.sleep(1) 52 | balance = pygate_client.wallet.balance(user_addr) 53 | now = int(time.time()) 54 | if now - start > timeout: 55 | raise Exception("Waiting for wallet to initialize timed out") 56 | 57 | assert type(balance) is int 58 | assert balance == 250000000000000000 59 | --------------------------------------------------------------------------------