Progress + replace pre-commit with black+ruff

This commit is contained in:
Administrator 2023-02-07 18:22:20 +00:00
parent ad6fb4ce2e
commit a947d83669
9 changed files with 621 additions and 472 deletions

View File

@ -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]

View File

@ -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
View File

@ -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"

846
Pipfile.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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)

View File

@ -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
)

View File

@ -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"
)

View File

@ -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()