Skip to content

Commit

Permalink
Add ruff (#75)
Browse files Browse the repository at this point in the history
  • Loading branch information
gaborbernat committed Jun 15, 2023
1 parent 8a59136 commit 9866ca5
Show file tree
Hide file tree
Showing 9 changed files with 133 additions and 111 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
- "3.8"
- "3.7"
os:
- ubuntu-22.04
- ubuntu-latest
- windows-2022
- macos-12

Expand Down Expand Up @@ -65,7 +65,7 @@ jobs:
fail-fast: false
matrix:
os:
- ubuntu-22.04
- ubuntu-latest
- windows-2022
tox_env:
- dev
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:

jobs:
release:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
environment:
name: release
url: https://pypi.org/p/devpi-process
Expand Down
54 changes: 9 additions & 45 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,68 +2,32 @@ repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: check-ast
- id: check-builtin-literals
- id: check-docstring-first
- id: check-merge-conflict
- id: check-yaml
- id: check-toml
- id: debug-statements
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/asottile/pyupgrade
rev: v3.3.2
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: "v0.0.272"
hooks:
- id: pyupgrade
args: ["--py37-plus"]
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- repo: https://github.com/psf/black
rev: 23.3.0
hooks:
- id: black
args: [--safe]
- repo: https://github.com/asottile/blacken-docs
rev: 1.13.0
hooks:
- id: blacken-docs
additional_dependencies: [black==23.3]
- repo: https://github.com/tox-dev/pyproject-fmt
rev: "0.11.1"
hooks:
- id: pyproject-fmt
- repo: https://github.com/tox-dev/tox-ini-fmt
rev: "1.3.0"
hooks:
- id: tox-ini-fmt
args: ["-p", "fix"]
- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
- repo: https://github.com/tox-dev/pyproject-fmt
rev: "0.11.2"
hooks:
- id: flake8
additional_dependencies:
- flake8-bugbear==23.3.23
- flake8-comprehensions==3.12
- flake8-pytest-style==1.7.2
- flake8-spellcheck==0.28
- flake8-unused-arguments==0.0.13
- flake8-noqa==1.3.1
- pep8-naming==0.13.3
- flake8-pyproject==1.2.3
- id: pyproject-fmt
additional_dependencies: ["tox>=4.6"]
- repo: https://github.com/pre-commit/mirrors-prettier
rev: "v2.7.1"
rev: "v3.0.0-alpha.9-for-vscode"
hooks:
- id: prettier
additional_dependencies:
- prettier@2.7.1
- "@prettier/plugin-xml@2.2"
args: ["--print-width=120", "--prose-wrap=always"]
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.34.0
hooks:
- id: markdownlint
- repo: meta
hooks:
- id: check-hooks-apply
Expand Down
36 changes: 21 additions & 15 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,19 +62,6 @@ version.source = "vcs"
[tool.black]
line-length = 120

[tool.isort]
profile = "black"
known_first_party = ["devpi_process"]
line_length = 120
add_imports = ["from __future__ import annotations"]

[tool.flake8]
max-complexity = 22
max-line-length = 120
unused-arguments-ignore-abstract-functions = true
noqa-require-code = true
dictionaries = ["en_US", "python", "technical", "django"]

[tool.coverage]
html.show_contexts = true
html.skip_covered = false
Expand All @@ -96,5 +83,24 @@ python_version = "3.11"
show_error_codes = true
strict = true

[tool.pep8]
max-line-length = "120"
[tool.ruff]
select = ["ALL"]
line-length = 120
target-version = "py37"
isort = {known-first-party = ["devpi_process"], required-imports = ["from __future__ import annotations"]}
ignore = [
"ANN101", # no typoe annotation for self
"ANN401", # allow Any as type annotation
"D203", # `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible
"D212", # `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible
"S104", # Possible binding to all interface
]
[tool.ruff.per-file-ignores]
"tests/**/*.py" = [
"S101", # asserts allowed in tests...
"FBT", # don"t care about booleans as positional arguments in tests
"INP001", # no implicit namespace
"D", # don"t care about documentation in tests
"S603", # `subprocess` call: check for execution of untrusted input
"PLR2004", # Magic value used in comparison, consider replacing with a constant variable
]
89 changes: 72 additions & 17 deletions src/devpi_process/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""Devpi PyPI to test with."""
from __future__ import annotations

import random
Expand All @@ -9,50 +10,85 @@
from pathlib import Path
from subprocess import PIPE, Popen, run
from threading import Thread
from types import TracebackType
from typing import IO, Iterator, Sequence, cast
from typing import IO, TYPE_CHECKING, Iterator, Sequence, cast

from ._version import __version__

if TYPE_CHECKING:
from types import TracebackType


def _check_call(cmd: list[str]) -> None:
run(cmd, check=True, capture_output=True)
run(cmd, check=True, capture_output=True) # noqa: S603


class Index:
"""Index."""

def __init__(self, base_url: str, name: str, user: str, client_cmd_base: list[str]) -> None:
"""
Create index.
:param base_url: base url
:param name: name for the index server
:param user: the username to use
:param client_cmd_base:
"""
self._client_cmd_base = client_cmd_base
self._server_url = base_url
self.name = name
self.user = user

@property
def url(self) -> str:
""":return: the URL to the index server"""
return f"{self._server_url}/{self.name}/+simple/"

def use(self) -> None:
_check_call(self._client_cmd_base + ["use", f"{self.user}/{self.name}"])
"""Use this index server."""
_check_call([*self._client_cmd_base, "use", f"{self.user}/{self.name}"])

def upload(self, *files: Path) -> None:
"""
Upload packages to the index.
:param files: the files to upload
"""
cmd = self._client_cmd_base + ["upload", "--index", self.name] + [str(i) for i in files]
_check_call(cmd)

def __repr__(self) -> str:
""":return: repr of the index"""
return f"{self.__class__.__name__}(url={self.url})"


class IndexServer:
def __init__(self, path: Path, with_root_pypi: bool = False, start_args: Sequence[str] | None = None) -> None:
"""A PyPI index server locally."""

def __init__(
self,
path: Path,
with_root_pypi: bool = False, # noqa: FBT001, FBT002
start_args: Sequence[str] | None = None,
) -> None:
"""
Create the local index server.
:param path: the path where to host files
:param with_root_pypi: access to upstream PyPI
:param start_args: additional arguments to start the server
"""
self.path = path
self._with_root_pypi = with_root_pypi
self._start_args: Sequence[str] = [] if start_args is None else start_args

self.host, self.port = "localhost", _find_free_port()
self._passwd = "".join(random.choices(string.ascii_letters, k=8))
self._passwd = "".join(random.choices(string.ascii_letters, k=8)) # noqa: S311

scripts_dir = sysconfig.get_path("scripts")
if scripts_dir is None:
raise RuntimeError("could not get scripts folder of host interpreter") # pragma: no cover
msg = "could not get scripts folder of host interpreter" # pragma: no cover
raise RuntimeError(msg) # pragma: no cover

def _exe(name: str) -> str:
return str(Path(scripts_dir) / f"{name}{'.exe' if sys.platform == 'win32' else ''}")
Expand All @@ -70,9 +106,11 @@ def _exe(name: str) -> str:

@property
def user(self) -> str:
""":return: username of the index server"""
return "root"

def __enter__(self) -> IndexServer:
""":return: start the index server"""
self._create_and_start_server()
self._setup_client()
return self
Expand All @@ -89,7 +127,7 @@ def _create_and_start_server(self) -> None:
# 2. start the server
cmd = [self._server, "--serverdir", server_at, "--port", str(self.port)]
cmd.extend(self._start_args)
self._process = Popen(cmd, stdout=PIPE, universal_newlines=True)
self._process = Popen(cmd, stdout=PIPE, universal_newlines=True) # noqa: S603
stdout = self._drain_stdout()
for line in stdout: # pragma: no branch # will always loop at least once
if "serving at url" in line:
Expand All @@ -108,42 +146,59 @@ def _drain_stdout(self) -> Iterator[str]:
stdout = cast(IO[str], process.stdout)
while True:
if process.poll() is not None: # pragma: no cover
print(f"devpi server with pid {process.pid} at {self._server_dir} died")
print(f"devpi server with pid {process.pid} at {self._server_dir} died") # noqa: T201
break
yield stdout.readline()

def _setup_client(self) -> None:
"""create a user on the server and authenticate it"""
"""Create a user on the server and authenticate it."""
self._client_dir.mkdir(exist_ok=True)
base = ["--clientdir", str(self._client_dir)]
_check_call([self._client, "use"] + base + [self.url])
_check_call([self._client, "login"] + base + [self.user, "--password", self._passwd])
_check_call([self._client, "use", *base, self.url])
_check_call([self._client, "login", *base, self.user, "--password", self._passwd])

def create_index(self, name: str, *args: str) -> Index:
"""
Create an index on the server.
:param name: with name
:param args: additional arguments
:return: the created index
"""
if name in self._indexes: # pragma: no cover
raise ValueError(f"index {name} already exists")
msg = f"index {name} already exists"
raise ValueError(msg)
base = [self._client, "--clientdir", str(self._client_dir)]
_check_call(base + ["index", "-c", name, *args])
_check_call([*base, "index", "-c", name, *args])
index = Index(f"{self.url}/{self.user}", name, self.user, base)
self._indexes[name] = index
return index

def __exit__(
self,
exc_type: type[BaseException] | None, # noqa: U100
exc_val: BaseException | None, # noqa: U100
exc_tb: TracebackType | None, # noqa: U100
exc_type: type[BaseException] | None,
exc_val: BaseException | None,
exc_tb: TracebackType | None,
) -> None:
"""
Stop the index server.
:param exc_type:
:param exc_val:
:param exc_tb:
"""
if self._process is not None: # pragma: no cover # defend against devpi startup fail
self._process.terminate()
if self._stdout_drain is not None and self._stdout_drain.is_alive(): # pragma: no cover # devpi startup fail
self._stdout_drain.join()

@property
def url(self) -> str:
""":return: url to the index server"""
return f"http://{self.host}:{self.port}"

def __repr__(self) -> str:
""":return: repr of the index server"""
return f"{self.__class__.__name__}(url={self.url}, indexes={list(self._indexes)})"


Expand Down
Loading

0 comments on commit 9866ca5

Please sign in to comment.