mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
## Description Rust fastembed enables ORT C API 24, but the Python dependency allowed ONNX Runtime 1.23.2. Entering ort's initializer with that library deadlocks permanently instead of returning an error. Align dependency resolution where compatible wheels exist and preflight native detection where they do not. Closes #2960 ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - Require ONNX Runtime 1.24+ for Python 3.11+ in the proxy and voice extras. - Keep the available pre-1.24 runtime on Python 3.10 for Python ONNX consumers. - Refuse to auto-pin an incompatible runtime into the Rust extension. - Bypass native detection immediately when API 24 is unavailable, preserving Python fallback without a five-second watchdog delay or stuck native thread. - Add dependency, pinning, override, and router regression coverage. ## Testing - [x] Unit tests pass (`pytest`) - [x] Linting passes (`ruff check .`) - [ ] Type checking passes (`mypy headroom`) - [x] New tests added for new functionality - [x] Manual testing performed ### Test Output ```text $ uv run pytest -q tests/test_transforms/test_ort_dylib.py tests/test_onnx_dependency_contract.py tests/test_onnx_runtime.py tests/test_transforms/test_content_router.py 88 passed in 9.31s $ uv run ruff check headroom/_ort.py headroom/transforms/content_router.py tests/test_transforms/test_ort_dylib.py tests/test_onnx_dependency_contract.py All checks passed! ``` ## Real Behavior Proof - Environment: macOS arm64; Python 3.13.14 and uv-managed Python 3.10.20. - Exact command / steps: run the issue's direct `headroom._core.detect_content_type` call in a subprocess with a 12-second timeout on Python 3.13; run `_detect_content` on Python 3.10 after resolving the proxy extra. - Observed result: Python 3.13 resolves ORT 1.26.0 and native detection returns `json_array`; Python 3.10 resolves ORT 1.23.2, leaves `ORT_DYLIB_PATH` unset, reports compatibility false, and immediately returns the Python `json_array` fallback. - Not tested: Linux-specific shared-object execution locally; CI's existing Linux Rust job already preflights ORT 1.24+ and exercises native tests. ## Runtime Rollout Safety - Rollout-managed feature(s): Native Rust content detection. - Minimum rollout channel: Stable/default; this is a deadlock prevention guard. - Stable/default behavior changed: Python 3.11+ installs a compatible ORT; Python 3.10 skips incompatible native detection. - Kill switch / disable path: `HEADROOM_DETECT_BACKEND=python` remains available; an explicit `ORT_DYLIB_PATH` remains an operator override. - Unsafe override required: No. - Qualification impact: Native detection stays enabled only with API-24-compatible ORT. - Rollback path: Revert this PR, which restores the old watchdog-only degradation. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] I did **not** edit `CHANGELOG.md` — it is generated by release-please from my Conventional Commit PR title (a CI guard enforces this) ## Screenshots (if applicable) Not applicable. ## Additional Notes The large lockfile diff is dependency resolution: Python 3.10 keeps ORT 1.23.2 while 3.11+ resolves 1.26.0. The functional Python change is intentionally small and keeps explicit `ORT_DYLIB_PATH` overrides working.
523 lines
20 KiB
TOML
523 lines
20 KiB
TOML
[build-system]
|
||
requires = ["maturin>=1.5,<2.0"]
|
||
build-backend = "maturin"
|
||
|
||
[project]
|
||
name = "headroom-ai"
|
||
version = "0.35.0"
|
||
description = "The Context Optimization Layer for LLM Applications - Cut costs by 50-90%"
|
||
readme = "README.md"
|
||
license = "Apache-2.0"
|
||
requires-python = ">=3.10"
|
||
authors = [
|
||
{ name = "Headroom Contributors" }
|
||
]
|
||
maintainers = [
|
||
{ name = "Headroom Contributors" }
|
||
]
|
||
keywords = [
|
||
"llm",
|
||
"openai",
|
||
"anthropic",
|
||
"claude",
|
||
"gpt",
|
||
"context",
|
||
"token",
|
||
"optimization",
|
||
"compression",
|
||
"caching",
|
||
"proxy",
|
||
"ai",
|
||
"machine-learning",
|
||
]
|
||
classifiers = [
|
||
"Development Status :: 4 - Beta",
|
||
"Intended Audience :: Developers",
|
||
"License :: OSI Approved :: Apache Software License",
|
||
"Operating System :: OS Independent",
|
||
"Programming Language :: Python :: 3",
|
||
"Programming Language :: Python :: 3.10",
|
||
"Programming Language :: Python :: 3.11",
|
||
"Programming Language :: Python :: 3.12",
|
||
"Programming Language :: Python :: 3.13",
|
||
"Programming Language :: Python :: 3.14",
|
||
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||
"Typing :: Typed",
|
||
]
|
||
dependencies = [
|
||
# Core: lightweight compression (SmartCrusher, ContentRouter, CCR, TOIN)
|
||
"tiktoken>=0.5.0", # Tokenizer for all compressors
|
||
"pydantic>=2.0.0", # Config and data models
|
||
# litellm's own metadata pins requires-python <3.14, and headroom only uses it for
|
||
# model registry / pricing / non-core providers — all lazily imported and
|
||
# ImportError-guarded. Marking it 3.14-optional lets headroom install on Python 3.14
|
||
# (core compression + the Anthropic proxy path never import litellm). See GH #956.
|
||
"litellm>=1.86.2,<2.0; python_version < '3.14'", # model registry, pricing, providers (lazy)
|
||
"click>=8.3.3", # CLI framework; PYSEC-2026-2132 fix (command injection in click.edit())
|
||
"rich>=13.0.0", # Rich terminal output
|
||
"opentelemetry-api>=1.24.0", # Safe no-op OTEL API for instrumentation
|
||
# AST-aware code slicing (CodeCompressor); binary wheel. 0.44.1 is excluded:
|
||
# that PyPI release was a compromised supply-chain build shipping an
|
||
# info-stealer `sg.exe` (Trojan:Win64/Lazy!MTB) alongside the real binary
|
||
# (GH #2332). The `!=` keeps every other release installable, including a
|
||
# future patched one.
|
||
"ast-grep-cli>=0.30.0,!=0.44.1",
|
||
"pyyaml>=6.0", # omp wrap: parse/merge omp's models.yml registry
|
||
"tomli>=2.0.0; python_version < '3.11'", # tomllib backport for helper scripts
|
||
"tomlkit>=0.13.0,<1.0", # Loss-minimizing Codex config recovery
|
||
]
|
||
|
||
[project.optional-dependencies]
|
||
# Proxy server (most common install: pip install headroom-ai[proxy])
|
||
proxy = [
|
||
"fastapi>=0.100.0",
|
||
"uvicorn>=0.23.0,<1.0",
|
||
# LiteLLM provider backends (e.g. openrouter) expect orjson at runtime but
|
||
# litellm only declares it under its own [proxy] extra (GH #2056).
|
||
"orjson>=3.9.14; platform_python_implementation != 'PyPy'",
|
||
"httpx[http2]>=0.24.0",
|
||
"openai>=2.14.0", # OpenAI API format support
|
||
# The server still uses the v1 low-level Server decorator API. Keep this
|
||
# cap until the SDK 2.x port lands (GH #2658 / regression GH #2977).
|
||
"mcp>=1.28.1,<2.0.0", # MCP server (headroom_compress, retrieve, stats)
|
||
"magika>=0.6.0", # ML content detection for ContentRouter
|
||
"zstandard>=0.20.0", # Decompress zstd request bodies (Codex, etc.)
|
||
"websockets>=13.0", # WebSocket proxy for /v1/responses (Codex gpt-5.4+)
|
||
# Rust fastembed enables ORT C API 24. ORT <1.24 deadlocks instead of
|
||
# returning an initialization error; 1.24+ no longer ships Python 3.10
|
||
# wheels, so 3.10 keeps Python-only ORT and bypasses native detection.
|
||
"onnxruntime>=1.24.0; python_version>='3.11'",
|
||
"onnxruntime>=1.16.0,<1.24.0; python_version<'3.11'",
|
||
"transformers>=5.5.0,<6.0", # Tokenizer only (for Kompress)
|
||
"watchdog>=4.0.0", # File watcher for live code graph reindexing (--code-graph)
|
||
"sqlite-vec>=0.1.6", # Vector index for memory (--memory). Lightweight, no torch.
|
||
]
|
||
# Production ASGI/WSGI server — Unix-only (gunicorn does not support Windows).
|
||
# Kept separate from [proxy] so that dev, CI, and Windows users are not forced
|
||
# to install a non-functional package. Production deployments should use:
|
||
# pip install headroom-ai[proxy,proxy-prod]
|
||
proxy-prod = [
|
||
"headroom-ai[proxy]",
|
||
"gunicorn>=21.0.0; sys_platform != 'win32'",
|
||
]
|
||
# AST-based code compression (tree-sitter)
|
||
# tree-sitter-language-pack >=1.0 removed the bundled tree-sitter package and
|
||
# switched to an incompatible internal node API (.kind vs .type, callable
|
||
# root_node, etc.). Pin to <1.0.0 so that tree-sitter>=0.25.2 is pulled in
|
||
# as a transitive dependency and the existing code_compressor.py node-walk
|
||
# logic continues to work. See issue #1216.
|
||
code = [
|
||
"tree-sitter-language-pack>=0.10.0,<1.0",
|
||
"tree-sitter>=0.25.2,<0.27",
|
||
]
|
||
# ML-based compression with Kompress (ModernBERT).
|
||
# (The legacy [llmlingua] extra was removed in 0.9.x — no live code path used it.
|
||
# Use [ml] for the supported ML compression dependencies.)
|
||
ml = [
|
||
# PyTorch does not publish wheels for macOS 15 x86_64 at this floor, which
|
||
# makes `headroom-ai[all]` unsatisfiable on Intel Macs (#1931).
|
||
"torch>=2.12.1; sys_platform != 'darwin' or platform_machine != 'x86_64'",
|
||
"transformers>=5.5.0,<6.0",
|
||
# transformers >= 5.x requires huggingface-hub >= 1.5.0,<2.0; pinning
|
||
# the floor here prevents Kompress from silently falling back to
|
||
# "unavailable" when a sibling install (e.g. `pip install
|
||
# strands-agents`) drags huggingface-hub backwards.
|
||
"huggingface-hub>=1.5.0,<2.0",
|
||
]
|
||
# Memory system (hierarchical memory with vector search).
|
||
# Uses the pure-Python sqlite-vec backend by default (VectorBackend.AUTO ->
|
||
# SQLITE_VEC), so no C++ toolchain is required. The optional HNSW backend lives
|
||
# in the [vector] extra below; installing it here would make `[all]` (which pulls
|
||
# [memory]) fail on any machine without a compiler — see #1368.
|
||
memory = [
|
||
"sqlite-vec>=0.1.6",
|
||
"sentence-transformers>=2.2.0,<6.0; sys_platform != 'darwin' or platform_machine != 'x86_64'",
|
||
]
|
||
# Optional HNSW vector backend. Needs a C++ toolchain to build hnswlib, so it is
|
||
# kept out of [memory] and [all]; opt in with `pip install headroom-ai[vector]`
|
||
# and select it via MemoryConfig(vector_backend=VectorBackend.HNSW). The default
|
||
# sqlite-vec backend needs no compiler.
|
||
vector = [
|
||
"hnswlib>=0.8.0",
|
||
]
|
||
# Qdrant + Neo4j memory backend helpers
|
||
memory-stack = [
|
||
"mem0ai>=2.0.0,<3.0",
|
||
"qdrant-client>=1.9.0,<2.0",
|
||
"neo4j>=5.20.0,<7.0",
|
||
]
|
||
# Apple-Silicon GPU (MPS) offload for the memory embedder. Opt in at runtime with
|
||
# HEADROOM_EMBEDDER_RUNTIME=pytorch_mps. macOS-only; intentionally excluded from [all].
|
||
pytorch-mps = [
|
||
"torch>=2.12.1; sys_platform == 'darwin'",
|
||
"sentence-transformers>=2.2.0; sys_platform == 'darwin'",
|
||
]
|
||
# Semantic relevance scoring with embeddings.
|
||
# Uses `fastembed` (BAAI/bge-small-en-v1.5 by default — 33M params,
|
||
# 384 dims, ~30 MB int8-quantized ONNX). Same library + model used by
|
||
# the Rust SmartCrusher (`fastembed` crate), giving byte-equal embeddings
|
||
# across the language boundary. Replaced sentence-transformers in
|
||
# Stage 3c.1 — fastembed is faster (~2-3x), smaller (no torch
|
||
# dependency), and outranks all-MiniLM-L6-v2 on MTEB by ~6 points.
|
||
relevance = [
|
||
"fastembed>=0.4.0",
|
||
"numpy>=1.24.0",
|
||
]
|
||
# Image compression (ML-based routing + OCR)
|
||
#
|
||
# OCR backend uses ONNX Runtime regardless of Python version. The
|
||
# rapidocr ecosystem split into two flavors after 1.4.x:
|
||
# * rapidocr-onnxruntime 1.4.x — bundled-ORT package, capped at
|
||
# Python <3.13 by its requires-python metadata. Drop-in for our
|
||
# existing v1 tuple-shaped API call.
|
||
# * rapidocr 3.x — engine-agnostic core, supports Python 3.13+.
|
||
# Returns a RapidOCROutput dataclass (txts, scores, boxes, ...).
|
||
# Needs `onnxruntime` installed separately to use the ORT backend.
|
||
#
|
||
# `headroom/image/compressor.py` adapts both API shapes at runtime via
|
||
# a try/except cascade. See issue #372 for context.
|
||
image = [
|
||
"pillow>=12.3.0", # PYSEC-2026-2253/2254/2255/2256/2257 fixes (decompression-bomb + cmd-injection)
|
||
"sentencepiece>=0.1.99", # Required by SigLIP tokenizer (SiglipTokenizer)
|
||
# Python 3.6–3.12: keep the proven ORT-bundled package directly.
|
||
# ~15 MB ONNX models auto-downloaded on first use.
|
||
"rapidocr-onnxruntime>=1.4.0,<2; python_version<'3.13'",
|
||
# Python 3.13+: rapidocr-onnxruntime is unavailable (its wheels
|
||
# declare requires-python<3.13). Use the successor `rapidocr` 3.x
|
||
# core + `onnxruntime` engine; same ORT backend, just split into
|
||
# two packages. Total install size and inference speed unchanged.
|
||
"rapidocr>=3.0,<4; python_version>='3.13'",
|
||
"onnxruntime>=1.7,<2; python_version>='3.13'",
|
||
]
|
||
# Report generation
|
||
reports = [
|
||
"jinja2>=3.0.0",
|
||
]
|
||
# Binary spreadsheet ingestion (.xlsx / .xls -> tabular text)
|
||
spreadsheet = [
|
||
"openpyxl>=3.1.0", # .xlsx
|
||
"xlrd>=2.0.1", # legacy .xls
|
||
]
|
||
# OpenTelemetry metrics export
|
||
otel = [
|
||
"opentelemetry-sdk>=1.24.0",
|
||
"opentelemetry-exporter-otlp-proto-http>=1.24.0",
|
||
]
|
||
# any-llm multi-provider backend (requires Python 3.11+)
|
||
anyllm = [
|
||
"any-llm-sdk>=1.0.0; python_version >= '3.11'",
|
||
]
|
||
# LangChain integration
|
||
langchain = [
|
||
"langchain-core>=1.3.3,<4.0",
|
||
"langchain-openai>=1.1.14,<2.0",
|
||
]
|
||
# Agno agent framework integration
|
||
agno = [
|
||
"agno>=1.0.0",
|
||
]
|
||
# AWS Strands Agents SDK integration
|
||
strands = [
|
||
"strands-agents>=0.1.0",
|
||
]
|
||
# CrewAI agent framework integration
|
||
crewai = [
|
||
"crewai>=1.0",
|
||
]
|
||
# AutoGen agent framework integration
|
||
autogen = [
|
||
"autogen-agentchat>=0.7",
|
||
]
|
||
# MCP server for Claude Code integration
|
||
mcp = [
|
||
"mcp>=1.28.1,<2.0.0",
|
||
"httpx>=0.24.0",
|
||
"starlette>=0.27.0",
|
||
"uvicorn>=0.23.0,<1.0",
|
||
]
|
||
# Voice filler detection
|
||
voice = [
|
||
"onnxruntime>=1.24.0; python_version>='3.11'",
|
||
"onnxruntime>=1.16.0,<1.24.0; python_version<'3.11'",
|
||
"transformers>=5.5.0,<6.0",
|
||
"torch>=2.12.1; sys_platform != 'darwin' or platform_machine != 'x86_64'",
|
||
]
|
||
# Voice training (includes voice deps + training extras)
|
||
voice-train = [
|
||
"headroom-ai[voice]",
|
||
"datasets>=2.14.0",
|
||
"accelerate>=0.20.0",
|
||
]
|
||
# Evaluation framework
|
||
evals = [
|
||
"datasets>=2.14.0",
|
||
"sentence-transformers>=2.2.0,<6.0; sys_platform != 'darwin' or platform_machine != 'x86_64'",
|
||
"numpy>=1.24.0",
|
||
"scikit-learn>=1.3.0",
|
||
"anthropic>=0.18.0",
|
||
"openai>=1.0.0",
|
||
]
|
||
# AWS Bedrock backend
|
||
bedrock = [
|
||
# `aws login` (IAM Identity Provider / console-login, DPoP) requires
|
||
# boto3 >= 1.41.0 AND the AWS Common Runtime (CRT) per AWS docs
|
||
# ("Boto3 1.41.0 or later with CRT"). CRT is a separate install — pull it
|
||
# via the botocore [crt] extra (awscrt). Without it, resolving `aws login`
|
||
# credentials raises botocore's MissingDependencyException.
|
||
"boto3>=1.41.0",
|
||
"botocore[crt]>=1.41.0",
|
||
]
|
||
# HTML content extraction
|
||
html = [
|
||
"trafilatura>=1.6.0",
|
||
]
|
||
# Development dependencies
|
||
dev = [
|
||
"pytest>=7.0.0",
|
||
"pytest-cov>=4.0.0",
|
||
"pytest-asyncio>=0.21.0",
|
||
"ruff==0.15.22",
|
||
"mypy>=1.0.0",
|
||
"pre-commit>=3.0.0",
|
||
"openai>=1.0.0",
|
||
"anthropic>=0.18.0",
|
||
"litellm>=1.86.2,<2.0; python_version < '3.14'", # see core deps note (GH #956)
|
||
"fastapi>=0.100.0",
|
||
"uvicorn>=0.23.0,<1.0",
|
||
"httpx[http2]>=0.24.0",
|
||
"websockets>=13.0",
|
||
"opentelemetry-sdk>=1.24.0",
|
||
"opentelemetry-exporter-otlp-proto-http>=1.24.0",
|
||
"ollama>=0.4.0",
|
||
"langchain-ollama>=0.2.0",
|
||
"hnswlib>=0.8.0",
|
||
"sqlite-vec>=0.1.6",
|
||
"sentence-transformers>=2.2.0,<6.0",
|
||
"numpy>=1.24.0",
|
||
"openpyxl>=3.1.0", # exercises spreadsheet_ingest (.xlsx) in the test suite
|
||
"respx>=0.20.0", # HTTP mock transport for passthrough handler tests
|
||
]
|
||
# All optional dependencies (everything you need).
|
||
#
|
||
# The EleutherAI lm-evaluation-harness is intentionally not exposed as a
|
||
# project extra. Headroom invokes it as an external subprocess
|
||
# (`python -m lm_eval`), and the harness currently pulls sqlitedict
|
||
# CVE-2024-35515 with no upstream fix. Keeping it out of locked project extras
|
||
# prevents repository scanners from flagging production installs; researchers
|
||
# who need standard accuracy benchmarks can install `lm-eval[api]` in their
|
||
# benchmark environment separately.
|
||
all = [
|
||
"headroom-ai[proxy,code,ml,memory,relevance,image,reports,otel,evals,voice,html,mcp,spreadsheet]",
|
||
]
|
||
# Sandbox: a lean proxy with ALL torch-free capability — for running Headroom in
|
||
# a locked-down/low-resource sandbox and offloading heavy ML elsewhere.
|
||
#
|
||
# = [all] MINUS:
|
||
# - image (SigLIP/OCR — excluded by request)
|
||
# - ml (torch — the PyTorch Kompress backend; ONNX path in [proxy] still
|
||
# runs Kompress locally with no torch, or offload it entirely via
|
||
# HEADROOM_KOMPRESS_ENDPOINT)
|
||
# - voice (excluded by request)
|
||
# - memory + evals (both pull sentence-transformers -> torch, i.e. the very
|
||
# ML weight a sandbox avoids; evals is a dev/test harness, not a
|
||
# runtime feature). Opt back in explicitly if you accept torch:
|
||
# pip install headroom-ai[sandbox,memory]
|
||
#
|
||
# Everything kept here is torch-free: code-aware compression (tree-sitter),
|
||
# embedding relevance (fastembed), HTML/spreadsheet ingestion, reports, OTel.
|
||
sandbox = [
|
||
"headroom-ai[proxy,code,relevance,reports,otel,html,mcp,spreadsheet]",
|
||
]
|
||
|
||
[project.scripts]
|
||
headroom = "headroom.cli:main"
|
||
|
||
[project.urls]
|
||
Homepage = "https://headroom-docs.vercel.app"
|
||
Documentation = "https://headroom-docs.vercel.app/docs"
|
||
Repository = "https://github.com/chopratejas/headroom"
|
||
Issues = "https://github.com/chopratejas/headroom/issues"
|
||
Changelog = "https://github.com/chopratejas/headroom/blob/main/CHANGELOG.md"
|
||
# llms.txt convention (llmstxt.org) — point AI agents / LLM crawlers
|
||
# at the auto-generated docs index so they can resolve install paths
|
||
# and entry points without a follow-up fetch.
|
||
"AI / LLM Index" = "https://headroom-docs.vercel.app/llms.txt"
|
||
|
||
# Maturin builds a single wheel containing both the Python source under
|
||
# `headroom/` AND the compiled Rust extension `headroom/_core.so` (cdylib
|
||
# from `crates/headroom-py`). One `pip install headroom-ai` ships everything
|
||
# atomically — no separate `headroom-core-py` package, no chicken-and-egg,
|
||
# no PIP_FIND_LINKS plumbing. Phase A0's runtime fail-loud check still
|
||
# exists but only fires if someone forces an sdist install on a platform
|
||
# without a wheel and the rust toolchain isn't available to compile it.
|
||
# Constrain transitive dependencies that have CVEs requiring minimum versions.
|
||
# These packages don't appear as direct headroom deps but are pulled in
|
||
# transitively; the floor pins below ensure uv resolves to patched versions.
|
||
[tool.uv]
|
||
constraint-dependencies = [
|
||
# GHSA-5239-wwwm-4pmq (Low) — transitive via rich; fix at 2.20.0
|
||
"pygments>=2.20.0",
|
||
# GHSA-4xgf-cpjx-pc3j (Medium) — transitive via mcp; fix at 2.14.2
|
||
"pydantic-settings>=2.14.2",
|
||
# GHSA-mv93-w799-cj2w + 4 others (High) — transitive via agno; fix at 3.1.50
|
||
"gitpython>=3.1.50",
|
||
# GHSA-f4xh-w4cj-qxq8 (High) — transitive via langchain-core; fix at 0.8.18
|
||
"langsmith>=0.9.0",
|
||
# CVE-2026-49825 (High, XSS) — transitive via lxml[html-clean]; fix at 0.4.5
|
||
"lxml-html-clean>=0.4.5",
|
||
# CVE-2026-5241 (High) — direct optional dep for proxy/ml/voice; fix at 5.5.0
|
||
"transformers>=5.5.0",
|
||
# PYSEC-2026-3447 — transitive dependency; fix at 83.0.0
|
||
"setuptools>=83.0.0",
|
||
# PYSEC-2026-3545/3546/3547 — transitive HTTP/WebSocket parser fixes
|
||
"aiohttp>=3.14.3",
|
||
# PYSEC-2026-3552/3553/3554 — PKCS#7 and certificate verification fixes
|
||
"cryptography>=50.0.0",
|
||
]
|
||
|
||
# Pin the project's package index to public PyPI. Without this, `uv lock`
|
||
# inherits the developer's user-level `~/.config/uv/uv.toml` index
|
||
# setting — including private/internal mirrors like
|
||
# `pypi.netflix.net/simple` — and bakes those URLs into uv.lock, which
|
||
# then breaks CI on every public runner that can't reach the mirror.
|
||
# Declaring the index in pyproject.toml makes the project authoritative
|
||
# regardless of who runs `uv lock`.
|
||
[[tool.uv.index]]
|
||
name = "pypi"
|
||
url = "https://pypi.org/simple/"
|
||
default = true
|
||
|
||
[tool.maturin]
|
||
# Where the Python package lives. With `python-source = "."` and the
|
||
# package directory `headroom/` at repo root, maturin includes every file
|
||
# under `headroom/` in the wheel — that picks up the dashboard HTML
|
||
# templates and bundled YAML configs. `LICENSE` and `NOTICE` are listed
|
||
# explicitly because maturin sdists do not get the package-directory
|
||
# treatment wheels do, and PEP 639 auto-discovery emits both files into
|
||
# `License-File:` metadata — PyPI rejects sdists whose declared license
|
||
# files are missing from the tarball with `400 License-File X does not
|
||
# exist in distribution file`.
|
||
include = [
|
||
{ path = "LICENSE", format = "sdist" },
|
||
{ path = "NOTICE", format = "sdist" },
|
||
]
|
||
python-source = "."
|
||
module-name = "headroom._core"
|
||
# The cdylib source lives under `crates/headroom-py`. Maturin invokes
|
||
# `cargo build` with this manifest to produce `_core.cdylib`, then injects
|
||
# the resulting `.so` into the wheel at `headroom/_core.so`.
|
||
manifest-path = "crates/headroom-py/Cargo.toml"
|
||
features = ["extension-module"]
|
||
# Forbid building without the cdylib feature — bare `cargo build` won't
|
||
# produce a usable Python extension. Maturin's default `bindings` is "pyo3"
|
||
# which is correct here (see `crates/headroom-py/src/`).
|
||
bindings = "pyo3"
|
||
|
||
[tool.ruff]
|
||
target-version = "py310"
|
||
line-length = 100
|
||
|
||
[tool.ruff.lint]
|
||
select = [
|
||
"E", # pycodestyle errors
|
||
"W", # pycodestyle warnings
|
||
"F", # pyflakes
|
||
"I", # isort
|
||
"B", # flake8-bugbear
|
||
"C4", # flake8-comprehensions
|
||
"UP", # pyupgrade
|
||
]
|
||
ignore = [
|
||
"E501", # line too long (handled by formatter)
|
||
"B008", # do not perform function calls in argument defaults
|
||
"B905", # zip without strict parameter
|
||
]
|
||
|
||
[tool.ruff.lint.isort]
|
||
known-first-party = ["headroom"]
|
||
|
||
[tool.ruff.format]
|
||
quote-style = "double"
|
||
indent-style = "space"
|
||
|
||
[tool.mypy]
|
||
python_version = "3.10"
|
||
warn_return_any = true
|
||
warn_unused_configs = true
|
||
disallow_untyped_defs = true
|
||
ignore_missing_imports = true
|
||
|
||
# Per-module overrides for modules with dynamic typing patterns
|
||
[[tool.mypy.overrides]]
|
||
module = [
|
||
"headroom.proxy.server",
|
||
"headroom.proxy.cost",
|
||
"headroom.proxy.prometheus_metrics",
|
||
"headroom.proxy.semantic_cache",
|
||
"headroom.proxy.rate_limiter",
|
||
"headroom.proxy.request_logger",
|
||
"headroom.proxy.helpers",
|
||
"headroom.integrations.langchain",
|
||
"headroom.integrations.mcp",
|
||
"headroom.ccr.mcp_server",
|
||
"headroom.relevance.embedding",
|
||
"headroom.reporting.generator",
|
||
]
|
||
disallow_untyped_defs = false
|
||
|
||
[[tool.mypy.overrides]]
|
||
module = [
|
||
"headroom.tokenizers.*",
|
||
"headroom.providers.litellm",
|
||
"headroom.providers.google",
|
||
]
|
||
disallow_untyped_defs = false
|
||
warn_return_any = false
|
||
|
||
# Handler mixins use self.* from HeadroomProxy via duck typing — mypy can't resolve these
|
||
[[tool.mypy.overrides]]
|
||
module = ["headroom.proxy.handlers.*"]
|
||
disallow_untyped_defs = false
|
||
ignore_errors = true
|
||
|
||
# Ignore third-party stubs with syntax errors
|
||
[[tool.mypy.overrides]]
|
||
module = ["mlx.*"]
|
||
ignore_errors = true
|
||
|
||
[tool.pytest.ini_options]
|
||
testpaths = ["tests"]
|
||
python_files = ["test_*.py"]
|
||
python_functions = ["test_*"]
|
||
addopts = "-v --tb=short"
|
||
asyncio_mode = "auto"
|
||
filterwarnings = [
|
||
# pyo3 Unsendable parsers emit an unraisable warning when GC drops them on a
|
||
# test-teardown thread; this is a test-harness artifact, not a production issue
|
||
# (production threads are long-lived and drop their parsers on themselves).
|
||
"ignore::pytest.PytestUnraisableExceptionWarning",
|
||
]
|
||
markers = [
|
||
"slow: slow tests (model loads, large fixtures)",
|
||
"real_llm: tests that hit real LLM APIs; skipped unless explicitly enabled",
|
||
"live: opt-in multi-turn tests that hit real upstream APIs; require provider keys",
|
||
"proxy_dependency_gate: exercises ensure_proxy_dependencies() without mocking",
|
||
]
|
||
|
||
[tool.coverage.run]
|
||
source = ["headroom"]
|
||
branch = true
|
||
omit = [
|
||
"headroom/cli.py",
|
||
"*/tests/*",
|
||
]
|
||
|
||
[tool.coverage.report]
|
||
exclude_lines = [
|
||
"pragma: no cover",
|
||
"def __repr__",
|
||
"raise NotImplementedError",
|
||
"if TYPE_CHECKING:",
|
||
"if __name__ == .__main__.:",
|
||
]
|