wake-alarm/pyproject.toml
Krzysztof kuhy Rudnicki 5117f5a0c9 Latest stable dependencies, and the gate that keeps them there
**The two shared Python libraries were four minor versions behind and the gate
could not see it.** They are PEP 508 direct references
(`gatelock @ git+...@gatelock-v0.4.1`), which parsed as unpinned PyPI packages
that do not exist on PyPI -- an undeterminable answer, not a stale one.
utils@7a682a4 teaches the gate that shape; this repo is the first to benefit:
gatelock v0.4.1 -> v0.7.2, crdt-sync v0.5.1 -> v0.9.1. The system install was
already on 0.7.1/0.9.0, so only the manifests were adrift.

Python side: bandit 1.7 -> 1.9.4, mypy 1 -> 2.3.1, pylint 3 -> 4.0.7,
pytest 8 -> 9.1.1, pytest-cov 4 -> 7.1.0, pytest-randomly 3 -> 4.1.0,
ruff 0.8 -> 0.16.5, python-kasa 0.7 -> 0.10.2, and the rest exact-pinned.
Five majors and not one lint or type finding: `scripts/ci_mirror.sh` builds a
clean venv from requirements.txt and runs the whole CI set, 465 tests at 100%
branch coverage.

phone_app takes crdt_sync_dart v0.11.1 and the flutter_secure_storage 11 chain
(compileSdk 37 / AGP 9.3.2 / Gradle 9.7.1). 79 tests green, analyzer clean,
release APK builds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRykabMwPgDzFHcARoqpaK
2026-08-28 14:56:16 +02:00

210 lines
6.4 KiB
TOML

