Progress + replace pre-commit with black+ruff
This commit is contained in:
parent
ad6fb4ce2e
commit
a947d83669
|
@ -1,30 +0,0 @@
|
||||||
# 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: db7346d375eda68a0174f2c057dd97f2fbffe030 # frozen: v4.2.0
|
|
||||||
hooks:
|
|
||||||
- id: trailing-whitespace
|
|
||||||
- repo: https://github.com/psf/black
|
|
||||||
rev: ae2c0758c9e61a385df9700dc9c231bf54887041 # frozen: 22.3.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: 256bd84aa5a17edbd3dcfaaa4f30f870168d2838 # frozen: v2.32.0
|
|
||||||
hooks:
|
|
||||||
- id: pyupgrade
|
|
||||||
args: [--py37-plus]
|
|
||||||
- repo: https://github.com/jackdewinter/pymarkdown
|
|
||||||
rev: be56696256d5491e8a907b72e5a3852034546adb # frozen: v0.9.5
|
|
||||||
hooks:
|
|
||||||
- id: pymarkdown
|
|
||||||
args: [--disable-rules=MD013, --set=plugins.md024.siblings_only=$!True, scan]
|
|
9
Makefile
9
Makefile
|
@ -1,10 +1,15 @@
|
||||||
|
format:
|
||||||
|
black .
|
||||||
|
ruff --fix .
|
||||||
|
|
||||||
test:
|
test:
|
||||||
python -m pflake8 .
|
black --check .
|
||||||
|
ruff .
|
||||||
python -m mypy --strict .
|
python -m mypy --strict .
|
||||||
$(MAKE) test-pytest
|
$(MAKE) test-pytest
|
||||||
|
|
||||||
test-pytest:
|
test-pytest:
|
||||||
python -m pytest --cov=sporestack --cov-fail-under=49 --cov-report=term --durations=3 --cache-clear
|
python -m pytest --cov=sporestack --cov-fail-under=40 --cov-report=term --durations=3 --cache-clear
|
||||||
|
|
||||||
build-dist:
|
build-dist:
|
||||||
rm dist/* || true
|
rm dist/* || true
|
||||||
|
|
20
Pipfile
20
Pipfile
|
@ -7,24 +7,24 @@ name = "pypi"
|
||||||
sporestack = {editable = true, path = "."}
|
sporestack = {editable = true, path = "."}
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
flake8 = "~=4.0"
|
black = "~=23.1"
|
||||||
pyproject-flake8 = "==0.0.1a2"
|
mypy = "~=1.0"
|
||||||
flake8-noqa = "~=1.2"
|
pytest = "~=7.2"
|
||||||
pep8-naming = "~=0.12.1"
|
pytest-cov = "~=4.0"
|
||||||
mypy = "==0.942"
|
pytest-mock = "~=3.6"
|
||||||
pytest = "~=6.2"
|
pytest-socket = "~=0.5.1"
|
||||||
pytest-cov = "~=3.0"
|
ruff = "==0.0.239"
|
||||||
|
|
||||||
types-requests = "~=2.25"
|
types-requests = "~=2.25"
|
||||||
|
|
||||||
# Building
|
# Building
|
||||||
wheel = "~=0.37.0"
|
wheel = "~=0.38.0"
|
||||||
build = "~=0.7.0"
|
build = "~=0.7.0"
|
||||||
# Publishing
|
# Publishing
|
||||||
twine = "~=3.4"
|
twine = "~=3.4"
|
||||||
|
|
||||||
# Docs
|
# Docs
|
||||||
pdoc = "~=9.0"
|
pdoc = "~=12.0"
|
||||||
|
|
||||||
# Python `make` implementation
|
# Python `make` implementation
|
||||||
almost-make = "~=0.5.1"
|
almost-make = "~=0.5.2"
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,23 +1,41 @@
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
addopts = "--strict-markers --disable-socket"
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
select = [
|
||||||
|
"F", # pyflakes
|
||||||
|
"E", # pycodestyle errors
|
||||||
|
"W", # pycodestyle warnings
|
||||||
|
"I", # isort
|
||||||
|
"N", # pep8-naming
|
||||||
|
"RUF", # Unused noqa + more
|
||||||
|
"ANN", # Type annotations
|
||||||
|
"UP", # pyupgrade
|
||||||
|
]
|
||||||
|
|
||||||
|
ignore = [
|
||||||
|
"ANN101", # Type annotations for self
|
||||||
|
"ANN401", # Allow ANY
|
||||||
|
]
|
||||||
|
|
||||||
|
unfixable = [
|
||||||
|
"F401", # Don't try to automatically remove unused imports
|
||||||
|
]
|
||||||
|
|
||||||
|
target-version = "py37"
|
||||||
|
update-check = false
|
||||||
|
|
||||||
[tool.coverage.report]
|
[tool.coverage.report]
|
||||||
show_missing = true
|
show_missing = true
|
||||||
|
|
||||||
[tool.coverage.run]
|
[tool.coverage.run]
|
||||||
omit = ["tests/*", "build/*"]
|
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]
|
[tool.mypy]
|
||||||
files = "."
|
files = "."
|
||||||
plugins = ["pydantic.mypy"]
|
plugins = ["pydantic.mypy"]
|
||||||
exclude = "(build|site-packages|__pycache__)"
|
exclude = "(build|site-packages|__pycache__)"
|
||||||
|
strict = true
|
||||||
|
|
||||||
[tool.pydantic-mypy]
|
[tool.pydantic-mypy]
|
||||||
init_forbid_extra = true
|
init_forbid_extra = true
|
||||||
|
|
|
@ -135,7 +135,7 @@ def _api_request(
|
||||||
class APIClient:
|
class APIClient:
|
||||||
api_endpoint: str = API_ENDPOINT
|
api_endpoint: str = API_ENDPOINT
|
||||||
|
|
||||||
def launch(
|
def server_launch(
|
||||||
self,
|
self,
|
||||||
machine_id: str,
|
machine_id: str,
|
||||||
days: int,
|
days: int,
|
||||||
|
@ -199,23 +199,23 @@ class APIClient:
|
||||||
)
|
)
|
||||||
_api_request(url, empty_post=True)
|
_api_request(url, empty_post=True)
|
||||||
|
|
||||||
def sever_start(self, machine_id: str) -> None:
|
def server_start(self, machine_id: str) -> None:
|
||||||
"""
|
"""
|
||||||
Boots the server.
|
Power on the server.
|
||||||
"""
|
"""
|
||||||
url = self.api_endpoint + api.ServerStart.url.format(machine_id=machine_id)
|
url = self.api_endpoint + api.ServerStart.url.format(machine_id=machine_id)
|
||||||
_api_request(url, empty_post=True)
|
_api_request(url, empty_post=True)
|
||||||
|
|
||||||
def server_stop(self, machine_id: str) -> None:
|
def server_stop(self, machine_id: str) -> None:
|
||||||
"""
|
"""
|
||||||
Powers off the server.
|
Power off the server.
|
||||||
"""
|
"""
|
||||||
url = self.api_endpoint + api.ServerStop.url.format(machine_id=machine_id)
|
url = self.api_endpoint + api.ServerStop.url.format(machine_id=machine_id)
|
||||||
_api_request(url, empty_post=True)
|
_api_request(url, empty_post=True)
|
||||||
|
|
||||||
def server_delete(self, machine_id: str) -> None:
|
def server_delete(self, machine_id: str) -> None:
|
||||||
"""
|
"""
|
||||||
Deletes the server.
|
Delete the server.
|
||||||
"""
|
"""
|
||||||
url = self.api_endpoint + api.ServerDelete.url.format(machine_id=machine_id)
|
url = self.api_endpoint + api.ServerDelete.url.format(machine_id=machine_id)
|
||||||
_api_request(url, empty_post=True)
|
_api_request(url, empty_post=True)
|
||||||
|
|
|
@ -1,63 +1,78 @@
|
||||||
from . import api_client
|
from dataclasses import dataclass
|
||||||
|
from typing import List, Union
|
||||||
|
|
||||||
|
from . import api
|
||||||
|
from .api_client import APIClient
|
||||||
from .utils import random_machine_id, random_token
|
from .utils import random_machine_id, random_token
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Server:
|
class Server:
|
||||||
machine_id: str
|
machine_id: str
|
||||||
api_client: api_client.APIClient = api_client.APIClient()
|
api_client: APIClient = APIClient()
|
||||||
|
token: Union[str, None] = None
|
||||||
|
|
||||||
def info(self) -> api.ServerInfo.Response:
|
def info(self) -> api.ServerInfo.Response:
|
||||||
return api_client.server_info(self.machine_id)
|
return self.api_client.server_info(self.machine_id)
|
||||||
|
|
||||||
def rebuild(self) -> None:
|
def rebuild(self) -> None:
|
||||||
api_client.server_rebuild(self.machine_id)
|
self.api_client.server_rebuild(self.machine_id)
|
||||||
|
|
||||||
def forget(self) -> None:
|
def forget(self) -> None:
|
||||||
api_client.server_forget(self.machine_id)
|
self.api_client.server_forget(self.machine_id)
|
||||||
|
|
||||||
def delete(self) -> None:
|
def delete(self) -> None:
|
||||||
api_client.server_delete(self.machine_id)
|
self.api_client.server_delete(self.machine_id)
|
||||||
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
"""Powers on the server."""
|
"""Powers on the server."""
|
||||||
api_client.server_start(self.machine_id)
|
self.api_client.server_start(self.machine_id)
|
||||||
|
|
||||||
def stop(self) -> None:
|
def stop(self) -> None:
|
||||||
"""Powers off the server."""
|
"""Powers off the server."""
|
||||||
api_client.server_stop(self.machine_id)
|
self.api_client.server_stop(self.machine_id)
|
||||||
|
|
||||||
def autorenew_enable(self) -> None:
|
def autorenew_enable(self) -> None:
|
||||||
"""Enables autorenew on the server."""
|
"""Enables autorenew on the server."""
|
||||||
api_client.autorenew_enable(self.machine_id)
|
self.api_client.autorenew_enable(self.machine_id)
|
||||||
|
|
||||||
def autorenew_disable(self) -> None:
|
def autorenew_disable(self) -> None:
|
||||||
"""Disables autorenew on the server."""
|
"""Disables autorenew on the server."""
|
||||||
api_client.autorenew_disable(self.machine_id)
|
self.api_client.autorenew_disable(self.machine_id)
|
||||||
|
|
||||||
def topup(days: int, token: str) -> None:
|
def topup(self, days: int) -> None:
|
||||||
"""Renew the server for the amount of days specified, from the token specified."""
|
"""
|
||||||
api_client.server_topup(machine_id=self.machine_id, days=days, token=token)
|
Renew the server for the amount of days specified, from the token specified.
|
||||||
|
"""
|
||||||
|
if self.token is None:
|
||||||
|
raise ValueError("token must be set to top up a server!")
|
||||||
|
self.api_client.server_topup(
|
||||||
|
machine_id=self.machine_id, days=days, token=self.token
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Token:
|
class Token:
|
||||||
token: str = random_token()
|
token: str = random_token()
|
||||||
api_client: api_client.APIClient = api_client.APIClient()
|
api_client: APIClient = APIClient()
|
||||||
|
|
||||||
def add(self, dollars: int, currency: str) -> None:
|
def add(self, dollars: int, currency: str) -> None:
|
||||||
"""FIXME"""
|
"""Add to token"""
|
||||||
self.api_client.token_add(token=token, dollars=dollars, currency=currency)
|
self.api_client.token_add(token=self.token, dollars=dollars, currency=currency)
|
||||||
|
|
||||||
def balance(self) -> int:
|
def balance(self) -> int:
|
||||||
"""Returns the token's balance in cents."""
|
"""Returns the token's balance in cents."""
|
||||||
self.api_client.token_balance(token=token).cents
|
return self.api_client.token_balance(token=self.token).cents
|
||||||
|
|
||||||
def servers(self) -> List[Server]:
|
def servers(self) -> List[Server]:
|
||||||
server_classes = []
|
server_classes: List[Server] = []
|
||||||
for server in api_client.servers_launched_from_token():
|
for server in self.api_client.servers_launched_from_token(self.token).servers:
|
||||||
server_classes.append(
|
server_classes.append(
|
||||||
Server(machine_id=server.machine_id, api_client=self.api_client)
|
Server(
|
||||||
|
machine_id=server.machine_id,
|
||||||
|
api_client=self.api_client,
|
||||||
|
token=self.token,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
return server_classes
|
return server_classes
|
||||||
|
|
||||||
|
@ -66,19 +81,23 @@ class Token:
|
||||||
ssh_key: str,
|
ssh_key: str,
|
||||||
flavor: str,
|
flavor: str,
|
||||||
days: int,
|
days: int,
|
||||||
|
operating_system: str,
|
||||||
region: Union[str, None] = None,
|
region: Union[str, None] = None,
|
||||||
hostname: str = "",
|
hostname: str = "",
|
||||||
autorenew: bool = False,
|
autorenew: bool = False,
|
||||||
machine_id=random_machine_id(),
|
machine_id: str = random_machine_id(),
|
||||||
) -> Server:
|
) -> Server:
|
||||||
self.api_client.server_launch(
|
self.api_client.server_launch(
|
||||||
machine_id=machine_id,
|
machine_id=machine_id,
|
||||||
days=days,
|
days=days,
|
||||||
token=self.token,
|
token=self.token,
|
||||||
region=region,
|
region=region,
|
||||||
|
flavor=flavor,
|
||||||
operating_system=operating_system,
|
operating_system=operating_system,
|
||||||
ssh_key=ssh_key,
|
ssh_key=ssh_key,
|
||||||
hostname=hostname,
|
hostname=hostname,
|
||||||
autorenew=autorenew,
|
autorenew=autorenew,
|
||||||
)
|
)
|
||||||
return Server(machine_id=machine_id, api_client=self.api_client)
|
return Server(
|
||||||
|
machine_id=machine_id, api_client=self.api_client, token=self.token
|
||||||
|
)
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
from unittest.mock import MagicMock, patch
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from pydantic import ValidationError
|
|
||||||
|
|
||||||
from sporestack import api_client
|
from sporestack import api_client
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,72 +13,3 @@ def test__is_onion_url() -> None:
|
||||||
assert api_client._is_onion_url("http://onion.domain.com/.onion/") is False
|
assert api_client._is_onion_url("http://onion.domain.com/.onion/") is False
|
||||||
assert api_client._is_onion_url("http://me.me/file.onion/") is False
|
assert api_client._is_onion_url("http://me.me/file.onion/") is False
|
||||||
assert api_client._is_onion_url("http://me.me/file.onion") is False
|
assert api_client._is_onion_url("http://me.me/file.onion") is False
|
||||||
|
|
||||||
|
|
||||||
@patch("sporestack.api_client._api_request")
|
|
||||||
def test_launch(mock_api_request: MagicMock) -> None:
|
|
||||||
with pytest.raises(ValidationError):
|
|
||||||
api_client.launch(
|
|
||||||
"dummymachineid",
|
|
||||||
days=1,
|
|
||||||
operating_system="freebsd-12",
|
|
||||||
ssh_key="id-rsa...",
|
|
||||||
flavor="aflavor",
|
|
||||||
token="f" * 64,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@patch("sporestack.api_client._api_request")
|
|
||||||
def test_topup(mock_api_request: MagicMock) -> None:
|
|
||||||
with pytest.raises(ValidationError):
|
|
||||||
api_client.topup("dummymachineid", token="f" * 64, days=1)
|
|
||||||
|
|
||||||
|
|
||||||
@patch("sporestack.api_client._api_request")
|
|
||||||
def test_start(mock_api_request: MagicMock) -> None:
|
|
||||||
api_client.start("dummymachineid")
|
|
||||||
mock_api_request.assert_called_once_with(
|
|
||||||
"https://api.sporestack.com/server/dummymachineid/start", empty_post=True
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@patch("sporestack.api_client._api_request")
|
|
||||||
def test_stop(mock_api_request: MagicMock) -> None:
|
|
||||||
api_client.stop("dummymachineid")
|
|
||||||
mock_api_request.assert_called_once_with(
|
|
||||||
"https://api.sporestack.com/server/dummymachineid/stop", empty_post=True
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@patch("sporestack.api_client._api_request")
|
|
||||||
def test_rebuild(mock_api_request: MagicMock) -> None:
|
|
||||||
api_client.rebuild("dummymachineid")
|
|
||||||
mock_api_request.assert_called_once_with(
|
|
||||||
"https://api.sporestack.com/server/dummymachineid/rebuild", empty_post=True
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@patch("sporestack.api_client._api_request")
|
|
||||||
def test_info(mock_api_request: MagicMock) -> None:
|
|
||||||
with pytest.raises(ValidationError):
|
|
||||||
api_client.info("dummymachineid")
|
|
||||||
mock_api_request.assert_called_once_with(
|
|
||||||
"https://api.sporestack.com/server/dummymachineid/info"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@patch("sporestack.api_client._api_request")
|
|
||||||
def test_delete(mock_api_request: MagicMock) -> None:
|
|
||||||
api_client.delete("dummymachineid")
|
|
||||||
mock_api_request.assert_called_once_with(
|
|
||||||
"https://api.sporestack.com/server/dummymachineid/destroy", empty_post=True
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@patch("sporestack.api_client._api_request")
|
|
||||||
def test_token_balance(mock_api_request: MagicMock) -> None:
|
|
||||||
with pytest.raises(ValidationError):
|
|
||||||
api_client.token_balance("dummytoken")
|
|
||||||
mock_api_request.assert_called_once_with(
|
|
||||||
url="https://api.sporestack.com/token/dummytoken/balance"
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import pytest
|
import pytest
|
||||||
import typer
|
import typer
|
||||||
from _pytest.monkeypatch import MonkeyPatch
|
from _pytest.monkeypatch import MonkeyPatch
|
||||||
from typer.testing import CliRunner
|
|
||||||
|
|
||||||
from sporestack import cli
|
from sporestack import cli
|
||||||
from sporestack.api_client import TOR_ENDPOINT
|
from sporestack.api_client import TOR_ENDPOINT
|
||||||
|
from typer.testing import CliRunner
|
||||||
|
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue