commit
39d0e41f29
@ -0,0 +1,12 @@ |
||||
# https://editorconfig.org |
||||
|
||||
root = true |
||||
|
||||
[*] |
||||
end_of_line = lf |
||||
charset = utf-8 |
||||
trim_trailing_whitespace = true |
||||
insert_final_newline = true |
||||
|
||||
[Makefile] |
||||
indent_style=tab |
@ -0,0 +1,10 @@ |
||||
*.pyc |
||||
.venv |
||||
venv |
||||
build |
||||
dist |
||||
*.egg-info |
||||
.eggs |
||||
__pycache__ |
||||
.pytest_cache |
||||
.coverage |
@ -0,0 +1,34 @@ |
||||
# See https://pre-commit.com for more information |
||||
# See https://pre-commit.com/hooks.html for more hooks |
||||
|
||||
repos: |
||||
- repo: https://github.com/pre-commit/pre-commit-hooks |
||||
rev: 8fe62d14e0b4d7d845a7022c5c2c3ae41bdd3f26 # frozen: v4.1.0 |
||||
hooks: |
||||
- id: trailing-whitespace |
||||
- repo: https://github.com/psf/black |
||||
rev: fc0be6eb1e2a96091e6f64009ee5e9081bf8b6c6 # frozen: 22.1.0 |
||||
hooks: |
||||
- id: black |
||||
- repo: https://github.com/PyCQA/isort |
||||
rev: c5e8fa75dda5f764d20f66a215d71c21cfa198e1 # frozen: 5.10.1 |
||||
hooks: |
||||
- id: isort |
||||
- repo: https://github.com/myint/autoflake |
||||
rev: 7a53fdafc82c33f446915b60fcac947c51279260 # frozen: v1.4 |
||||
hooks: |
||||
- id: autoflake |
||||
- repo: https://github.com/asottile/pyupgrade |
||||
rev: e695ecd365119ab4e5463f6e49bea5f4b7ca786b # frozen: v2.31.0 |
||||
hooks: |
||||
- id: pyupgrade |
||||
args: [--py37-plus] |
||||
- repo: https://github.com/asottile/setup-cfg-fmt |
||||
rev: 58b14248db425913ea7502c0b1af9d6653403e07 # frozen: v1.20.0 |
||||
hooks: |
||||
- id: setup-cfg-fmt |
||||
- repo: https://github.com/jackdewinter/pymarkdown |
||||
rev: cf71b3c9cb0c361c4a17eacb80ed52432b57b420 # frozen: 0.9.4 |
||||
hooks: |
||||
- id: pymarkdown |
||||
args: [--disable-rules=MD013, --set=plugins.md024.siblings_only=$!True, scan] |
@ -0,0 +1,39 @@ |
||||
pipeline: |
||||
python-3.7: |
||||
group: test |
||||
image: python:3.7 |
||||
commands: |
||||
- pip install pipenv==2022.1.8 |
||||
- pipenv install --dev --deploy |
||||
- pipenv run almake test-pytest # We only test with pytest on 3.7 |
||||
|
||||
# More than three jobs seems to cause issues with Woodpecker? |
||||
# python-3.8: |
||||
# group: test |
||||
# image: python:3.8-slim |
||||
# commands: |
||||
# - pip install pipenv==2021.11.23 |
||||
# - pipenv install --dev --deploy |
||||
# - pipenv run almake test |
||||
# - pipenv run almake build-dist |
||||
# - sha256sum dist/* |
||||
|
||||
python-3.9: |
||||
group: test |
||||
image: python:3.9 |
||||
commands: |
||||
- pip install pipenv==2022.1.8 pre-commit==2.17.0 |
||||
- pipenv install --dev --deploy |
||||
- pipenv run almake test |
||||
- pipenv run almake build-dist |
||||
- sha256sum dist/* |
||||
|
||||
python-3.10: |
||||
group: test |
||||
image: python:3.10 |
||||
commands: |
||||
- pip install pipenv==2022.1.8 pre-commit==2.17.0 |
||||
- pipenv install --dev --deploy |
||||
- pipenv run almake test |
||||
- pipenv run almake build-dist |
||||
- sha256sum dist/* |
@ -0,0 +1,35 @@ |
||||
# Changelog |
||||
|
||||
All notable changes to this project will be documented in this file. |
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), |
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). |
||||
|
||||
## [Unreleased] |
||||
|
||||
### Added |
||||
|
||||
- Nothing yet. |
||||
|
||||
### Fixed |
||||
|
||||
- Nothing yet. |
||||
|
||||
## [5.2.1 - 2022-02-10] |
||||
|
||||
### Added |
||||
|
||||
- New, 32 character machine ID format. (Old, 64 hex character format still supported.) |
||||
- CHANGELOG.md in Keep a Changelog format. |
||||
|
||||
## [5.2.0 - 2022-01-31] |
||||
|
||||
### Added |
||||
|
||||
- `sporestack rebuild` command. |
||||
|
||||
## [5.1.2 - 2021-10-18] |
||||
|
||||
### Added |
||||
|
||||
- Send `sporestack-python/version` in Use-Agent header. |
@ -0,0 +1,24 @@ |
||||
This is free and unencumbered software released into the public domain. |
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or |
||||
distribute this software, either in source code form or as a compiled |
||||
binary, for any purpose, commercial or non-commercial, and by any |
||||
means. |
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors |
||||
of this software dedicate any and all copyright interest in the |
||||
software to the public domain. We make this dedication for the benefit |
||||
of the public at large and to the detriment of our heirs and |
||||
successors. We intend this dedication to be an overt act of |
||||
relinquishment in perpetuity of all present and future rights to this |
||||
software under copyright law. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR |
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, |
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
||||
OTHER DEALINGS IN THE SOFTWARE. |
||||
|
||||
For more information, please refer to <http://unlicense.org/> |
@ -0,0 +1,21 @@ |
||||
test: |
||||
pre-commit run --all-files
|
||||
python -m pflake8 .
|
||||
python -m mypy --strict .
|
||||
$(MAKE) test-pytest
|
||||
|
||||
test-pytest: |
||||
python -m pytest --cov=sporestack --cov-fail-under=49 --cov-report=term --durations=3 --cache-clear
|
||||
|
||||
build-dist: |
||||
rm dist/* || true
|
||||
# This should result in a reproducible wheel.
|
||||
SOURCE_DATE_EPOCH=1309379017 python -m build --no-isolation
|
||||
python -m twine check --strict dist/*
|
||||
|
||||
servedocs: |
||||
pdoc sporestack
|
||||
|
||||
publish: build-dist |
||||
# The sdist isn't reproducible, but the wheel is.
|
||||
python -m twine upload dist/*
|
@ -0,0 +1,30 @@ |
||||
[[source]] |
||||
url = "https://pypi.org/simple" |
||||
verify_ssl = true |
||||
name = "pypi" |
||||
|
||||
[packages] |
||||
sporestack = {editable = true, path = "."} |
||||
|
||||
[dev-packages] |
||||
flake8 = "~=4.0" |
||||
pyproject-flake8 = "==0.0.1a2" |
||||
flake8-noqa = "~=1.2" |
||||
pep8-naming = "~=0.12.1" |
||||
mypy = "==0.931" |
||||
pytest = "~=6.2" |
||||
pytest-cov = "~=3.0" |
||||
|
||||
types-requests = "~=2.25" |
||||
|
||||
# Building |
||||
wheel = "~=0.37.0" |
||||
build = "~=0.7.0" |
||||
# Publishing |
||||
twine = "~=3.4" |
||||
|
||||
# Docs |
||||
pdoc = "~=9.0" |
||||
|
||||
# Python `make` implementation |
||||
almost-make = "~=0.5.1" |
@ -0,0 +1,681 @@ |
||||
{ |
||||
"_meta": { |
||||
"hash": { |
||||
"sha256": "a28c5484b41e439216180f4b486774887c8a49cf9b36f8997ed098d7462ceb2e" |
||||
}, |
||||
"pipfile-spec": 6, |
||||
"requires": {}, |
||||
"sources": [ |
||||
{ |
||||
"name": "pypi", |
||||
"url": "https://pypi.org/simple", |
||||
"verify_ssl": true |
||||
} |
||||
] |
||||
}, |
||||
"default": { |
||||
"certifi": { |
||||
"hashes": [ |
||||
"sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872", |
||||
"sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569" |
||||
], |
||||
"version": "==2021.10.8" |
||||
}, |
||||
"charset-normalizer": { |
||||
"hashes": [ |
||||
"sha256:2842d8f5e82a1f6aa437380934d5e1cd4fcf2003b06fed6940769c164a480a45", |
||||
"sha256:98398a9d69ee80548c762ba991a4728bfc3836768ed226b3945908d1a688371c" |
||||
], |
||||
"markers": "python_version >= '3'", |
||||
"version": "==2.0.11" |
||||
}, |
||||
"click": { |
||||
"hashes": [ |
||||
"sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3", |
||||
"sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b" |
||||
], |
||||
"markers": "python_version >= '3.6'", |
||||
"version": "==8.0.3" |
||||
}, |
||||
"idna": { |
||||
"hashes": [ |
||||
"sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", |
||||
"sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" |
||||
], |
||||
"markers": "python_version >= '3'", |
||||
"version": "==3.3" |
||||
}, |
||||
"pydantic": { |
||||
"hashes": [ |
||||
"sha256:085ca1de245782e9b46cefcf99deecc67d418737a1fd3f6a4f511344b613a5b3", |
||||
"sha256:086254884d10d3ba16da0588604ffdc5aab3f7f09557b998373e885c690dd398", |
||||
"sha256:0b6037175234850ffd094ca77bf60fb54b08b5b22bc85865331dd3bda7a02fa1", |
||||
"sha256:0fe476769acaa7fcddd17cadd172b156b53546ec3614a4d880e5d29ea5fbce65", |
||||
"sha256:1d5278bd9f0eee04a44c712982343103bba63507480bfd2fc2790fa70cd64cf4", |
||||
"sha256:2cc6a4cb8a118ffec2ca5fcb47afbacb4f16d0ab8b7350ddea5e8ef7bcc53a16", |
||||
"sha256:2ee7e3209db1e468341ef41fe263eb655f67f5c5a76c924044314e139a1103a2", |
||||
"sha256:3011b975c973819883842c5ab925a4e4298dffccf7782c55ec3580ed17dc464c", |
||||
"sha256:3c3b035103bd4e2e4a28da9da7ef2fa47b00ee4a9cf4f1a735214c1bcd05e0f6", |
||||
"sha256:4c68c3bc88dbda2a6805e9a142ce84782d3930f8fdd9655430d8576315ad97ce", |
||||
"sha256:574936363cd4b9eed8acdd6b80d0143162f2eb654d96cb3a8ee91d3e64bf4cf9", |
||||
"sha256:5a79330f8571faf71bf93667d3ee054609816f10a259a109a0738dac983b23c3", |
||||
"sha256:5e48ef4a8b8c066c4a31409d91d7ca372a774d0212da2787c0d32f8045b1e034", |
||||
"sha256:6c5b77947b9e85a54848343928b597b4f74fc364b70926b3c4441ff52620640c", |
||||
"sha256:742645059757a56ecd886faf4ed2441b9c0cd406079c2b4bee51bcc3fbcd510a", |
||||
"sha256:7bdfdadb5994b44bd5579cfa7c9b0e1b0e540c952d56f627eb227851cda9db77", |
||||
"sha256:815ddebb2792efd4bba5488bc8fde09c29e8ca3227d27cf1c6990fc830fd292b", |
||||
"sha256:8b5ac0f1c83d31b324e57a273da59197c83d1bb18171e512908fe5dc7278a1d6", |
||||
"sha256:96f240bce182ca7fe045c76bcebfa0b0534a1bf402ed05914a6f1dadff91877f", |
||||
"sha256:a733965f1a2b4090a5238d40d983dcd78f3ecea221c7af1497b845a9709c1721", |
||||
"sha256:ab624700dc145aa809e6f3ec93fb8e7d0f99d9023b713f6a953637429b437d37", |
||||
"sha256:b2571db88c636d862b35090ccf92bf24004393f85c8870a37f42d9f23d13e032", |
||||
"sha256:bbbc94d0c94dd80b3340fc4f04fd4d701f4b038ebad72c39693c794fd3bc2d9d", |
||||
"sha256:c0727bda6e38144d464daec31dff936a82917f431d9c39c39c60a26567eae3ed", |
||||
"sha256:c556695b699f648c58373b542534308922c46a1cda06ea47bc9ca45ef5b39ae6", |
||||
"sha256:c86229333cabaaa8c51cf971496f10318c4734cf7b641f08af0a6fbf17ca3054", |
||||
"sha256:c8d7da6f1c1049eefb718d43d99ad73100c958a5367d30b9321b092771e96c25", |
||||
"sha256:c8e9dcf1ac499679aceedac7e7ca6d8641f0193c591a2d090282aaf8e9445a46", |
||||
"sha256:cb23bcc093697cdea2708baae4f9ba0e972960a835af22560f6ae4e7e47d33f5", |
||||
"sha256:d1e4c28f30e767fd07f2ddc6f74f41f034d1dd6bc526cd59e63a82fe8bb9ef4c", |
||||
"sha256:d9c9bdb3af48e242838f9f6e6127de9be7063aad17b32215ccc36a09c5cf1070", |
||||
"sha256:dee5ef83a76ac31ab0c78c10bd7d5437bfdb6358c95b91f1ba7ff7b76f9996a1", |
||||
"sha256:e0896200b6a40197405af18828da49f067c2fa1f821491bc8f5bde241ef3f7d7", |
||||
"sha256:f5a64b64ddf4c99fe201ac2724daada8595ada0d102ab96d019c1555c2d6441d", |
||||
"sha256:f947352c3434e8b937e3aa8f96f47bdfe6d92779e44bb3f41e4c213ba6a32145" |
||||
], |
||||
"markers": "python_full_version >= '3.6.1'", |
||||
"version": "==1.9.0" |
||||
}, |
||||
"pysocks": { |
||||
"hashes": [ |
||||
"sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299", |
||||
"sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5", |
||||
"sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0" |
||||
], |
||||
"version": "==1.7.1" |
||||
}, |
||||
"requests": { |
||||
"extras": [ |
||||
"socks" |
||||
], |
||||
"hashes": [ |
||||
"sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61", |
||||
"sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d" |
||||
], |
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", |
||||
"version": "==2.27.1" |
||||
}, |
||||
"segno": { |
||||
"hashes": [ |
||||
"sha256:79d1d7b9c893243411acd031682e0ece007fbd632885c6c650186871be572111", |
||||
"sha256:b8e90823b7ab5249044d22f022291bb06e112104779d6339baf0997fad656c9a" |
||||
], |
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", |
||||
"version": "==1.4.1" |
||||
}, |
||||
"sporestack": { |
||||
"editable": true, |
||||
"path": "." |
||||
}, |
||||
"typer": { |
||||
"hashes": [ |
||||
"sha256:63c3aeab0549750ffe40da79a1b524f60e08a2cbc3126c520ebf2eeaf507f5dd", |
||||
"sha256:d81169725140423d072df464cad1ff25ee154ef381aaf5b8225352ea187ca338" |
||||
], |
||||
"markers": "python_version >= '3.6'", |
||||
"version": "==0.4.0" |
||||
}, |
||||
"typing-extensions": { |
||||
"hashes": [ |
||||
"sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e", |
||||
"sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b" |
||||
], |
||||
"markers": "python_version >= '3.6'", |
||||
"version": "==4.0.1" |
||||
}, |
||||
"urllib3": { |
||||
"hashes": [ |
||||
"sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed", |
||||
"sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c" |
||||
], |
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", |
||||
"version": "==1.26.8" |
||||
} |
||||
}, |
||||
"develop": { |
||||
"almost-make": { |
||||
"hashes": [ |
||||
"sha256:57a3fd147074a041f6b8735e239a4d425bf3944b3c68a52e6225dab25caef4e0", |
||||
"sha256:b227ad53d27f767ea600cc97b5165bef94e6f4ec24ccefe4dc52ed532f6b6f71" |
||||
], |
||||
"index": "pypi", |
||||
"version": "==0.5.1" |
||||
}, |
||||
"astunparse": { |
||||
"hashes": [ |
||||
"sha256:5ad93a8456f0d084c3456d059fd9a92cce667963232cbf763eac3bc5b7940872", |
||||
"sha256:c2652417f2c8b5bb325c885ae329bdf3f86424075c4fd1a128674bc6fba4b8e8" |
||||
], |
||||
"markers": "python_version < '3.9'", |
||||
"version": "==1.6.3" |
||||
}, |
||||
"attrs": { |
||||
"hashes": [ |
||||
"sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4", |
||||
"sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd" |
||||
], |
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", |
||||
"version": "==21.4.0" |
||||
}, |
||||
"bleach": { |
||||
"hashes": [ |
||||
"sha256:0900d8b37eba61a802ee40ac0061f8c2b5dee29c1927dd1d233e075ebf5a71da", |
||||
"sha256:4d2651ab93271d1129ac9cbc679f524565cc8a1b791909c4a51eac4446a15994" |
||||
], |
||||
"markers": "python_version >= '3.6'", |
||||
"version": "==4.1.0" |
||||
}, |
||||
"build": { |
||||
"hashes": [ |
||||
"sha256:1aaadcd69338252ade4f7ec1265e1a19184bf916d84c9b7df095f423948cb89f", |
||||
"sha256:21b7ebbd1b22499c4dac536abc7606696ea4d909fd755e00f09f3c0f2c05e3c8" |
||||
], |
||||
"index": "pypi", |
||||
"version": "==0.7.0" |
||||
}, |
||||
"certifi": { |
||||
"hashes": [ |
||||
"sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872", |
||||
"sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569" |
||||
], |
||||
"version": "==2021.10.8" |
||||
}, |
||||
"charset-normalizer": { |
||||
"hashes": [ |
||||
"sha256:2842d8f5e82a1f6aa437380934d5e1cd4fcf2003b06fed6940769c164a480a45", |
||||
"sha256:98398a9d69ee80548c762ba991a4728bfc3836768ed226b3945908d1a688371c" |
||||
], |
||||
"markers": "python_version >= '3'", |
||||
"version": "==2.0.11" |
||||
}, |
||||
"colorama": { |
||||
"hashes": [ |
||||
"sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b", |
||||
"sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2" |
||||
], |
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", |
||||
"version": "==0.4.4" |
||||
}, |
||||
"coverage": { |
||||
"extras": [ |
||||
"toml" |
||||
], |
||||
"hashes": [ |
||||
"sha256:012157499ec4f135fc36cd2177e3d1a1840af9b236cbe80e9a5ccfc83d912a69", |
||||
"sha256:0a34d313105cdd0d3644c56df2d743fe467270d6ab93b5d4a347eb9fec8924d6", |
||||
"sha256:11e61c5548ecf74ea1f8b059730b049871f0e32b74f88bd0d670c20c819ad749", |
||||
"sha256:152cc2624381df4e4e604e21bd8e95eb8059535f7b768c1fb8b8ae0b26f47ab0", |
||||
"sha256:1b4285fde5286b946835a1a53bba3ad41ef74285ba9e8013e14b5ea93deaeafc", |
||||
"sha256:27a94db5dc098c25048b0aca155f5fac674f2cf1b1736c5272ba28ead2fc267e", |
||||
"sha256:27ac7cb84538e278e07569ceaaa6f807a029dc194b1c819a9820b9bb5dbf63ab", |
||||
"sha256:2a491e159294d756e7fc8462f98175e2d2225e4dbe062cca7d3e0d5a75ba6260", |
||||
"sha256:2bc85664b06ba42d14bb74d6ddf19d8bfc520cb660561d2d9ce5786ae72f71b5", |
||||
"sha256:32168001f33025fd756884d56d01adebb34e6c8c0b3395ca8584cdcee9c7c9d2", |
||||
"sha256:3c4ce3b647bd1792d4394f5690d9df6dc035b00bcdbc5595099c01282a59ae01", |
||||
"sha256:433b99f7b0613bdcdc0b00cc3d39ed6d756797e3b078d2c43f8a38288520aec6", |
||||
"sha256:4578728c36de2801c1deb1c6b760d31883e62e33f33c7ba8f982e609dc95167d", |
||||
"sha256:51372e24b1f7143ee2df6b45cff6a721f3abe93b1e506196f3ffa4155c2497f7", |
||||
"sha256:5d008e0f67ac800b0ca04d7914b8501312c8c6c00ad8c7ba17754609fae1231a", |
||||
"sha256:649df3641eb351cdfd0d5533c92fc9df507b6b2bf48a7ef8c71ab63cbc7b5c3c", |
||||
"sha256:6e78b1e25e5c5695dea012be473e442f7094d066925604be20b30713dbd47f89", |
||||
"sha256:72d9d186508325a456475dd05b1756f9a204c7086b07fffb227ef8cee03b1dc2", |
||||
"sha256:7d82c610a2e10372e128023c5baf9ce3d270f3029fe7274ff5bc2897c68f1318", |
||||
"sha256:7ee317486593193e066fc5e98ac0ce712178c21529a85c07b7cb978171f25d53", |
||||
"sha256:7eed8459a2b81848cafb3280b39d7d49950d5f98e403677941c752e7e7ee47cb", |
||||
"sha256:823f9325283dc9565ba0aa2d240471a93ca8999861779b2b6c7aded45b58ee0f", |
||||
"sha256:85c5fc9029043cf8b07f73fbb0a7ab6d3b717510c3b5642b77058ea55d7cacde", |
||||
"sha256:86c91c511853dfda81c2cf2360502cb72783f4b7cebabef27869f00cbe1db07d", |
||||
"sha256:8e0c3525b1a182c8ffc9bca7e56b521e0c2b8b3e82f033c8e16d6d721f1b54d6", |
||||
"sha256:987a84ff98a309994ca77ed3cc4b92424f824278e48e4bf7d1bb79a63cfe2099", |
||||
"sha256:9ed3244b415725f08ca3bdf02ed681089fd95e9465099a21c8e2d9c5d6ca2606", |
||||
"sha256:a189036c50dcd56100746139a459f0d27540fef95b09aba03e786540b8feaa5f", |
||||
"sha256:a4748349734110fd32d46ff8897b561e6300d8989a494ad5a0a2e4f0ca974fc7", |
||||
"sha256:a5d79c9af3f410a2b5acad91258b4ae179ee9c83897eb9de69151b179b0227f5", |
||||
"sha256:a7596aa2f2b8fa5604129cfc9a27ad9beec0a96f18078cb424d029fdd707468d", |
||||
"sha256:ab4fc4b866b279740e0d917402f0e9a08683e002f43fa408e9655818ed392196", |
||||
"sha256:bde4aeabc0d1b2e52c4036c54440b1ad05beeca8113f47aceb4998bb7471e2c2", |
||||
"sha256:c72bb4679283c6737f452eeb9b2a0e570acaef2197ad255fb20162adc80bea76", |
||||
"sha256:c8582e9280f8d0f38114fe95a92ae8d0790b56b099d728cc4f8a2e14b1c4a18c", |
||||
"sha256:ca29c352389ea27a24c79acd117abdd8a865c6eb01576b6f0990cd9a4e9c9f48", |
||||
"sha256:ce443a3e6df90d692c38762f108fc4c88314bf477689f04de76b3f252e7a351c", |
||||
"sha256:da1a428bdbe71f9a8c270c7baab29e9552ac9d0e0cba5e7e9a4c9ee6465d258d", |
||||
"sha256:e67ccd53da5958ea1ec833a160b96357f90859c220a00150de011b787c27b98d", |
||||
"sha256:e8071e7d9ba9f457fc674afc3de054450be2c9b195c470147fbbc082468d8ff7", |
||||
"sha256:fff16a30fdf57b214778eff86391301c4509e327a65b877862f7c929f10a4253" |
||||
], |
||||
"markers": "python_version >= '3.7'", |
||||
"version": "==6.3" |
||||
}, |
||||
"docutils": { |
||||
"hashes": [ |
||||
"sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c", |
||||
"sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06" |
||||
], |
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", |
||||
"version": "==0.18.1" |
||||
}, |
||||
"flake8": { |
||||
"hashes": [ |
||||
"sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d", |
||||
"sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d" |
||||
], |
||||
"index": "pypi", |
||||
"version": "==4.0.1" |
||||
}, |
||||
"flake8-noqa": { |
||||
"hashes": [ |
||||
"sha256:629b87b542f9b4cbd7ee6de10b2c669e460a200145a7577b98092b7e94373153" |
||||
], |
||||
"index": "pypi", |
||||
"version": "==1.2.1" |
||||
}, |
||||
"flake8-polyfill": { |
||||
"hashes": [ |
||||
"sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9", |
||||
"sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda" |
||||
], |
||||
"version": "==1.0.2" |
||||
}, |
||||
"idna": { |
||||
"hashes": [ |
||||
"sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", |
||||
"sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" |
||||
], |
||||
"markers": "python_version >= '3'", |
||||
"version": "==3.3" |
||||
}, |
||||
"importlib-metadata": { |
||||
"hashes": [ |
||||
"sha256:899e2a40a8c4a1aec681feef45733de8a6c58f3f6a0dbed2eb6574b4387a77b6", |
||||
"sha256:951f0d8a5b7260e9db5e41d429285b5f451e928479f19d80818878527d36e95e" |
||||
], |
||||
"markers": "python_version >= '3.7'", |
||||
"version": "==4.10.1" |
||||
}, |
||||
"iniconfig": { |
||||
"hashes": [ |
||||
"sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", |
||||
"sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" |
||||
], |
||||
"version": "==1.1.1" |
||||
}, |
||||
"jinja2": { |
||||
"hashes": [ |
||||
"sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8", |
||||
"sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7" |
||||
], |
||||
"markers": "python_version >= '3.6'", |
||||
"version": "==3.0.3" |
||||
}, |
||||
"keyring": { |
||||
"hashes": [ |
||||
"sha256:9012508e141a80bd1c0b6778d5c610dd9f8c464d75ac6774248500503f972fb9", |
||||
"sha256:b0d28928ac3ec8e42ef4cc227822647a19f1d544f21f96457965dc01cf555261" |
||||
], |
||||
"markers": "python_version >= '3.7'", |
||||
"version": "==23.5.0" |
||||
}, |
||||
"markupsafe": { |
||||
"hashes": [ |
||||
"sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298", |
||||
"sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64", |
||||
"sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b", |
||||
"sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194", |
||||
"sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567", |
||||
"sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff", |
||||
"sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724", |
||||
"sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74", |
||||
"sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646", |
||||
"sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35", |
||||
"sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6", |
||||
"sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a", |
||||
"sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6", |
||||
"sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad", |
||||
"sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26", |
||||
"sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38", |
||||
"sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac", |
||||
"sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7", |
||||
"sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6", |
||||
"sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047", |
||||
"sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75", |
||||
"sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f", |
||||
"sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b", |
||||
"sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135", |
||||
"sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8", |
||||
"sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a", |
||||
"sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a", |
||||
"sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1", |
||||
"sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9", |
||||
"sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864", |
||||
"sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914", |
||||
"sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee", |
||||
"sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f", |
||||
"sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18", |
||||
"sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8", |
||||
"sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2", |
||||
"sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d", |
||||
"sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b", |
||||
"sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b", |
||||
"sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86", |
||||
"sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6", |
||||
"sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f", |
||||
"sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb", |
||||
"sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833", |
||||
"sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28", |
||||
"sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e", |
||||
"sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415", |
||||
"sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902", |
||||
"sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f", |
||||
"sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d", |
||||
"sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9", |
||||
"sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d", |
||||
"sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145", |
||||
"sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066", |
||||
"sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c", |
||||
"sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1", |
||||
"sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a", |
||||
"sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207", |
||||
"sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f", |
||||
"sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53", |
||||
"sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd", |
||||
"sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134", |
||||
"sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85", |
||||
"sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9", |
||||
"sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5", |
||||
"sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94", |
||||
"sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509", |
||||
"sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51", |
||||
"sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872" |
||||
], |
||||
"markers": "python_version >= '3.6'", |
||||
"version": "==2.0.1" |
||||
}, |
||||
"mccabe": { |
||||
"hashes": [ |
||||
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", |
||||
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" |
||||
], |
||||
"version": "==0.6.1" |
||||
}, |
||||
"mypy": { |
||||
"hashes": [ |
||||
"sha256:0038b21890867793581e4cb0d810829f5fd4441aa75796b53033af3aa30430ce", |
||||
"sha256:1171f2e0859cfff2d366da2c7092b06130f232c636a3f7301e3feb8b41f6377d", |
||||
"sha256:1b06268df7eb53a8feea99cbfff77a6e2b205e70bf31743e786678ef87ee8069", |
||||
"sha256:1b65714dc296a7991000b6ee59a35b3f550e0073411ac9d3202f6516621ba66c", |
||||
"sha256:1bf752559797c897cdd2c65f7b60c2b6969ffe458417b8d947b8340cc9cec08d", |
||||
"sha256:300717a07ad09525401a508ef5d105e6b56646f7942eb92715a1c8d610149714", |
||||
"sha256:3c5b42d0815e15518b1f0990cff7a705805961613e701db60387e6fb663fe78a", |
||||
"sha256:4365c60266b95a3f216a3047f1d8e3f895da6c7402e9e1ddfab96393122cc58d", |
||||
"sha256:50c7346a46dc76a4ed88f3277d4959de8a2bd0a0fa47fa87a4cde36fe247ac05", |
||||
"sha256:5b56154f8c09427bae082b32275a21f500b24d93c88d69a5e82f3978018a0266", |
||||
"sha256:74f7eccbfd436abe9c352ad9fb65872cc0f1f0a868e9d9c44db0893440f0c697", |
||||
"sha256:7b3f6f557ba4afc7f2ce6d3215d5db279bcf120b3cfd0add20a5d4f4abdae5bc", |
||||
"sha256:8c11003aaeaf7cc2d0f1bc101c1cc9454ec4cc9cb825aef3cafff8a5fdf4c799", |
||||
"sha256:8ca7f8c4b1584d63c9a0f827c37ba7a47226c19a23a753d52e5b5eddb201afcd", |
||||
"sha256:c89702cac5b302f0c5d33b172d2b55b5df2bede3344a2fbed99ff96bddb2cf00", |
||||
"sha256:d8f1ff62f7a879c9fe5917b3f9eb93a79b78aad47b533911b853a757223f72e7", |
||||
"sha256:d9d2b84b2007cea426e327d2483238f040c49405a6bf4074f605f0156c91a47a", |
||||
"sha256:e839191b8da5b4e5d805f940537efcaa13ea5dd98418f06dc585d2891d228cf0", |
||||
"sha256:f9fe20d0872b26c4bba1c1be02c5340de1019530302cf2dcc85c7f9fc3252ae0", |
||||
"sha256:ff3bf387c14c805ab1388185dd22d6b210824e164d4bb324b195ff34e322d166" |
||||
], |
||||
"index": "pypi", |
||||
"version": "==0.931" |
||||
}, |
||||
"mypy-extensions": { |
||||
"hashes": [ |
||||
"sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", |
||||
"sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" |
||||
], |
||||
"version": "==0.4.3" |
||||
}, |
||||
"packaging": { |
||||
"hashes": [ |
||||
"sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", |
||||
"sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" |
||||
], |
||||
"markers": "python_version >= '3.6'", |
||||
"version": "==21.3" |
||||
}, |
||||
"pdoc": { |
||||
"hashes": [ |
||||
"sha256:de3784b5692397fa2f9ddb57a61513a1cabf38281f084aef32b5ed4ee6fbc09e" |
||||
], |
||||
"index": "pypi", |
||||
"version": "==9.0.1" |
||||
}, |
||||
"pep517": { |
||||
"hashes": [ |
||||
"sha256:931378d93d11b298cf511dd634cf5ea4cb249a28ef84160b3247ee9afb4e8ab0", |
||||
"sha256:dd884c326898e2c6e11f9e0b64940606a93eb10ea022a2e067959f3a110cf161" |
||||
], |
||||
"version": "==0.12.0" |
||||
}, |
||||
"pep8-naming": { |
||||
"hashes": [ |
||||
"sha256:4a8daeaeb33cfcde779309fc0c9c0a68a3bbe2ad8a8308b763c5068f86eb9f37", |
||||
"sha256:bb2455947757d162aa4cad55dba4ce029005cd1692f2899a21d51d8630ca7841" |
||||
], |
||||
"index": "pypi", |
||||
"version": "==0.12.1" |
||||
}, |
||||
"pkginfo": { |
||||
"hashes": [ |
||||
"sha256:542e0d0b6750e2e21c20179803e40ab50598d8066d51097a0e382cba9eb02bff", |
||||
"sha256:c24c487c6a7f72c66e816ab1796b96ac6c3d14d49338293d2141664330b55ffc" |
||||
], |
||||
"version": "==1.8.2" |
||||
}, |
||||
"pluggy": { |
||||
"hashes": [ |
||||
"sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", |
||||
"sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" |
||||
], |
||||
"markers": "python_version >= '3.6'", |
||||
"version": "==1.0.0" |
||||
}, |
||||
"py": { |
||||
"hashes": [ |
||||
"sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", |
||||
"sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378" |
||||
], |
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", |
||||
"version": "==1.11.0" |
||||
}, |
||||
"pycodestyle": { |
||||
"hashes": [ |
||||
"sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20", |
||||
"sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f" |
||||
], |
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", |
||||
"version": "==2.8.0" |
||||
}, |
||||
"pyflakes": { |
||||
"hashes": [ |
||||
"sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c", |
||||
"sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e" |
||||
], |
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", |
||||
"version": "==2.4.0" |
||||
}, |
||||
"pygments": { |
||||
"hashes": [ |
||||
"sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65", |
||||
"sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a" |
||||
], |
||||
"markers": "python_version >= '3.5'", |
||||
"version": "==2.11.2" |
||||
}, |
||||
"pyparsing": { |
||||
"hashes": [ |
||||
"sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea", |
||||
"sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484" |
||||
], |
||||
"markers": "python_version >= '3.6'", |
||||
"version": "==3.0.7" |
||||
}, |
||||
"pyproject-flake8": { |
||||
"hashes": [ |
||||
"sha256:bdeca37f78ecd34bd64a49d3657d53d099f5445831071a31c46e1fe20cd61461", |
||||
"sha256:e61ed1dc088e9f9f8a7170967ac4ec135acfef3a59ab9738c7b58cc11f294a7e" |
||||
], |
||||
"index": "pypi", |
||||
"version": "==0.0.1a2" |
||||
}, |
||||
"pytest": { |
||||
"hashes": [ |
||||
"sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89", |
||||
"sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134" |
||||
], |
||||
"index": "pypi", |
||||
"version": "==6.2.5" |
||||
}, |
||||
"pytest-cov": { |
||||
"hashes": [ |
||||
"sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6", |
||||
"sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470" |
||||
], |
||||
"index": "pypi", |
||||
"version": "==3.0.0" |
||||
}, |
||||
"readme-renderer": { |
||||
"hashes": [ |
||||
"sha256:a50a0f2123a4c1145ac6f420e1a348aafefcc9211c846e3d51df05fe3d865b7d", |
||||
"sha256:b512beafa6798260c7d5af3e1b1f097e58bfcd9a575da7c4ddd5e037490a5b85" |
||||
], |
||||
"markers": "python_version >= '3.6'", |
||||
"version": "==32.0" |
||||
}, |
||||
"requests": { |
||||
"extras": [ |
||||
"socks" |
||||
], |
||||
"hashes": [ |
||||
"sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61", |
||||
"sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d" |
||||
], |
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", |
||||
"version": "==2.27.1" |
||||
}, |
||||
"requests-toolbelt": { |
||||
"hashes": [ |
||||
"sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f", |
||||
"sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0" |
||||
], |
||||
"version": "==0.9.1" |
||||
}, |
||||
"rfc3986": { |
||||
"hashes": [ |
||||
"sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", |
||||
"sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c" |
||||
], |
||||
"markers": "python_version >= '3.7'", |
||||
"version": "==2.0.0" |
||||
}, |
||||
"six": { |
||||
"hashes": [ |
||||
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", |
||||
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" |
||||
], |
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", |
||||
"version": "==1.16.0" |
||||
}, |
||||
"toml": { |
||||
"hashes": [ |
||||
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", |
||||
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" |
||||
], |
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", |
||||
"version": "==0.10.2" |
||||
}, |
||||
"tomli": { |
||||
"hashes": [ |
||||
"sha256:b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224", |
||||
"sha256:c292c34f58502a1eb2bbb9f5bbc9a5ebc37bee10ffb8c2d6bbdfa8eb13cc14e1" |
||||
], |
||||
"markers": "python_version >= '3.7'", |
||||
"version": "==2.0.0" |
||||
}, |
||||
"tqdm": { |
||||
"hashes": [ |
||||
"sha256:8dd278a422499cd6b727e6ae4061c40b48fce8b76d1ccbf5d34fca9b7f925b0c", |
||||
"sha256:d359de7217506c9851b7869f3708d8ee53ed70a1b8edbba4dbcb47442592920d" |
||||
], |
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", |
||||
"version": "==4.62.3" |
||||
}, |
||||
"twine": { |
||||
"hashes": [ |
||||
"sha256:28460a3db6b4532bde6a5db6755cf2dce6c5020bada8a641bb2c5c7a9b1f35b8", |
||||
"sha256:8c120845fc05270f9ee3e9d7ebbed29ea840e41f48cd059e04733f7e1d401345" |
||||
], |
||||
"index": "pypi", |
||||
"version": "==3.7.1" |
||||
}, |
||||
"types-requests": { |
||||
"hashes": [ |
||||
"sha256:8ec9f5f84adc6f579f53943312c28a84e87dc70201b54f7c4fbc7d22ecfa8a3e", |
||||
"sha256:c2f4e4754d07ca0a88fd8a89bbc6c8a9f90fb441f9c9b572fd5c484f04817486" |
||||
], |
||||
"index": "pypi", |
||||
"version": "==2.27.8" |
||||
}, |
||||
"types-urllib3": { |
||||
"hashes": [ |
||||
"sha256:a929c68a57b24eee8f7003357b935b802e17767e752d9237266f799ca48ee326", |
||||
"sha256:aa0de26893f138523d5552bbb023826c0cc7ea5749d80c1693c57aab7b55f469" |
||||
], |
||||
"version": "==1.26.8" |
||||
}, |
||||
"typing-extensions": { |
||||
"hashes": [ |
||||
"sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e", |
||||
"sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b" |
||||
], |
||||
"markers": "python_version >= '3.6'", |
||||
"version": "==4.0.1" |
||||
}, |
||||
"urllib3": { |
||||
"hashes": [ |
||||
"sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed", |
||||
"sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c" |
||||
], |
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", |
||||
"version": "==1.26.8" |
||||
}, |
||||
"webencodings": { |
||||
"hashes": [ |
||||
"sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", |
||||
"sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" |
||||
], |
||||
"version": "==0.5.1" |
||||
}, |
||||
"wheel": { |
||||
"hashes": [ |
||||
"sha256:4bdcd7d840138086126cd09254dc6195fb4fc6f01c050a1d7236f2630db1d22a", |
||||
"sha256:e9a504e793efbca1b8e0e9cb979a249cf4a0a7b5b8c9e8b65a5e39d49529c1c4" |
||||
], |
||||
"index": "pypi", |
||||
"version": "==0.37.1" |
||||
}, |
||||
"zipp": { |
||||
"hashes": [ |
||||
"sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d", |
||||
"sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375" |
||||
], |
||||
"markers": "python_version >= '3.7'", |
||||
"version": "==3.7.0" |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,52 @@ |
||||
# Python 3 library and CLI for [SporeStack](https://sporestack.com) [.onion](http://spore64i5sofqlfz5gq2ju4msgzojjwifls7rok2cti624zyq3fcelad.onion) |
||||
|
||||
## Requirements |
||||
|
||||
* Python 3.7-3.10 (or maybe newer) |
||||
|
||||
## Installation |
||||
|
||||
* `pip install sporestack` |
||||
* Recommended: Create a virtual environment, first. Can use `pipenv`, as well. |
||||
|
||||
## Running without installing (preferred) |
||||
|
||||
* Make sure `pipx` is installed. |
||||
* `pipx run sporestack` |
||||
* Make sure you're on the latest version with `sporestack version`. |
||||
|
||||
## Screenshot |
||||
|
||||
 |
||||
|
||||
## Usage |
||||
|
||||
* `sporestack launch SomeHostname --flavor vps-1vcpu-1gb --days 7 --ssh-key ~/.ssh/id_rsa.pub --operating-system debian-10 --currency btc` |
||||
* `sporestack topup SomeHostname --days 3 --currency xmr` |
||||
* `sporestack launch SomeOtherHostname --flavor vps-1vcpu-2gb --days 7 --ssh-key ~/.ssh/id_rsa.pub --operating-system debian-11 --currency btc` |
||||
* `sporestack stop SomeHostname` |
||||
* `sporestack start SomeHostname` |
||||
* `sporestack list` |
||||
* `sporestack remove SomeHostname # If expired` |
||||
* `sporestack settlement-token-generate` |
||||
* `sporestack settlement-token-enable (token) --dollars 10 --currency xmr` |
||||
* `sporestack settlement-token-add (token) --dollars 25 --currency btc` |
||||
* `sporestack settlement-token-balance (token)` |
||||
|
||||
More examples on the [website](https://sporestack.com). |
||||
|
||||
## Notes |
||||
|
||||
* You can use `--settlement-token` if you don't want to pay with QR codes all the time. |
||||
* If using a .onion API endpoint, will try to use a local Tor proxy if connecting to a .onion URL. (127.0.0.1:9050) |
||||
|
||||
## Developing |
||||
|
||||
* `pip install pipenv pre-commit` |
||||
* `pipenv install --deploy --dev` |
||||
* `pipenv run make test` (If you don't have `make`, use `almake`) |
||||
* Hint: `pre-commit run` is a faster way to run some of the tests/autofixers. |
||||
|
||||
## Licence |
||||
|
||||
[Unlicense/Public domain](LICENSE.txt) |
@ -0,0 +1,30 @@ |
||||
[tool.coverage.report] |
||||
show_missing = true |
||||
|
||||
[tool.coverage.run] |
||||
omit = ["tests/*", "build/*"] |
||||
|
||||
# Have to use `pflake8` instead of `flake8` |
||||
[tool.flake8] |
||||
max-line-length = 88 |
||||
noqa-require-code = "true" |
||||
exclude = ".git,__pycache__,build,dist" |
||||
max-complexity = 15 |
||||
|
||||
[tool.isort] |
||||
profile = "black" |
||||
|
||||
[tool.mypy] |
||||
files = "." |
||||
plugins = ["pydantic.mypy"] |
||||
exclude = "(build|site-packages|__pycache__)" |
||||
|
||||
[tool.pydantic-mypy] |
||||
init_forbid_extra = true |
||||
init_typed = true |
||||
warn_required_dynamic_aliases = true |
||||
warn_untyped_fields = true |
||||
|
||||
[build-system] |
||||
requires = ["setuptools", "wheel"] |
||||
build-backend = "setuptools.build_meta" |
@ -0,0 +1,50 @@ |
||||
[metadata] |
||||
name = sporestack |
||||
version = 5.2.1 |
||||
description = SporeStack.com library and client. Launch servers with Monero or Bitcoin. |
||||
long_description = file: README.md |
||||
long_description_content_type = text/markdown |
||||
url = https://sporestack.com/ |
||||
author = SporeStack |
||||
author_email = admin@sporestack.com |
||||
license = Unlicense |
||||
license_file = LICENSE.txt |
||||
classifiers = |
||||
Programming Language :: Python :: 3 |
||||
Programming Language :: Python :: 3 :: Only |
||||
Programming Language :: Python :: 3.7 |
||||
Programming Language :: Python :: 3.8 |
||||
Programming Language :: Python :: 3.9 |
||||
Programming Language :: Python :: 3.10 |
||||
keywords = |
||||
bitcoin |
||||
bitcoincash |
||||
bitcoinsv |
||||
monero |
||||
servers |
||||
infrastructure |
||||
vps |
||||
virtual private server |
||||
|
||||
[options] |
||||
packages = find: |
||||
install_requires = |
||||
pydantic |
||||
requests[socks]>=2.22.0 |
||||
segno |
||||
typer |
||||
importlib-metadata;python_version<"3.8" |
||||
python_requires = >=3.7 |
||||
package_dir = =src |
||||
zip_safe = False |
||||
|
||||
[options.packages.find] |
||||
where = src |
||||
|
||||
[options.entry_points] |
||||
console_scripts = |
||||
sporestack = sporestack.cli:cli |
||||
|
||||
[options.package_data] |
||||
sporestack = |
||||
py.typed |
@ -0,0 +1 @@ |
||||
__all__ = ["api", "api_client", "exceptions"] |
@ -0,0 +1,136 @@ |
||||
""" |
||||
|
||||
SporeStack API request/response models |
||||
|
||||
""" |
||||
|
||||
|
||||
from typing import List, Optional |
||||
|
||||
from pydantic import BaseModel |
||||
|
||||
from .models import NetworkInterface, Payment |
||||
|
||||
|
||||
class TokenEnable: |
||||
url = "/token/{token}/enable" |
||||
method = "POST" |
||||
|
||||
class Request(BaseModel): |
||||
currency: str |
||||
dollars: int |
||||
|
||||
class Response(BaseModel): |
||||
token: str |
||||
payment: Payment |
||||
|
||||
|
||||
class TokenAdd: |
||||
url = "/token/{token}/add" |
||||
method = "POST" |
||||
|
||||
class Request(BaseModel): |
||||
currency: str |
||||
dollars: int |
||||
|
||||
class Response(BaseModel): |
||||
token: str |
||||
payment: Payment |
||||
|
||||
|
||||
class TokenBalance: |
||||
url = "/token/{token}/balance" |
||||
method = "GET" |
||||
|
||||
class Response(BaseModel): |
||||
token: str |
||||
cents: int |
||||
usd: str |
||||
|
||||
|
||||
class ServerLaunch: |
||||
url = "/server/{machine_id}/launch" |
||||
method = "POST" |
||||
|
||||
class Request(BaseModel): |
||||
machine_id: str |
||||
days: int |
||||
currency: str |
||||
flavor: str |
||||
ssh_key: str |
||||
operating_system: str |
||||
region: Optional[str] |
||||
organization: Optional[str] |
||||
settlement_token: Optional[str] |
||||
affiliate_amount: Optional[int] |
||||
affiliate_token: Optional[str] |
||||
|
||||
class Response(BaseModel): |
||||
created_at: Optional[int] |
||||
payment: Payment |
||||
expiration: Optional[int] |
||||
machine_id: str |
||||
network_interfaces: List[NetworkInterface] |
||||
region: str |
||||
latest_api_version: int |
||||
created: bool |
||||
paid: bool |
||||
warning: Optional[str] |
||||
txid: Optional[str] |
||||
operating_system: str |
||||
flavor: str |
||||
|
||||
|
||||
class ServerTopup: |
||||
url = "/server/{machine_id}/topup" |
||||
method = "POST" |
||||
|
||||
class Request(BaseModel): |
||||
machine_id: str |
||||
days: int |
||||
currency: str |
||||
settlement_token: Optional[str] |
||||
affiliate_amount: Optional[int] |
||||
affiliate_token: Optional[str] |
||||
|
||||
class Response(BaseModel): |
||||
machine_id: str |
||||
payment: Payment |
||||
paid: bool |
||||
warning: Optional[str] |
||||
expiration: int |
||||
txid: Optional[str] |
||||
latest_api_version: int |
||||
|
||||
|
||||
class ServerInfo: |
||||
url = "/server/{machine_id}/info" |
||||
method = "GET" |
||||
|
||||
class Response(BaseModel): |
||||
created_at: int |
||||
expiration: int |
||||
running: bool |
||||
machine_id: str |
||||
network_interfaces: List[NetworkInterface] |
||||
region: str |
||||
|
||||
|
||||
class ServerStart: |
||||
url = "/server/{machine_id}/start" |
||||
method = "POST" |
||||
|
||||
|
||||
class ServerStop: |
||||
url = "/server/{machine_id}/stop" |
||||
method = "POST" |
||||
|
||||
|
||||
class ServerDelete: |
||||
url = "/server/{machine_id}/delete" |
||||
method = "POST" |
||||
|
||||
|
||||
class ServerRebuild: |
||||
url = "/server/{machine_id}/rebuild" |
||||
method = "POST" |
@ -0,0 +1,274 @@ |
||||
import logging |
||||
import os |
||||
from time import sleep |
||||
from typing import Any, Dict, Optional |
||||
|
||||
import requests |
||||
|
||||
from . import api, exceptions |
||||
from .version import __version__ |
||||
|
||||
log = logging.getLogger(__name__) |
||||
|
||||
LATEST_API_VERSION = 2 |
||||
|
||||
CLEARNET_ENDPOINT = "https://api.sporestack.com" |
||||
TOR_ENDPOINT = ( |
||||
"http://api.spore64i5sofqlfz5gq2ju4msgzojjwifls7rok2cti624zyq3fcelad.onion" |
||||
) |
||||
|
||||
API_ENDPOINT = CLEARNET_ENDPOINT |
||||
|
||||
GET_TIMEOUT = 60 |
||||
POST_TIMEOUT = 90 |
||||
USE_TOR_PROXY = "auto" |
||||
|
||||
|
||||
def _get_tor_proxy() -> str: |
||||
""" |
||||
This makes testing easier. |
||||
""" |
||||
return os.getenv("TOR_PROXY", "socks5h://127.0.0.1:9050") |
||||
|
||||
|
||||
# For requests module |
||||
TOR_PROXY_REQUESTS = {"http": _get_tor_proxy(), "https": _get_tor_proxy()} |
||||
|
||||
|
||||
def _is_onion_url(url: str) -> bool: |
||||
""" |
||||
returns True/False depending on if a URL looks like a Tor hidden service |
||||
(.onion) or not. |
||||
This is designed to false as non-onion just to be on the safe-ish side, |
||||
depending on your view point. It requires URLs like: http://domain.tld/, |
||||
not http://domain.tld or domain.tld/. |
||||
|
||||
This can be optimized a lot. |
||||
""" |
||||
try: |
||||
url_parts = url.split("/") |
||||
domain = url_parts[2] |
||||
tld = domain.split(".")[-1] |
||||
if tld == "onion": |
||||
return True |
||||
except Exception: |
||||
pass |
||||
return False |
||||
|
||||
|
||||
def _api_request( |
||||
url: str, |
||||
empty_post: bool = False, |
||||
json_params: Optional[Dict[str, Any]] = None, |
||||
retry: bool = False, |
||||
) -> Any: |
||||
headers = {"User-Agent": f"sporestack-python/{__version__}"} |
||||
proxies = {} |
||||
if _is_onion_url(url) is True: |
||||
log.debug("Got a .onion API endpoint, using local Tor SOCKS proxy.") |
||||
proxies = TOR_PROXY_REQUESTS |
||||
|
||||
try: |
||||
if empty_post is True: |
||||
request = requests.post( |
||||
url, timeout=POST_TIMEOUT, proxies=proxies, headers=headers |
||||
) |
||||
elif json_params is None: |
||||
request = requests.get( |
||||
url, timeout=GET_TIMEOUT, proxies=proxies, headers=headers |
||||
) |
||||
else: |
||||
request = requests.post( |
||||
url, |
||||
json=json_params, |
||||
timeout=POST_TIMEOUT, |
||||
proxies=proxies, |
||||
headers=headers, |
||||
) |
||||
except Exception as e: |
||||
if retry is True: |
||||
log.warning(f"Got an error, but retrying: {e}") |
||||
sleep(5) |
||||
# Try again. |
||||
return _api_request( |
||||
url, |
||||
empty_post=empty_post, |
||||
json_params=json_params, |
||||
retry=retry, |
||||
) |
||||
else: |
||||
raise |
||||
|
||||
status_code_first_digit = request.status_code // 100 |
||||
if status_code_first_digit == 2: |
||||
try: |
||||
return request.json() |
||||
except Exception: |
||||
return request.content |
||||
elif status_code_first_digit == 4: |
||||
log.debug("HTTP status code: {request.status_code}") |
||||
raise exceptions.SporeStackUserError(request.content.decode("utf-8")) |
||||
elif status_code_first_digit == 5: |
||||
if retry is True: |
||||
log.warning(request.content.decode("utf-8")) |
||||
log.warning("Got a 500, retrying in 5 seconds...") |
||||
sleep(5) |
||||
# Try again if we get a 500 |
||||
return _api_request( |
||||
url, |
||||
empty_post=empty_post, |
||||
json_params=json_params, |
||||
retry=retry, |
||||
) |
||||
else: |
||||
raise exceptions.SporeStackServerError(str(request.content)) |
||||
else: |
||||
# Not sure why we'd get this. |
||||
request.raise_for_status() |
||||
raise Exception("Stuff broke strangely. Please contact SporeStack support.") |
||||
|
||||
|
||||
def launch( |
||||
machine_id: str, |
||||
days: int, |
||||
currency: str, |
||||
flavor: str, |
||||
operating_system: str, |
||||
ssh_key: str, |
||||
api_endpoint: str = API_ENDPOINT, |
||||
region: Optional[str] = None, |
||||
settlement_token: Optional[str] = None, |
||||
retry: bool = False, |
||||
affiliate_amount: Optional[int] = None, |
||||
affiliate_token: Optional[str] = None, |
||||
) -> api.ServerLaunch.Response: |
||||
request = api.ServerLaunch.Request( |
||||
machine_id=machine_id, |
||||
days=days, |
||||
currency=currency, |
||||
settlement_token=settlement_token, |
||||
affiliate_amount=affiliate_amount, |
||||
affiliate_token=affiliate_token, |
||||
flavor=flavor, |
||||
region=region, |
||||
operating_system=operating_system, |
||||
ssh_key=ssh_key, |
||||
) |
||||
url = api_endpoint + api.ServerLaunch.url.format(machine_id=machine_id) |
||||
response = _api_request(url=url, json_params=request.dict(), retry=retry) |
||||
response_object = api.ServerLaunch.Response.parse_obj(response) |
||||
assert response_object.machine_id == machine_id |
||||
return response_object |
||||
|
||||
|
||||
def topup( |
||||
machine_id: str, |
||||
days: int, |
||||
currency: str, |
||||
api_endpoint: str = API_ENDPOINT, |
||||
settlement_token: Optional[str] = None, |
||||
retry: bool = False, |
||||
affiliate_amount: Optional[int] = None, |
||||
affiliate_token: Optional[str] = None, |
||||
) -> api.ServerTopup.Response: |
||||
""" |
||||
Topup a server. |
||||
""" |
||||
request = api.ServerTopup.Request( |
||||
machine_id=machine_id, |
||||
days=days, |
||||
currency=currency, |
||||
settlement_token=settlement_token, |
||||
affiliate_amount=affiliate_amount, |
||||
affiliate_token=affiliate_token, |
||||
) |
||||
url = api_endpoint + api.ServerTopup.url.format(machine_id=machine_id) |
||||
response = _api_request(url=url, json_params=request.dict(), retry=retry) |
||||
response_object = api.ServerTopup.Response.parse_obj(response) |
||||
assert response_object.machine_id == machine_id |
||||
return response_object |
||||
|
||||
|
||||
def start(machine_id: str, api_endpoint: str = API_ENDPOINT) -> None: |
||||
""" |
||||
Boots the server. |
||||
""" |
||||
url = api_endpoint + api.ServerStart.url.format(machine_id=machine_id) |
||||
_api_request(url, empty_post=True) |
||||
|
||||
|
||||
def stop(machine_id: str, api_endpoint: str = API_ENDPOINT) -> None: |
||||
""" |
||||
Powers off the server. |
||||
""" |
||||
url = api_endpoint + api.ServerStop.url.format(machine_id=machine_id) |
||||
_api_request(url, empty_post=True) |
||||
|
||||
|
||||
def delete(machine_id: str, api_endpoint: str = API_ENDPOINT) -> None: |
||||
""" |
||||
Deletes the server. |
||||
""" |
||||
url = api_endpoint + api.ServerDelete.url.format(machine_id=machine_id) |
||||
_api_request(url, empty_post=True) |
||||
|
||||
|
||||
def rebuild(machine_id: str, api_endpoint: str = API_ENDPOINT) -> None: |
||||
""" |
||||
Rebuilds the server with the operating system and SSH key set at launch time. |
||||
|
||||
Deletes all of the data on the server! |
||||
""" |
||||
url = api_endpoint + api.ServerRebuild.url.format(machine_id=machine_id) |
||||
_api_request(url, empty_post=True) |
||||
|
||||
|
||||
def info(machine_id: str, api_endpoint: str = API_ENDPOINT) -> api.ServerInfo.Response: |
||||
""" |
||||
Returns info about the server. |
||||
""" |
||||
url = api_endpoint + api.ServerInfo.url.format(machine_id=machine_id) |
||||
response = _api_request(url) |
||||
response_object = api.ServerInfo.Response.parse_obj(response) |
||||
assert response_object.machine_id == machine_id |
||||
return response_object |
||||
|
||||
|
||||
def token_enable( |
||||
token: str, |
||||
dollars: int, |
||||
currency: str, |
||||
api_endpoint: str = API_ENDPOINT, |
||||
retry: bool = False, |
||||
) -> api.TokenEnable.Response: |
||||
request = api.TokenEnable.Request(dollars=dollars, currency=currency) |
||||
url = api_endpoint + api.TokenEnable.url.format(token=token) |
||||
response = _api_request(url=url, json_params=request.dict(), retry=retry) |
||||
response_object = api.TokenEnable.Response.parse_obj(response) |
||||
assert response_object.token == token |
||||
return response_object |
||||
|
||||
|
||||
def token_add( |
||||
token: str, |
||||
dollars: int, |
||||
currency: str, |
||||
api_endpoint: str = API_ENDPOINT, |
||||
retry: bool = False, |
||||
) -> api.TokenAdd.Response: |
||||
request = api.TokenAdd.Request(dollars=dollars, currency=currency) |
||||
url = api_endpoint + api.TokenAdd.url.format(token=token) |
||||
response = _api_request(url=url, json_params=request.dict(), retry=retry) |
||||
response_object = api.TokenAdd.Response.parse_obj(response) |
||||
assert response_object.token == token |
||||
return response_object |
||||
|
||||
|
||||
def token_balance( |
||||
token: str, api_endpoint: str = API_ENDPOINT |
||||
) -> api.TokenBalance.Response: |
||||
url = api_endpoint + api.TokenBalance.url.format(token=token) |
||||
response = _api_request(url=url) |
||||
response_object = api.TokenBalance.Response.parse_obj(response) |
||||
assert response_object.token == token |
||||
return response_object |
@ -0,0 +1,593 @@ |
||||
""" |
||||
SporeStack CLI: `sporestack` |
||||
""" |
||||
|
||||
import importlib.util |
||||
import json |
||||
import logging |
||||
import os |
||||
import sys |
||||
import time |
||||
from pathlib import Path |
||||
from types import ModuleType |
||||
from typing import TYPE_CHECKING, Any, Dict, Optional |
||||
|
||||
if sys.version_info[:2] >= (3, 8): # pragma: nocover |
||||
from importlib.metadata import version as importlib_metadata_version |
||||
else: # pragma: nocover |
||||
# Python 3.7 doesn't have this. |
||||
from importlib_metadata import version as importlib_metadata_version |
||||
|
||||
import typer |
||||
|
||||
|
||||
def lazy_import(name: str) -> ModuleType: |
||||
""" |
||||
Lazily import a module. Helps speed up CLI performance. |
||||
""" |
||||
spec = importlib.util.find_spec(name) |
||||
assert spec is not None |
||||
assert spec.loader is not None |
||||
loader = importlib.util.LazyLoader(spec.loader) |
||||
spec.loader = loader |
||||
module = importlib.util.module_from_spec(spec) |
||||
sys.modules[name] = module |
||||
loader.exec_module(module) |
||||
return module |
||||
|
||||
|
||||
# For mypy |
||||
if TYPE_CHECKING: |
||||
from . import api_client |
||||
else: |
||||
api_client = lazy_import("sporestack.api_client") |
||||
|
||||
HELP = """ |
||||
SporeStack Python CLI |
||||
|
||||
Optional environment variables: |
||||
SPORESTACK_ENDPOINT |
||||
*or* |
||||
SPORESTACK_USE_TOR_ENDPOINT |
||||
|
||||
TOR_PROXY (defaults to socks5h://127.0.0.1:9050 which is fine for most) |
||||
""" |
||||
|
||||
cli = typer.Typer(help=HELP) |
||||
|
||||
logging.basicConfig(level=logging.INFO) |
||||
|
||||
DEFAULT_FLAVOR = "vps-1vcpu-1gb" |
||||
|
||||
WAITING_PAYMENT_TO_PROCESS = "Waiting for payment to process..." |
||||
|
||||
|
||||
def get_api_endpoint() -> str: |
||||
api_endpoint = os.getenv("SPORESTACK_ENDPOINT", api_client.CLEARNET_ENDPOINT) |
||||
if os.getenv("SPORESTACK_USE_TOR_ENDPOINT", None) is not None: |
||||
api_endpoint = api_client.TOR_ENDPOINT |
||||
return api_endpoint |
||||
|
||||
|
||||
def make_payment(currency: str, uri: str, usd: str) -> None: |
||||
import segno |
||||
|
||||
premessage = """Payment URI: {} |
||||