[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"
[project]
name = "wake-alarm"
version = "1.0.0"
description = "Phone-driven wake alarm with fan ramp and smart-plug control"
requires-python = ">=3.10"
dependencies = [
"gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.7.2#subdirectory=gatelock",
# Firebase transport for the alarm read. _sync.py imports it lazily and
# falls back to its stdlib GitHub path if the import fails, so a broken
# install degrades the read instead of killing the per-minute trigger.
"crdt-sync @ git+https://github.com/kuhyx/utils@crdt-sync-v0.9.1#subdirectory=crdt-sync",
"python-kasa==0.10.2",
]
# Package discovery must be explicit: phone_app/ is a Flutter app, not a Python
# package, but setuptools' flat-layout autodiscovery still sees it as a second
# top-level package and refuses to build ("Multiple top-level packages
# discovered in a flat-layout"). That aborted install.sh at step 1 — which is
# how /usr/local/bin/shutdown silently went stale.
[tool.setuptools.packages.find]
include = ["wake_alarm*"]
[tool.ruff]
target-version = "py310"
include = ["*.py", "**/*.py"]
exclude = [".git", ".venv", "__pycache__", "build", "dist", ".eggs"]
[tool.ruff.lint]
select = ["ALL"]
ignore = [
"D203",
"D213",
"COM812",
"ISC001",
"S603",
# urllib "audit URL open for permitted schemes". _sync.py builds its one
# URL from module constants (never from remote input) and asserts
# request.type == "https" before sending the token. Ignored repo-wide
# rather than per-line because inline `# noqa` is blocked by a
# pre-commit hook; same rationale as S603 above.
"S310",
]
fixable = ["ALL"]
unfixable = []
[tool.ruff.lint.per-file-ignores]
"**/tests/**/*.py" = ["ARG", "D", "PLC0415", "PLR2004", "S101", "SLF001"]
"**/test_*.py" = ["ARG", "D", "PLC0415", "PLR2004", "S101", "SLF001"]
[tool.ruff.lint.pydocstyle]
convention = "google"
[tool.ruff.lint.isort]
force-single-line = false
force-sort-within-sections = true
known-first-party = ["wake_alarm"]
[tool.ruff.lint.flake8-quotes]
docstring-quotes = "double"
inline-quotes = "double"
[tool.ruff.lint.flake8-tidy-imports]
ban-relative-imports = "all"
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "auto"
docstring-code-format = true
[tool.mypy]
python_version = "3.10"
strict = true
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
disallow_untyped_decorators = true
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_no_return = true
warn_unreachable = true
disallow_any_unimported = true
disallow_any_explicit = false
disallow_any_generics = true
disallow_subclassing_any = true
strict_equality = true
extra_checks = true
ignore_missing_imports = true
show_error_codes = true
color_output = true
exclude = [".venv/"]
[tool.pylint.main]
# The repo root on the path, so a file outside the package (scripts/) can be
# linted on its own. pylint otherwise infers the path from the files it is
# given, so a script resolved its package imports only when a package file
# happened to be in the same batch -- passing or failing by what else was
# staged.
init-hook = "import sys; sys.path.insert(0, '.')"
analyse-fallback-blocks = true
persistent = true
jobs = 0
py-version = "3.10"
# "tests" is a basename match: test suites intentionally use patterns
# (protected-access, magic-value comparisons) that don't apply to source
# code, matching testsAndMisc's pylint scope (tests are linted by ruff, not
# pylint there either).
ignore = [".venv", "__pycache__", "tests"]
ignore-patterns = [".*\\.pyi$"]
[tool.pylint.messages_control]
# Enable all checks by disabling disable
enable = "all"
# Globally disabled checks. Each is either a stylistic preference that conflicts
# with deliberate, clearer code, or a structural false positive that cannot be
# rewritten without harming readability. Everything else stays at max strictness.
disable = [
# use-implicit-booleaness family (C1803/C1804/C1805): pylint wants
# `not x` / `not s` instead of `x == 0` / `s == ""`. Explicit comparisons
# against 0 and "" state numeric/string intent more clearly than truthiness
# (and are not equivalent when the value may be None), so we keep them.
"use-implicit-booleaness-not-comparison",
"use-implicit-booleaness-not-comparison-to-string",
"use-implicit-booleaness-not-comparison-to-zero",
# consider-using-with (R1732): several subprocess.Popen calls are
# intentionally fire-and-forget — the process must outlive the calling
# scope and is polled/killed later, so a `with` block is wrong here.
"consider-using-with",
]
[tool.pylint.design]
min-public-methods = 0
max-module-lines = 1000
max-attributes = 10
[tool.pylint.typecheck]
# unittest.mock.MagicMock generates assertion/introspection methods at runtime.
# wave.open(path, "wb") returns a Wave_write, but pylint's stdlib stub infers the
# read-mode Wave_read overload and wrongly reports its setter/writer methods as
# missing — list them so the write API is recognised.
generated-members = [
".*\\.assert_called_once_with",
".*\\.assert_called_once",
".*\\.assert_called",
".*\\.assert_not_called",
".*\\.assert_any_call",
".*\\.call_args",
".*\\.call_args_list",
".*\\.call_count",
".*\\.setnchannels",
".*\\.setsampwidth",
".*\\.setframerate",
".*\\.writeframes",
".*\\.writeframesraw",
]
[tool.bandit]
exclude_dirs = ["tests", ".venv"]
[tool.pytest.ini_options]
testpaths = ["wake_alarm/tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"-v",
"--strict-markers",
"--strict-config",
"-ra",
"--cov=wake_alarm",
"--cov-branch",
"--cov-report=term-missing",
"--cov-report=lcov",
]
filterwarnings = [
"error",
"ignore::DeprecationWarning",
"default::pytest.PytestUnraisableExceptionWarning",
]
[tool.coverage.run]
source = ["wake_alarm"]
branch = true
omit = [
"*/__pycache__/*",
"*/tests/*",
"*/.venv/*",
]
[tool.coverage.report]
fail_under = 100
show_missing = true
skip_covered = false
exclude_lines = [
"pragma: no cover",
"raise NotImplementedError",
"raise AssertionError",
"if TYPE_CHECKING:",
'if __name__ == "__main__":',
]
partial_branches = ["pragma: no branch"]