mirror of
https://github.com/kuhyx/diet-guard.git
synced 2026-11-10 19:27:14 +01:00
Third app onto freedays (~/utils/freedays), the shared pool of pre-declared days when every gate app stands down. due_slots() returns nothing on a free day, so no slot is ever due, the lock never opens and nothing is said about the missing meals. Read from one local file, never the network, so this cannot hang the systemd-fired gate -- and an unreadable pool reads as "not free", which means the normal rules apply rather than the gate quietly switching itself off. Tested explicitly: fail closed, not open. The conftest redirect is load-bearing. The pool lives under ~/.local/share, outside every diet_guard path the suite already isolates, so without it a real free day on this machine would fail every "the gate is due" test here for a reason that looks nothing like the cause. gatelock moves v0.7.2 -> v0.7.3, and the pylint hook's own pins (which had drifted further, to gatelock v0.7.1 and crdt-sync v0.6.0) come with it. Not cosmetic: pip refuses two different direct URLs for one distribution, so any stale pin alongside freedays -- which requires v0.7.3 -- is an unresolvable environment rather than a warning. requirements.txt also picks up pylint 4.0.8, pytest-randomly 5.0.0 and ruff 0.16.6, which the freshness gate flagged once this file was touched. 1043 tests pass at 100% coverage across the pytest-randomly major bump. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
201 lines
6.0 KiB
TOML
201 lines
6.0 KiB
TOML
[project]
|
|
name = "diet-guard"
|
|
version = "1.0.0"
|
|
description = "Tkinter/systemd log-to-unlock meal gate with calorie/macro budget tracking"
|
|
requires-python = ">=3.14"
|
|
dependencies = [
|
|
"gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.7.3#subdirectory=gatelock",
|
|
"crdt-sync @ git+https://github.com/kuhyx/utils@crdt-sync-v0.9.1#subdirectory=crdt-sync",
|
|
"freedays @ git+https://github.com/kuhyx/utils@freedays-v0.1.0#subdirectory=freedays",
|
|
"requests==2.34.2",
|
|
]
|
|
|
|
# Optional extras. The `mcp` group is ONLY needed by the MCP server
|
|
# (diet_guard._mcp), which Claude Code spawns via a dedicated venv (see
|
|
# scripts/setup_mcp.sh / .mcp.json). It is deliberately NOT a core dependency
|
|
# so the systemd/CLI system-python path stays dependency-light and never has
|
|
# to import the MCP SDK.
|
|
[project.optional-dependencies]
|
|
mcp = ["mcp==2.1.1"]
|
|
|
|
# Without this, setuptools' flat-layout auto-discovery treats the Flutter
|
|
# app/ directory as a second top-level package and refuses to build.
|
|
[tool.setuptools.packages.find]
|
|
include = ["diet_guard*"]
|
|
|
|
[tool.ruff]
|
|
target-version = "py314"
|
|
include = ["*.py", "**/*.py"]
|
|
exclude = [".git", ".venv", "__pycache__", "build", "dist", ".eggs"]
|
|
|
|
[tool.ruff.lint]
|
|
select = ["ALL"]
|
|
ignore = [
|
|
"D203",
|
|
"D213",
|
|
"COM812",
|
|
"ISC001",
|
|
"S603",
|
|
]
|
|
fixable = ["ALL"]
|
|
unfixable = []
|
|
|
|
[tool.ruff.lint.per-file-ignores]
|
|
"**/tests/**/*.py" = ["ARG", "D", "PLC0415", "PLR2004", "S101", "S107", "SLF001"]
|
|
"**/test_*.py" = ["ARG", "D", "PLC0415", "PLR2004", "S101", "S107", "SLF001"]
|
|
|
|
[tool.ruff.lint.pydocstyle]
|
|
convention = "google"
|
|
|
|
[tool.ruff.lint.isort]
|
|
force-single-line = false
|
|
force-sort-within-sections = true
|
|
known-first-party = ["diet_guard"]
|
|
|
|
[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.14"
|
|
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.14"
|
|
# "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.similarities]
|
|
# _foodbank's BankRecord and _mcp's response dict both list the same five
|
|
# nutrition fields (kcal/protein_g/carbs_g/fat_g/grams) because they mirror the
|
|
# same domain schema, then diverge (desc/count vs source). Raise the threshold
|
|
# so pylint doesn't flag this intentional structural overlap as duplicate-code.
|
|
min-similarity-lines = 8
|
|
|
|
[tool.pylint.typecheck]
|
|
generated-members = [
|
|
".*\\.assert_called_once_with",
|
|
".*\\.assert_called_once",
|
|
".*\\.assert_called",
|
|
".*\\.assert_not_called",
|
|
".*\\.assert_any_call",
|
|
".*\\.call_args",
|
|
".*\\.call_args_list",
|
|
".*\\.call_count",
|
|
]
|
|
|
|
[tool.bandit]
|
|
exclude_dirs = ["tests", ".venv"]
|
|
|
|
[tool.pytest.ini_options]
|
|
testpaths = ["diet_guard/tests"]
|
|
python_files = ["test_*.py", "*_test.py"]
|
|
python_classes = ["Test*"]
|
|
python_functions = ["test_*"]
|
|
addopts = [
|
|
"-v",
|
|
"--strict-markers",
|
|
"--strict-config",
|
|
"-ra",
|
|
"--cov=diet_guard",
|
|
"--cov-branch",
|
|
"--cov-report=term-missing",
|
|
"--cov-report=lcov",
|
|
]
|
|
filterwarnings = [
|
|
"error",
|
|
"ignore::DeprecationWarning",
|
|
"default::pytest.PytestUnraisableExceptionWarning",
|
|
]
|
|
|
|
[tool.coverage.run]
|
|
source = ["diet_guard"]
|
|
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"]
|