style: adopt ruff (format + lint) at 88, replacing black/isort/flake8

Format-only: Python files are AST-identical apart from import order.
pre-commit end-of-file/trailing-whitespace fixes applied repo-wide.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
golem
2026-10-04 20:39:30 -06:00
co-authored by Claude Opus 5.5
parent a6e09a5796
commit bca4c38f98
19 changed files with 63 additions and 88 deletions
-11
View File
@@ -1,11 +0,0 @@
[flake8]
max-line-length=120
ignore =
E121,
E123,
E126,
E226,
E24,
E704,
W605
exclude = ./tests
-1
View File
@@ -3,4 +3,3 @@ dist
*.egg-info
__pycache__
.idea
+7 -20
View File
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
rev: v6.0.0
hooks:
- id: check-yaml
- id: check-added-large-files
@@ -9,23 +9,10 @@ repos:
- id: end-of-file-fixer
- id: trailing-whitespace
# isort -- sorts imports
- repo: https://github.com/timothycrosley/isort
rev: 5.13.2
# ruff replaces black, isort and flake8. Keep rev equal to the ruff pin in pyproject.toml.
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.16.10
hooks:
- id: isort
args: ["--profile", "black", "--filter-files"]
# Flake8
- repo: https://github.com/pycqa/flake8
rev: '7.0.0'
hooks:
- id: flake8
# Black
# Using this mirror lets us use mypyc-compiled black, which is about 2x faster
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 24.2.0
hooks:
- id: black
language_version: python3.8
- id: ruff-check
args: [--fix]
- id: ruff-format
+1 -2
View File
@@ -2,7 +2,7 @@
## [0.2.2] (2020-07-19)
Second release is focused on cleanup, documentation.
Second release is focused on cleanup, documentation.
* flake8, black, isort - all warnings and errors fixed.
* Documentation improved.
@@ -19,4 +19,3 @@ First release to PyPi.
* Config through ConfigObj ini. Define spec file and input config.ini
* Colored Logging
* Services can be defined and provided to classes
+4 -6
View File
@@ -9,22 +9,20 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Commands
```bash
pip install -e '.[dev]' pytest # dev extras = black, isort, flake8, pre-commit; pytest is NOT in the extras
pip install -e '.[dev]' pytest # dev extras = ruff, pre-commit; pytest is NOT in the extras
pytest tests # full suite from repo root (or `pytest .` from inside tests/)
pytest tests/cfg/test_cfg.py::TestConfig_e2e::test_uses_spec_as_defaults # single test
pytest tests -k "<keyword>"
black app_skellington
isort --profile black app_skellington
flake8 app_skellington # .flake8: max-line-length 120, tests/ excluded
ruff check --fix # lint + import sorting (config in pyproject.toml)
ruff format # formatter, line length 88
python -m build # sdist + wheel into dist/
python -m setuptools_scm # print the version the build will get
```
- **Versioning is tag-driven.** setuptools_scm uses `release-branch-semver` and derives the version from the latest `vX.Y.Z` git tag. It writes `app_skellington/_version.py` at build time. That file is tracked, but don't hand-edit it. `pip install -e .` also rewrites it, so `git restore` it rather than committing the churn. A release means tagging, then `python -m build`, then `twine upload dist/*`. The README covers the full publish steps.
- **Formatter config is inconsistent.** `pyproject.toml` has no `[tool.black]`, so black runs at its default width of 88. Only flake8 allows 120. The code on `main` is formatted at 88. The pre-commit black hook pins `language_version: python3.8`. It fails unless a 3.8 interpreter is on PATH (`.python-version` names 3.8.x for pyenv).
- **Lint is not currently clean on `main`.** black wants to reformat 2 files, isort flags 3, and flake8 reports W391 and W503. Keep unrelated reformatting out of feature diffs.
- **ruff** handles formatting (88 columns) and lint (`E`, `F`, `W`, `I`). E501 is ignored on purpose, since the formatter owns line length. Keep the `ruff==` pin in `pyproject.toml` equal to the `rev:` in `.pre-commit-config.yaml`.
- Tests cover only `cfg` (ConfigObj fixtures live in `tests/cfg/`) plus a CommandTree constructor smoke test. `tests/test_log.py` is empty.
## Python support policy
+3 -5
View File
@@ -4,13 +4,13 @@ import os
import appdirs
from ._util import ServiceNotFound, NoCommandSpecified, get_root_asset
from . import log
from .cfg import Config
from .cli import CommandTree
# Application scaffolding:
from ._bootstrap import _bootstrap_logger
from ._util import NoCommandSpecified, ServiceNotFound, get_root_asset
from .cfg import Config
from .cli import CommandTree
# These two variables affect the directory paths for
# config files and logging.
@@ -189,5 +189,3 @@ class ApplicationContainer:
def usage(self):
pass
# Applications need a default usage
+3 -3
View File
@@ -217,9 +217,9 @@ class Config:
try:
_bootstrap_logger.info("cfg - Validating config file against spec")
val = validate.Validator()
assert isinstance(
self._config_obj, configobj.ConfigObj
), "expecting configobj.ConfigObj, received %s" % type(self._config_obj)
assert isinstance(self._config_obj, configobj.ConfigObj), (
"expecting configobj.ConfigObj, received %s" % type(self._config_obj)
)
# NOTE(MG) copy arg below instructs configobj to use defaults from spec file
test_results = self._config_obj.validate(
val, copy=True, preserve_errors=True
+10 -11
View File
@@ -3,8 +3,8 @@ import inspect
import re
import sys
from ._util import NoCommandSpecified
from ._bootstrap import _bootstrap_logger
from ._util import NoCommandSpecified
# If explicit fail is enabled, any command with at least one unknown
# argument will be rejected entirely. If not enabled, unknown arguments
@@ -233,22 +233,21 @@ class CommandTree:
# the CommandTree with no SubMenu (submenu will be disabled
# in this case):
if self._cmd_tree_is_single_command:
assert (
self._cmd_tree_is_single_command is True
), "corrupt data structure in CommandMenu"
assert self._cmd_tree_is_single_command is True, (
"corrupt data structure in CommandMenu"
)
assert self._entries is None, "corrupt data structure in CommandMenu"
assert isinstance(
self._single_command, CommandEntry
), "corrupt data structure in CommandMenu"
assert isinstance(self._single_command, CommandEntry), (
"corrupt data structure in CommandMenu"
)
return self._single_command
# There is at least one submenu we need to go down:
else:
assert self._single_command is None, "corrupt data structure in CommandMenu"
assert (
self._cmd_tree_is_single_command is False
), "corrupt data structure in CommandMenu"
assert self._cmd_tree_is_single_command is False, (
"corrupt data structure in CommandMenu"
)
# Key or variable name used by argparse to store the submenu options
argparse_param = self.submenu_param # e.g.: submenu_root
@@ -160,7 +160,9 @@ def norm(src):
tree = ast.parse(src)
imports, rest = [], []
for node in tree.body:
(imports if isinstance(node, (ast.Import, ast.ImportFrom)) else rest).append(node)
(imports if isinstance(node, (ast.Import, ast.ImportFrom)) else rest).append(
node
)
for node in imports:
node.names = sorted(node.names, key=lambda a: (a.name, a.asname or ""))
return sorted(ast.dump(n) for n in imports), [ast.dump(n) for n in rest]
@@ -876,8 +878,7 @@ import pytest # add to the imports
@pytest.mark.skipif(not VERIFY_SCRIPT.exists(), reason="added with the release task")
def test_verify_script_covers_matrix():
...
def test_verify_script_covers_matrix(): ...
```
Run again. Expected: all pass, 1 skipped.
+16 -3
View File
@@ -33,13 +33,26 @@ documentation = "https://git-repos.zavage.net/Zavage-Software/app_skellington"
[project.optional-dependencies]
dev = [
"black",
"ruff==0.16.10",
"pre-commit",
"isort",
"flake8"
]
[tool.setuptools_scm]
version_file = "app_skellington/_version.py"
version_scheme = "release-branch-semver"
local_scheme = "node-and-date"
[tool.ruff]
line-length = 88
target-version = "py38"
extend-exclude = ["app_skellington/_version.py"]
[tool.ruff.lint]
# E/W = pycodestyle, F = pyflakes, I = isort: the old flake8 + isort coverage.
select = ["E", "F", "W", "I"]
# The formatter owns line length. Long strings and comments it can't wrap are allowed.
ignore = ["E501"]
[tool.ruff.lint.per-file-ignores]
# Placeholder smoke test. The characterization-tests plan rewrites it.
"tests/test_cli.py" = ["F841", "E712"]
-1
View File
@@ -1,3 +1,2 @@
[metadata]
description-file = README.md
-1
View File
@@ -15,4 +15,3 @@ Run test by keyword:
Run test by directory:
pytest <dirname>
-1
View File
@@ -2,4 +2,3 @@ root_option = root_option_val
[app]
sub_option = sub_option_val
-1
View File
@@ -2,4 +2,3 @@ root_option = string(max=255, default='def_string')
[app]
sub_option = string(max=255, default='def_sub')
-1
View File
@@ -2,4 +2,3 @@ root_option = root_option_val
[app]
sub_option = sub_option_val
-1
View File
@@ -3,4 +3,3 @@ int_option = integer(min=0, max=100)
[app]
sub_option = string(max=255, default='def_sub')
-1
View File
@@ -1,2 +1 @@
root_option = invalid(max=255, default='def_string')
+15 -15
View File
@@ -32,18 +32,18 @@ def sample_invalid_configspec_filepath():
class TestConfig_e2e:
def test_allows_reading_ini_and_no_spec(self, sample_configini_filepath):
cfg = Config(configini_filepath=sample_configini_filepath)
assert (
cfg["root_option"] == "root_option_val"
), "expecting default from config.spec (didnt get)"
assert (
cfg["app"]["sub_option"] == "sub_option_val"
), "expecting default for sub option"
assert cfg["root_option"] == "root_option_val", (
"expecting default from config.spec (didnt get)"
)
assert cfg["app"]["sub_option"] == "sub_option_val", (
"expecting default for sub option"
)
def test_allows_reading_spec_and_no_ini(self, sample_configspec_filepath):
cfg = Config(configspec_filepath=sample_configspec_filepath)
assert (
cfg["root_option"] == "def_string"
), "expecting default from config.spec (didnt get)"
assert cfg["root_option"] == "def_string", (
"expecting default from config.spec (didnt get)"
)
# NOTE(MG) Changed the functionality to not do it this way.
# def test_constructor_fails_with_invalid_spec(
@@ -71,9 +71,9 @@ class TestConfig_e2e:
assert cfg["root_option"] == "newval"
cfg["app"]["sub_option"] = "another_new_val"
assert (
cfg["app"]["sub_option"] == "another_new_val"
), "expecting default for sub option"
assert cfg["app"]["sub_option"] == "another_new_val", (
"expecting default for sub option"
)
def test_can_set_option_without_config(self):
cfg = Config()
@@ -86,7 +86,7 @@ class TestConfig_e2e:
def test_uses_spec_as_defaults(self, sample_configspec_filepath):
cfg = Config(configspec_filepath=sample_configspec_filepath)
assert (
cfg["root_option"] == "def_string"
), "expecting default from config.spec (didnt get)"
assert cfg["root_option"] == "def_string", (
"expecting default from config.spec (didnt get)"
)
assert cfg["app"]["sub_option"] == "def_sub", "expecting default for sub option"
-1
View File
@@ -1,4 +1,3 @@
[pytest]
filterwarnings =
ignore::DeprecationWarning