[project] name = "morsl" # Build scaffolding only, NOT the shipped version. The Dockerfile does # `pip install .` in the builder purely to resolve dependencies into the venv, # and a project table needs a version for that to work — it then deletes the # package. The version users see is MORSL_VERSION, set from the git tag via # APP_VERSION at image build time. Do not read this value for anything. version = "0.0.0" description = "Autocreate menus from Tandoor API" requires-python = ">=3.12" license = "MIT" authors = [{ name = "Cron", email = "cron@featurecreep.dev" }] keywords = ["meal-planning", "tandoor", "menu-generator", "docker", "self-hosted"] dependencies = [ "requests==2.34.2", "pulp==2.9.0", "tzlocal==5.4.5", # zoneinfo reads the system tz database first and falls back to this # package. Recent Debian/Ubuntu moved legacy names (US/Eastern) into an # optional tzdata-legacy package, so without it TZ=US/Eastern silently # resolves to UTC depending on the base image. "tzdata==2026.6", "fastapi==0.162.0", "uvicorn[standard]==0.54.0", "pydantic-settings==2.16.2", "python-multipart==0.0.33", "apscheduler==3.11.3", "defusedxml==0.7.1", "Pillow==13.0.0", "cairosvg==2.9.1", "python-slugify==9.1.3", "cachetools==7.2.8", "py-svg-hush==0.3.0", "httpx==0.28.1", ] [project.optional-dependencies] dev = [ "ruff==0.17.3", # linter, formatter, import sort "mypy==2.4.1", "pre-commit==4.7.0", "colorlog==6.12.0", "pytest==9.2.0", "pytest-cov==7.1.0", "pytest-asyncio==1.4.1.post1", ] [tool.setuptools.packages.find] include = ["morsl", "morsl.*"] [tool.ruff] fix = true line-length = 99 target-version = "py312" # ruff >= 0.16 formats Python blocks inside Markdown. The design docs use # elided snippets (`...` for omitted bodies), which the formatter rewrites — # so keep prose out of its reach. extend-exclude = ["*.md"] [tool.ruff.lint] select = [ "E", # pycodestyle errors "W", # pycodestyle warnings "F", # pyflakes "I", # isort "C", # flake8-comprehensions "B", # flake8-bugbear "ANN", # flake8 type annotations "RUF", # ruff-specific rules "S", # flake8-bandit (security) "PT", # flake8-pytest-style "SIM", # flake8-simplify ] ignore = [ "ANN001", "ANN002", "ANN003", "ANN201", "ANN204", "ANN202", "ANN401", "RUF012", "C901", # complexity — un-suppress after Phase 5 refactoring "S101", # assert in non-test code — used in solver debug "S104", # binding to 0.0.0.0 — intentional in Docker "S311", # random — used for LP variety, not security ] fixable = ["ALL"] external = ["broad-except", "direct-service"] # custom architectural lint markers [tool.ruff.lint.per-file-ignores] "tests/**" = ["ANN", "S"] "morsl/app/api/routes/*.py" = ["B008"] # FastAPI Depends() in defaults "morsl/app/api/dependencies.py" = ["B008"] "morsl/app/main.py" = ["B008"] [tool.ruff.lint.flake8-annotations] suppress-dummy-args = true mypy-init-return = true [tool.ruff.format] quote-style = "double" [tool.pytest.ini_options] testpaths = ["tests"] python_files = "test_*.py" python_functions = "test_*" addopts = ["-v", "--strict-markers", "--tb=short"] markers = [ "slow: marks tests as slow (deselect with '-m \"not slow\"')", "integration: marks tests that require live Tandoor API", ] [tool.mypy] python_version = "3.12" strict = true ignore_missing_imports = true [tool.ruff.lint.isort] required-imports = ["from __future__ import annotations"] [tool.semantic_release] # Used only to COMPUTE the next version in push.yml's auto-release job; psr # never writes, commits or tags here. The tag is created by that job and fed to # the image build as APP_VERSION, so the git tag stays the single source. commit_parser = "conventional" tag_format = "v{version}" major_on_zero = false allow_zero_version = true [tool.semantic_release.commit_parser_options] minor_tags = ["feat"] # "deps" is patch-producing: pip, npm and docker updates all reach a user here. # allowed_tags must be listed explicitly — its default is computed from the # class-level defaults, so overriding patch_tags alone leaves "deps" # unrecognised and silently drops every such commit. patch_tags = ["fix", "perf", "deps"] allowed_tags = [ "feat", "fix", "perf", "deps", "build", "chore", "ci", "docs", "style", "refactor", "test", ] [tool.semantic_release.branches.main] match = "main" prerelease = false