Coding Standards¶
Code Style¶
- Python 3.12+ syntax and features
- PEP 8, enforced by
ruff(100-character line length, seepyproject.toml) - Type hints on all functions and methods
- Google-style docstrings for all public classes and functions
- Meaningful test coverage — the project targets 80%+ (see
CLAUDE.mdfor current per-module numbers)
Formatting and Linting¶
ruff handles both formatting and linting; pre-commit also runs mypy.
# Format code
task lint
# Check formatting without modifying files
task lint:check
# Run all pre-commit hooks (ruff format, mypy, trailing whitespace, etc.)
task pre-commit
# Run ruff directly if you need more control
uv run ruff check src tests
uv run ruff format src tests
Docstrings¶
- Google-style, as configured in
[tool.ruff.lint.pydocstyle] - Document
Args,Returns, andRaisesfor public functions - Include a short usage example in the docstring where it clarifies behavior non-obviously — don't restate what the signature already says
Writing Tests¶
- Unit tests live in
tests/unit/, integration tests intests/integration/(seetests/conftest.pyfor shared fixtures) - Use descriptive test names that describe the scenario, not just the function under test
- Cover both the success path and the relevant failure/edge cases
- Mock external dependencies (SSH via
pexpect, filesystem operations) — seetests/unit/test_gear.pyfor the established mocking patterns
Example:
def test_config_validation_success():
"""Test successful configuration validation."""
config_data = {
"fetch": {
"test_task": {
"label": "test",
"summary": "Test task",
"host": "user@host",
"source": "/remote/path/",
"target": "./local/path/",
"options": ["-av"],
"enabled": True,
}
}
}
config = FnbConfig.model_validate(config_data)
assert len(config.fetch) == 1
assert config.fetch["test_task"].label == "test"
A pitfall worth knowing
fetcher.py, backuper.py, and reader.py import gear/env functions with
from fnb.gear import run_rsync, verify_directory — a from-import binds
the name at import time. Patching fnb.gear.run_rsync in a test will
not affect code that already imported it this way; patch the
consumer's namespace instead (e.g. fnb.fetcher.run_rsync). See
tests/integration/test_integration.py's integrated_mocks fixture for
the pattern.