diet-guard/pyproject.toml
Krzysztof kuhy Rudnicki b77145010c Stop asking for meals on a shared free day
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>
2026-09-07 15:22:38 +02:00

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"]