5.2.1
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: {}
|
||||
Pay *exactly* the specified amount. No more, no less. Pay within
|
||||
one hour at the very most.
|
||||
Resize your terminal and try again if QR code above is not readable.
|
||||
Press ctrl+c to abort."""
|
||||
message = premessage.format(uri)
|
||||
qr = segno.make(uri)
|
||||
# This typer.echos.
|
||||
qr.terminal()
|
||||
typer.echo(message)
|
||||
typer.echo(f"Approximate price in USD: {usd}")
|
||||
input("[Press enter once you have made payment.]")
|
||||
|
||||
|
||||
@cli.command()
|
||||
def launch(
|
||||
hostname: str,
|
||||
days: int = typer.Option(...),
|
||||
ssh_key_file: Path = typer.Option(...),
|
||||
operating_system: str = typer.Option(...),
|
||||
flavor: str = DEFAULT_FLAVOR,
|
||||
currency: Optional[str] = None,
|
||||
settlement_token: Optional[str] = None,
|
||||
region: Optional[str] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Attempts to launch a server.
|
||||
"""
|
||||
|
||||
from . import utils
|
||||
|
||||
if settlement_token is not None:
|
||||
if currency is None or currency == "settlement":
|
||||
currency = "settlement"
|
||||
else:
|
||||
msg = "Cannot use non-settlement --currency with --settlement-token"
|
||||
typer.echo(msg, err=True)
|
||||
raise typer.Exit(code=2)
|
||||
if currency is None:
|
||||
typer.echo("--currency must be set.", err=True)
|
||||
raise typer.Exit(code=2)
|
||||
|
||||
if machine_exists(hostname):
|
||||
typer.echo(f"{hostname} already created.")
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
ssh_key = ssh_key_file.read_text()
|
||||
|
||||
machine_id = utils.random_machine_id()
|
||||
|
||||
assert currency is not None
|
||||
response = api_client.launch(
|
||||
machine_id=machine_id,
|
||||
days=days,
|
||||
flavor=flavor,
|
||||
operating_system=operating_system,
|
||||
ssh_key=ssh_key,
|
||||
currency=currency,
|
||||
region=region,
|
||||
settlement_token=settlement_token,
|
||||
api_endpoint=get_api_endpoint(),
|
||||
retry=True,
|
||||
)
|
||||
|
||||
# This will be false at least the first time if paying with BTC or BCH.
|
||||
if response.payment.paid is False:
|
||||
assert response.payment.uri is not None
|
||||
make_payment(
|
||||
currency=currency,
|
||||
uri=response.payment.uri,
|
||||
usd=response.payment.usd,
|
||||
)
|
||||
|
||||
tries = 360
|
||||
while tries > 0:
|
||||
tries = tries - 1
|
||||
typer.echo(WAITING_PAYMENT_TO_PROCESS, err=True)
|
||||
# FIXME: Wait one hour in a smarter way.
|
||||
# Waiting for payment to set in.
|
||||
time.sleep(10)
|
||||
response = api_client.launch(
|
||||
machine_id=machine_id,
|
||||
days=days,
|
||||
flavor=flavor,
|
||||
operating_system=operating_system,
|
||||
ssh_key=ssh_key,
|
||||
currency=currency,
|
||||
region=region,
|
||||
settlement_token=settlement_token,
|
||||
api_endpoint=get_api_endpoint(),
|
||||
retry=True,
|
||||
)
|
||||
if response.payment.paid is True:
|
||||
break
|
||||
|
||||
if response.created is False:
|
||||
tries = 360
|
||||
while tries > 0:
|
||||
typer.echo("Waiting for server to build...", err=True)
|
||||
tries = tries + 1
|
||||
# Waiting for server to spin up.
|
||||
time.sleep(10)
|
||||
response = api_client.launch(
|
||||
machine_id=machine_id,
|
||||
days=days,
|
||||
flavor=flavor,
|
||||
operating_system=operating_system,
|
||||
ssh_key=ssh_key,
|
||||
currency=currency,
|
||||
region=region,
|
||||
settlement_token=settlement_token,
|
||||
api_endpoint=get_api_endpoint(),
|
||||
retry=True,
|
||||
)
|
||||
if response.created is True:
|
||||
break
|
||||
|
||||
if response.created is False:
|
||||
typer.echo("Server creation failed, tries exceeded.", err=True)
|
||||
raise typer.Exit(code=1) |