[project] name = "spendif-ai" version = "2026.11.0" description = "Unified personal finance ledger with hybrid LLM pipeline" readme = "README.md" license = { file = "LICENSE" } authors = [{ name = "Luigi Corsaro" }] # Upper bound widened to 3.15 on 2026-09-22: Arch Linux has shipped Python # 3.14 since August, so a cap at <3.14 made the app uninstallable on every # rolling distribution - the venv is created against the system interpreter. # The CI matrix runs the suite on 3.14 so this stays a checked claim. requires-python = ">=3.12,<3.15" dependencies = [ # Core data "pandas>=2.2", "openpyxl>=3.1", # pandas reads legacy .xls through xlrd; the upload accepts that format. "xlrd>=2.0.1", "chardet>=5.0", # LLM backends (direct SDKs — no LangChain) "openai>=1.30", "anthropic>=0.28", "requests>=2.31", # Version floor is the first one the prebuilt wheel indexes below carry # for every platform we ship. See [tool.uv.sources]. "llama-cpp-python>=0.3.35", # The app's own ears (spec 32, 3): whisper.cpp, MIT. Prebuilt wheels for # every platform we ship; none for Intel macOS, which we do not ship. "pywhispercpp>=1.5.1; sys_platform != 'darwin' or platform_machine == 'arm64'", # Database "sqlalchemy>=2.0", "alembic>=1.13", # Validation "pydantic>=2.0", "pyyaml>=6.0", # Configuration "python-dotenv>=1.0", # UI "streamlit>=1.35", "plotly>=5.20", # Reports "jinja2>=3.1", # REST API "fastapi>=0.111", "uvicorn[standard]>=0.29", "python-multipart>=0.0.9", "httpx>=0.27", # Security pin: idna<3.15 carries CVE-2026-45409 (pip-audit gate). # Floor declared here even though idna is a transitive dep of httpx/requests # so a fresh `uv lock` resolves to >=3.15. "idna>=3.15", # Security pins (transitive deps of streamlit) — pip-audit gate. # pillow<12.3.0 carries PYSEC-2026-2253..2257 / 3451..3454; 12.3.0 carries # CVE-2026-112395, 112397, 112400, 112404, 112895..112897, 112899 (fixed in # 13.0.0, which needs streamlit>=1.66). # gitpython<3.1.58 carries GHSA-9rj7-rf2p-w77r, GHSA-4gmw-gg2m-w46p, # CVE-2026-76217, GHSA-wvpp-8hx9-p66j, GHSA-jm78-9fvv-mhgr (all fixed in 3.1.58); # 3.1.59 carries PYSEC-2026-3982, PYSEC-2026-3983, PYSEC-2026-3984 (fixed in 3.1.60); # 3.1.62 carries CVE-2026-114145 (fixed in 3.2.1). "pillow>=13.0.0", "gitpython>=3.2.1", # certifi<2026.10.19 carries PYSEC-2026-4320 (pip-audit gate); transitive # dep of requests/httpx, floor declared so a fresh `uv lock` resolves to it. "certifi>=2026.10.19", # Security pin: cryptography<50.0.0 carries PYSEC-2026-3552 (pip-audit gate). # Transitive dep of azure-identity/msal; floor declared here so a fresh # `uv lock` resolves to >=50.0.0. "cryptography>=50.0.0", # Security pin: opentelemetry-api<1.45.1 carries CVE-2026-114603 (pip-audit # gate). Transitive dep of azure-ai-ml through azure-monitor-opentelemetry. "opentelemetry-api>=1.45.1", # Security pin: filelock<4.2.1 carries CVE-2026-112907, 112913, 112940 # (pip-audit gate). Transitive dep of huggingface-hub. "filelock>=4.2.1", # Testing "pytest>=8.0", "pytest-cov>=5.0", "huggingface-hub>=1.8.0", "azure-ai-ml>=1.32.0", "azure-identity>=1.25.3", ] [project.optional-dependencies] desktop = [ "pywebview>=5.0", "pyinstaller>=6.0", # GUI backend for pywebview: # - macOS uses Cocoa (built into pyobjc) — no extra dep # - Windows uses WebView2 (system component) — no extra dep # - Linux uses GTK. We do NOT pip-install PyGObject/pycairo here — # instead the .deb / .rpm depends on system `python3-gi` and # `python3-cairo`, and launch.sh creates the venv with # `--system-site-packages` so those system modules are visible # inside the venv. Reasons: # • pycairo has no Linux wheels on PyPI — every pip install # triggers a source build that needs libcairo2-dev. # • PyGObject wheels exist but require matching system libs # (gir1.2-webkit2-4.1, libgirepository) which we already # depend on from the .deb side — there is no reason to # install another copy via pip. ] # ───────────────────────────────────────────────────────────────────────────── # llama-cpp-python: install a prebuilt wheel, never compile on the user machine # # PyPI ships this package as a source distribution only, so `uv sync` compiled # it on every machine that installed Spendif.ai. That needs a full C/C++ # toolchain and CMake, which a normal desktop does not have: on 2026-09-22 it # was the cause of three failed installations out of four (a Ubuntu without # g++, a Windows without the Visual Studio Build Tools) and of a 3-to-8-minute # first launch where it did work. # # Linux and Windows therefore take the prebuilt CPU wheels that upstream # publishes. macOS deliberately does NOT: # • the macOS wheel is built for CPU, and taking it would silently drop # Metal inference on Apple Silicon, our fastest platform; # • the `metal` wheel published upstream for 0.3.35 is a malformed zip # (uv: "invalid Zip archive: Invalid local file header"; Python's more # tolerant zipfile opens it, which is how a broken file gets published); # • and no macOS user ever compiles it anyway - the DMG is built by CI with # PyInstaller, so the compilation happens on a runner, once, with the # toolchain Xcode already provides. # # On Linux and Windows the CPU wheel means an AMD GPU stays unused: there is # no prebuilt Vulkan or ROCm wheel upstream. Building one is a separate piece # of work; until then the app says so instead of reporting "no GPU". # # The index is declared `explicit`, so ONLY this package is ever fetched from # it and no other dependency can be shadowed by a same-named file published # there. # # INTEGRITY: unlike PyPI, this index publishes no checksums, so uv.lock # records its wheel URLs WITHOUT a sha256 - the only entries in the whole # lockfile that have none. The version is pinned, the bytes are not: a GitHub # release asset can be replaced in place by whoever owns it. The checksums we # observed are therefore committed in packaging/llama_wheel_hashes.txt and # checked by tools/check_llama_wheels.py, so a swap upstream turns CI red # instead of reaching a user. # ───────────────────────────────────────────────────────────────────────────── [[tool.uv.index]] name = "llama-cpp-cpu" url = "https://abetlen.github.io/llama-cpp-python/whl/cpu/" explicit = true [tool.uv.sources] llama-cpp-python = [ { index = "llama-cpp-cpu", marker = "sys_platform != 'darwin'" }, ] # The platforms we actually ship: arm64 DMG, x86_64 and arm64 .deb, x86_64 MSIX # and RPM. Declaring them keeps the lock honest - a resolution that cannot be # satisfied on one of these fails here, at lock time, instead of on a user # machine. Intel Macs are deliberately absent (see getting-started.html). [tool.uv] environments = [ "sys_platform == 'darwin' and platform_machine == 'arm64'", "sys_platform == 'linux'", "sys_platform == 'win32'", ] [tool.pytest.ini_options] markers = [ "llm: tests requiring a running LLM backend (Ollama/OpenAI)", "siri_files: the test reads the real Siri folders, by design (only the path builder)", ] addopts = "-m 'not llm'" testpaths = ["tests"] [tool.coverage.run] omit = [ "app.py", # Streamlit entrypoint — top-level st.* calls, not importable in pytest "desktop/*", # GUI launcher — requires pywebview display, not testable in headless CI "quarantine/*", # archived/dead code "benchmark/*", # standalone benchmark scripts "tools/*", # dev tooling ] [tool.bandit] exclude_dirs = ["tests", "tools", "benchmark", "scripts", "quarantine"] skips = ["B101"] # assert used in tests / dev tooling