[project] name = "adcp-sales-agent" version = "2.0.0" description = "Prebid Sales Agent — AdCP-compliant MCP/A2A/REST server for ad inventory orchestration." readme = "README.md" requires-python = ">=3.12" dependencies = [ "a2wsgi>=1.10.0", "adcp==5.7.0", # exact pin — 5.7.0 targets AdCP spec 3.1.0-beta.3; see docs/adcp-spec-version.md "fastmcp>=3.2.0,<3.3.0", # 3.3.0 split fastmcp-slim[server] from the monolith (breaking Context/Middleware imports); stay on 3.2.x "google-cloud-iam>=2.19.1", "rich>=13.7.1", "google-ads>=24.1.0", "requests>=2.33.0", "googleads==49.0.0", # Pinned: library default sets GAM API version "flask>=3.1.3", "flask-caching>=2.3.0", # Response caching for improved performance "psycopg2-binary>=2.9.9", "authlib>=1.6.12", # GHSA-jj8c-mmj3-mmgv; PYSEC-2026-188 "requests-oauthlib>=1.3.1", "fastapi>=0.133.0", # 0.133.0 adds starlette 1.0+ support (PYSEC-2026-161) "starlette>=1.0.1", # PYSEC-2026-161 / CVE-2026-48710 (BadHost); explicit pin — fastapi allows 0.50.x "uvicorn>=0.23.0", "beautifulsoup4>=4.12.0", "aiohttp>=3.14.0", # CVE-2026-34993 "alembic>=1.13.0", "sqlalchemy>=2.0.0", "watchdog>=6.0.0", "waitress>=3.0.0", "pytz>=2024.1", "flask-socketio>=5.5.1", "python-socketio>=5.16.2", "simple-websocket>=1.1.0", "httpx>=0.28.1", "a2a-cli>=0.2.0", "a2a-sdk[http-server]>=1.0.1", # a2a-sdk 1.0 (protobuf API, required by adcp>=4.3.0) "jinja2>=3.1.0", "prometheus-client>=0.23.1", "types-pytz>=2025.2.0.20250809", "markdown>=3.4.0", "types-Markdown>=3.4.2.10", "freezegun>=1.5.5", "babel>=2.17.0", "logfire>=4.16.0", "pydantic-ai-slim[google,openai,anthropic,cohere,mistral,groq]>=1.99.0", # 1.99.0 fixes CVE-2026-46678 / GHSA-cqp8-fcvh-x7r3; use slim to avoid pydantic-ai meta-package pulling fastmcp>=3.3.0 # Security patches for transitive dependencies "pyjwt>=2.13.0", # GHSA-752w-5fwx-jx9f; PYSEC-2026-175/177/178/179 "filelock>=3.20.3", # GHSA-qmgc-5h2g-mvrw "urllib3>=2.7.0", # GHSA-38jv-5279-wg99 + GHSA-qccp-gfcp-xxvc + GHSA-mf9v-mfxr-j63j "werkzeug>=3.1.6", # GHSA-87hc-h4r5-73f7 + GHSA-29vq-49wr-vm6x "jaraco-context>=6.1.0", # GHSA-58pv-8j8x-9vj2 "cryptography>=46.0.7", # GHSA-r6ph-v2qm-q3c2 + GHSA-p423-j2cm-9vmq "pillow>=12.2.0", # GHSA-cfh3-3jmp-rvhc + GHSA-whj4-6x5x-4v2j "python-multipart>=0.0.27", # GHSA-wp53-j4wj-2cfg + GHSA-mj87-hwqh-73pj + GHSA-pp6c-gr5w-3c5g "pyasn1>=0.6.3", # GHSA-jr27-m4p2-rc6r "mako>=1.3.12", # GHSA-2h4p-vjrc-8xpq (transitive via alembic) ] # [project.optional-dependencies] removed in PR 2 of issue #1234. # [dependency-groups].dev below is the single source of truth for dev deps. # The ui-tests extras are preserved here as they serve a distinct purpose # (Playwright-based UI test infra, not part of the standard dev workflow). [project.optional-dependencies] ui-tests = [ "playwright==1.60.0", "pytest-playwright>=0.7.2", # 0.6.x caps pytest<9.0.0; bump to allow pytest>=9.0.3 (GHSA-6w46-j5rx-g56g) "pytest-asyncio>=1.1.0", "allure-pytest==2.16.0", ] [project.urls] Homepage = "https://github.com/prebid/salesagent" Repository = "https://github.com/prebid/salesagent" Issues = "https://github.com/prebid/salesagent/issues" Documentation = "https://github.com/prebid/salesagent/tree/main/docs" Changelog = "https://github.com/prebid/salesagent/blob/main/CHANGELOG.md" [dependency-groups] dev = [ "asgi-lifespan>=2.1.0", "factory-boy>=3.3.0", "mocker>=1.1.1", "mypy>=1.18.2", "pytest>=9.0.3", # GHSA-6w46-j5rx-g56g "pytest-asyncio>=1.1.0", "pytest-cov>=6.2.1", "pytest-mock>=3.14.1", "pytest-timeout>=2.3.1", "ruff>=0.8.0", "datamodel-code-generator>=0.26.0", "jsonref>=1.1.0", "types-psycopg2>=2.9.21.20251012", "types-requests>=2.32.4.20250913", "types-pytz>=2025.2.0.20250809", "types-waitress>=3.0.1.20250801", "pytest-json-report>=1.5.0", "pytest-bdd>=7.0.0", "pytest-xdist>=3.5.0", # bdd suite runs `pytest -n auto --dist loadfile` "semgrep>=1.50.0", "pylint>=4.0.5", # Security patches for transitive dev dependencies "black>=26.3.1", # GHSA-3936-cmfr-pm3m "types-python-dateutil>=2.9.0.20260408", # Added in PR 2 of issue #1234: randomly for test order randomisation to # surface order-dependent failures. xdist floor kept at >=3.5.0 (union of # both sides; branch fix(test-infra) 45c310f3e). pytest-xdist is declared # once above next to pytest-bdd. "pytest-randomly>=3.15.0", ] [tool.ruff] line-length = 120 target-version = "py311" exclude = [ ".eggs", ".git", ".hg", ".mypy_cache", ".tox", ".venv", ".claude/worktrees", # Agent worktrees have their own checkouts "_build", "buck-out", "build", "dist", "alembic/versions", # Never format migration files ] [tool.ruff.lint] select = [ "E", # pycodestyle errors "W", # pycodestyle warnings "F", # pyflakes "I", # isort "B", # flake8-bugbear "C4", # flake8-comprehensions "UP", # pyupgrade "C90", # mccabe complexity "PLR", # pylint refactor "TID251", # banned-api — enforce prohibited function usage ] ignore = [ # Justified suppressions — each must explain WHY it's ignored, not just WHAT it is. # Do not add new entries without a justification comment. "E501", # Handled by ruff formatter; manual enforcement would conflict "E402", # Flask app (admin_ui.py) requires imports after app config; 3 occurrences "E722", # 12 bare excepts in error-recovery paths (adapters, auth); TODO: add specific exceptions "F821", # 4 false positives from conditional imports and TYPE_CHECKING blocks "F403", # 8 star imports in __init__.py re-exports; TODO: replace with explicit imports "F405", # Consequence of F403 — will resolve when F403 is fixed "E741", # 3 uses of `l` variable in loops; renaming would hurt readability in math-heavy code "B904", # 47 raise-without-from in adapter error handling; TODO: add `from e` systematically "B027", # 6 empty methods in BaseAdapter — intentional optional interface, not abstract "F841", # 5 remaining unused vars in complex destructuring; each reviewed, kept for clarity "C901", # 216 complex functions — structural; fix incrementally per-module "PLR2004", # 158 magic values — too noisy for signal; numeric constants in config/tests are readable "PLR0911", # 68 too-many-return-statements — structural; fix incrementally "PLR0912", # 148 too-many-branches — structural; fix incrementally "PLR0913", # 94 too-many-arguments — structural; fix incrementally "PLR0915", # 134 too-many-statements — structural; fix incrementally ] [tool.ruff.lint.flake8-tidy-imports.banned-api] "inspect.getsource".msg = "inspect.getsource() is banned in tests. Write behavioral tests that exercise the actual code path instead of asserting strings exist in source code. See salesagent-gqzp." [tool.ruff.lint.per-file-ignores] "tests/*" = ["E722", "F821"] "admin_ui.py" = ["E402", "E722"] # ─── Coverage configuration ────────────────────────────────────── [tool.coverage.run] source = ["src"] branch = true relative_files = true [tool.coverage.paths] source = [ "src/", ".tox/**/site-packages/", ] [tool.coverage.report] show_missing = true skip_covered = true exclude_also = [ "pragma: no cover", "def __repr__", "raise AssertionError", "raise NotImplementedError", "if __name__ == .__main__.", "if TYPE_CHECKING:", "@abstract", "@abstractmethod", ] [tool.coverage.html] directory = "htmlcov" [tool.coverage.json] output = "coverage.json" pretty_print = true # ─── Pylint configuration (used ONLY for R0801 duplicate-code detection) ────── # General linting uses ruff. Pylint is invoked solely for its similarities # checker, which has no equivalent in ruff. [tool.pylint."messages control"] disable = "all" enable = "R0801" [tool.pylint.similarities] min-similarity-lines = 6 ignore-imports = true ignore-docstrings = true ignore-comments = true ignore-signatures = true