-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathpyproject.toml
More file actions
481 lines (467 loc) · 27.2 KB
/
Copy pathpyproject.toml
File metadata and controls
481 lines (467 loc) · 27.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
[build-system]
requires = ["setuptools>=82.0.1"]
build-backend = "setuptools.build_meta"
[project]
name = "podcast-scraper"
version = "2.7.0.dev0"
description = "Download podcast transcripts from RSS feeds with optional Whisper fallback"
readme = "README.md"
authors = [{name = "Podcast Scraper Maintainers"}]
license = "MIT"
requires-python = ">=3.10"
dependencies = [
"requests>=2.31.0,<3.0.0",
# Server HTTP client, required unconditionally by the create_app import chain (consumer
# OAuth app_oauth, audio-bridge HEAD validation, reference client, job webhooks) + Ollama
# health checks + the [observability] control plane. A true base dep: the api image installs
# only `.[search,sentry]`, so httpx must live in core — not only in extras.
"httpx>=0.28.1,<1.0.0",
"urllib3>=2.6.0,<3.0.0",
"rich>=15.0.0,<16.0.0",
"defusedxml>=0.7.1,<1.0.0",
"platformdirs>=3.11.0,<5.0.0",
"pydantic>=2.13.4,<3.0.0",
"PyYAML>=6.0.3,<7.0.0",
"python-dotenv>=1.2.2,<2.0.0", # For .env file support (RFC-013)
"zipp>=4.1.0,<5.0.0", # Security fix: CVE-2024-50208 (infinite loop vulnerability)
"jinja2>=3.1.6,<4.0.0", # For prompt templating (RFC-017)
"filelock>=3.29.7,<4.0.0", # Security fix: CVE-2025-68146 (TOCTOU race condition)
# CVE-2026-4539 (ReDoS in AdlLexer, NVD/GHSA: 2.19.0–2.19.2): stay on 2.18.x until 2.19.3+; pip-audit still needs --ignore-vuln until OSV range matches
# TODO(CVE-2026-4539): After pygments ships a fixed release and pip-audit/OSV match NVD ranges, bump the cap and drop Makefile ignore.
"pygments>=2.20.0,<2.21.0",
"psutil>=5.9.0,<8.0.0", # RFC-065 monitor / sampler (imported with orchestration); Docker base image
# Pure-Python VADER lexicon sentiment for the deterministic `insight_sentiment` enricher
# (conversation-timeline sentiment colour). Bundles its own lexicon → no network / no model
# download → CI-safe + airgapped-safe; a true deterministic-tier dep, so it lives in core.
"vaderSentiment>=3.3.2,<4.0.0",
# NOTE: no 3rd-party observability SaaS SDKs here — Sentry/Langfuse are fully
# optional opt-in extras ([sentry] / [langfuse]); the core no-ops without them.
# See the [project.optional-dependencies] header + docs/guides/OBSERVABILITY_EXTENSIONS.md.
]
[project.optional-dependencies]
# Canonical extras (no separate [gemini] / [openai] extras):
# dev — tests, lint, typecheck, FastAPI/uvicorn (viewer API), text eval metrics; no torch/transformers
# ml — Whisper, spaCy (+models), torch, transformers, sentence-transformers, lancedb,
# llama-cpp-python (GGUF hybrid REDUCE, RFC-042), etc.
# llm — API SDKs: openai, google-genai (Gemini), anthropic, mistralai, httpx (Ollama checks)
# search — Minimum local ML for the vector index (sentence-transformers + lancedb + torch CPU).
# Pulled into the cloud-API pipeline image alongside [llm] so cloud_balanced can
# build its LanceDB index without dragging in spaCy/Whisper/Pegasus/llama-cpp from [ml].
# compare — Streamlit compare UI (RFC-047)
# monitor — py-spy + memray optional profiling (RFC-065; `--monitor` needs no extra)
# (FastAPI / uvicorn / metrics / scheduler for ``podcast serve`` live in [dev], not a separate extra.)
# observability — standalone prod control plane (#803, ``podcast_obs``). LIGHT by design.
# Standalone prod observability control plane (#803). Intentionally LIGHT so it runs as a small
# container anywhere on the tailnet — no torch/spaCy/pipeline deps. (httpx + PyYAML are already
# base deps; listed here so a future slim ``podcast_obs``-only install is self-describing.)
observability = [
"httpx>=0.28.1,<1.0.0",
"PyYAML>=6.0.3,<7.0.0",
"mcp>=1.2.0,<2.0.0", # FastMCP server for the agent-facing layer (stdio + sse/http)
]
# ── Optional 3rd-party observability extensions ──────────────────────────────────
# o11y SaaS integrations are FULLY OPTIONAL, one extra per service. The core carries
# ZERO of these deps and no-ops without them (every touchpoint is a lazy guarded import
# + config-gated). Two distinct kinds of o11y dep:
# • READ an o11y backend (Grafana / Loki / Sentry API / Langfuse API) → httpx only, in
# [observability] (the light control plane). No vendor SDK — uniform across the 5
# podcast_obs sources.
# • EMIT from the app → the service's vendor SDK, one extra each below. Each is a
# config-gated no-op (active only when its secret is present) AND degrades to a no-op
# when the SDK isn't installed. Install only what you use; compose them into the prod
# images you deploy (docker/{api,pipeline}); [dev] pulls all of them so tests/CI
# exercise them. Adding a new one → docs/guides/OBSERVABILITY_EXTENSIONS.md.
sentry = [
# Error reporting for the api (FastAPI) + pipeline (cli) via
# podcast_scraper.utils.sentry_init.init_sentry(); no-op without PODCAST_SENTRY_DSN_*.
"sentry-sdk>=2.60.0,<3.0.0",
]
langfuse = [
# LLM tracing emitted at the provider cost choke point (#1052); no-op without
# LANGFUSE_PUBLIC_KEY + LANGFUSE_SECRET_KEY. Pulls OpenTelemetry.
"langfuse>=4.9.0,<5.0.0",
]
otel = [
# Distributed tracing -> OTLP (ADR-119 "traces" signal; VictoriaTraces reference
# backend). `opentelemetry-instrument` auto-wraps the process and
# `opentelemetry-bootstrap -a install` (run in the Dockerfile) adds per-library
# instrumentations. No-op unless OTEL_TRACES_EXPORTER=otlp + an OTLP endpoint are
# set — safe to bake into the image. See docs/guides/OBSERVABILITY_ARCHITECTURE.md.
"opentelemetry-distro>=0.65b0",
"opentelemetry-exporter-otlp-proto-http>=1.44.0",
]
# Heavy ML dependencies - only install when needed
ml = [
"openai-whisper>=20250625",
# spaCy 3.8 / thinc 8.3 for numpy 2.x compatibility (required by pyannote.audio 4.x).
"spacy>=3.8.0,<4.0.0",
# spaCy's CLI imports ``click`` via typer (``spacy/cli/_util.py``).
# typer-0.26.0+ dropped click from its hard deps in favour of having
# consumers depend on it directly. The cloud-thin / API images still
# get click transitively (uvicorn / fastapi), but the [ml] pipeline
# image has no such fallback — ``import spacy`` fails with
# ``ModuleNotFoundError: No module named 'click'`` if click isn't
# explicit here. Surfaced 2026-05-27 when typer-0.26.1 first appeared
# in resolution.
"click>=8.0.0,<9.0.0",
# spaCy language models are distributed via GitHub releases (not PyPI) due to size constraints.
# This is the official and standard method recommended by spaCy.
# Version compatibility: spaCy 3.8.x requires en_core_web_sm 3.8.x and en_core_web_trf 3.8.x
# See: https://spacy.io/usage/models for model version compatibility
# Keep scripts/spacy_model_wheels_requirements.txt in sync (make download-spacy-wheels).
"en-core-web-sm @ https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl",
"en-core-web-trf @ https://github.com/explosion/spacy-models/releases/download/en_core_web_trf-3.8.0/en_core_web_trf-3.8.0-py3-none-any.whl",
"torch>=2.11.0,<3.0.0",
# numpy 2: spaCy 3.8 + thinc 8.3 (above) support it — the migration the previous
# numpy<2 pin (#899) was waiting for — and pyannote.audio 4.x REQUIRES numpy 2.
# The old thinc 8.2 ABI crash is gone with thinc 8.3.
"numpy>=2.2.6,<3.0",
# transformers v5 (#382): pipeline() removed for summarization / text2text-generation /
# question-answering — call sites rewritten to AutoModel*.from_pretrained + generate() /
# QA-head forward. Also picks up the CVE-2026-1839 fix (Trainer rng_state / torch.load
# weights_only) that shipped in >=5.0.0rc3. See docs/wip/ISSUE-382-*.md + ADR-068 post-impl.
# Native-wheel drift guard (2026-07-17): the acceptance job was green on
# 84b17ab (07-11, transformers 5.13.1 / sentencepiece 0.2.1 / torchcodec
# 0.14.0) and started SIGSEGV/SIGBUS-ing at ML-stack teardown on 2174be4
# (07-16) with ZERO code cause — the only change was these floating native
# wheels re-resolving to newer builds (transformers 5.14.1, sentencepiece
# 0.2.2, torchcodec 0.15.0). Cap below the crashing builds until the upstream
# teardown regression is isolated + fixed. macOS/arm64 can't mirror the linux
# crash (it segfaults even on the green stack), so CI is the only valid oracle.
"transformers>=5.0.0,<5.14",
"sentencepiece>=0.2.1,<0.2.2",
# sentence-transformers 5.6.0 fixes silent causal-LM reranker scoring bug + restores
# TSDAE on transformers v5. Dual v4/v5 compat since ST 5.2.1 — safe floor for #382.
"sentence-transformers>=5.6.0,<6.0.0",
# NOTE: lancedb is intentionally NOT here — it is search-only (used solely under
# ``search/`` + the index-rebuild route + upgrade migration, never by transcription /
# summarization / GI). It lives in [search]; an ML pipeline that also indexes composes
# ``.[ml,search]``. The torch/transformers/sentence-transformers/sentencepiece/protobuf
# overlap with [search] above is genuine — both ML (whisper, summarization, GI grounding +
# NLI) and search (embeddings) import them — not a subset relationship (#1019).
"accelerate>=1.13.0,<2.0.0",
"protobuf>=3.20.0,!=6.33.4,<8.0.0", # Exclude 6.33.4 due to CVE-2026-0994 (fixed in 6.33.5+)
# GGUF in-process inference for hybrid_ml Tier 2 REDUCE (RFC-042, Issue #352); Ollama needs no extra
"llama-cpp-python>=0.3.26,<0.4.0",
# Neural speaker diarization (RFC-058): pyannote after local Whisper transcribe.
# 4.x switched audio I/O to torchcodec (no torchaudio.AudioMetaData) so it works
# with torch>=2.11/torchaudio>=2.9; 3.x is import-broken against that torchaudio.
"pyannote.audio>=4.0.7,<5.0",
"torchaudio",
# torchcodec is transitive (torchaudio 4.x / pyannote 4.x). Cap <0.15: 0.15.0
# is part of the 2026-07-17 native teardown SIGSEGV drift (see transformers
# note above). Explicit line so the cap survives transitive re-resolution.
"torchcodec<0.15",
]
# Streamlit run comparison UI (RFC-047, Issue #373)
compare = [
"streamlit>=1.41.0,<2.0.0",
"plotly>=6.7.0,<7.0.0",
"pandas>=2.3.3,<3.0.0",
"rouge-score>=0.1.2,<1.0.0",
]
# LLM API providers - grouped for CI/test convenience
llm = [
"openai>=1.0.0,<3.0.0", # OpenAI API provider (RFC-013, Issue #405)
# Google Gemini (GeminiProvider); no [gemini] extra — install via .[llm]
# 1.x: ThinkingConfig.thinking_budget for gemini-2.5-flash (Issue #572); 0.x lacked the field
"google-genai>=2.16.0,<3.0.0",
"google-api-core>=2.30.3,<3.0.0", # Required by google-genai for some operations
"anthropic>=0.116.0,<1.0.0", # Anthropic API provider (Issue #106)
# Mistral API provider (Issue #106). PyPI ``mistralai`` is quarantined (no installable
# files on https://pypi.org/simple/mistralai/ as of 2026-05). Pin the official v2.4.5
# tree (commit matches tag v2.4.5) until PyPI restores the project.
"mistralai @ https://github.com/mistralai/client-python/archive/7ebe84b5a9eee0714f81c687c8cf636eaa12476f.tar.gz",
"deepgram-sdk>=7.3.1,<8.0.0",
"httpx>=0.28.1,<1.0.0", # Ollama provider (Issue #196) - for health checks
]
# Minimum local ML to build the LanceDB vector index when running an otherwise cloud-only
# pipeline (cloud_balanced on the pipeline-llm image). No spaCy / Whisper / Pegasus /
# llama-cpp — those stay in [ml]. Image size delta on top of [llm]: ~700 MB → ~1.5 GB.
search = [
"torch>=2.11.0,<3.0.0",
# Kept aligned with [ml] pin (#382): transformers v5 + sentence-transformers 5.6+.
# Native-wheel drift caps mirror [ml] (2026-07-17 teardown SIGSEGV; see note there).
"transformers>=5.0.0,<5.14",
"sentencepiece>=0.2.1,<0.2.2",
"sentence-transformers>=5.6.0,<6.0.0",
# RFC-090 two-tier hybrid backend: embedded vector + native FTS (BM25) in one store.
"lancedb>=0.33.0,<1.0.0",
"protobuf>=3.20.0,!=6.33.4,<8.0.0", # Exclude 6.33.4 due to CVE-2026-0994 (fixed in 6.33.5+)
]
# Live pipeline monitor profiling: py-spy + memray (RFC-065; core `--monitor` uses stdlib + psutil + rich)
monitor = [
"py-spy>=0.4.2",
"memray>=1.19.3",
]
# Development tools plus GI/KG viewer HTTP stack (FastAPI, RFC-062). The server-runtime pins
# (fastapi/uvicorn/prometheus-fastapi-instrumentator/apscheduler) are mirrored by
# docker/api/Dockerfile, which installs them directly rather than via ``.[dev]`` (to keep
# pytest/black/etc out of the production image). KEEP THE TWO IN SYNC — they drifted once (#1019).
dev = [
# All optional o11y emit-SDKs — so every dev/CI/`make install` venv exercises them
# (the tests import the SDKs to mock them) and the core-without-them path is covered
# via simulated absence. Runtime stays a no-op unless each service's secrets are set.
"podcast-scraper[sentry]",
"podcast-scraper[langfuse]",
# Cap <0.137 lifted (#1029 → Dependabot #1375): prometheus-fastapi-instrumentator 8.0.1
# added handling for FastAPI's internal ``_IncludedRouter`` route type (the 0.137
# regression that returned /api/health 500 when metrics are enabled). Instrumentator
# floor is raised to >=8.0.1 below to guarantee that fix is present. Verified
# /api/health green on fastapi 0.141 + instrumentator 8.0.2.
"fastapi>=0.141.1,<0.142.0",
# Generic MCP server (PRD-034 / RFC-095) — a core dev/server capability, not a separate
# extra. The official MCP Python SDK bundles FastMCP; the retrieval tools also need
# [search] (ML deps) at runtime, same as the viewer.
"mcp>=1.2.0,<2.0.0",
# Starlette TestClient (tests/conftest.py preload) requires httpx; not always
# pulled as a transitive install in minimal CI venvs.
"httpx>=0.28.1,<1.0.0",
"uvicorn[standard]>=0.49.0,<1.0.0",
# Prometheus exporter for /metrics (gated on PODCAST_METRICS_ENABLED in app.py).
"prometheus-fastapi-instrumentator>=8.0.1,<9.0.0",
# In-process feed-sweep scheduler (#708); no-op unless ``scheduled_jobs:`` in viewer_operator.yaml.
"apscheduler>=3.11.2,<4.0.0",
"black>=26.5.1,<27.0.0",
"isort>=8.0.1,<9.0.0",
"flake8>=7.3.0,<8.0.0",
"pytest>=9.0.3,<10.0.0",
"pytest-cov>=4.1.0,<8.0.0",
"pytest-xdist>=3.8.0,<4.0.0", # Parallel test execution (RFC-018)
"pytest-rerunfailures>=16.4,<17.0", # Flaky test reruns (RFC-018)
"pytest-socket>=0.8.0,<1.0.0", # Network blocking for E2E tests (RFC-019)
"pytest-json-report>=1.5.0,<2.0.0", # JSON test reports for metrics (RFC-025)
"mypy==2.3.0", # pinned exactly: a floating range let CI drift to 2.2.0 (differing jinja2 inference)
"types-PyYAML>=6.0.12,<7.0.0",
"bandit>=1.9.4,<2.0.0",
"pip-audit>=2.10.1,<3.0.0",
"build",
"rouge-score>=0.1.2,<1.0.0", # Required for ROUGE computation in evaluation loop
"jiwer>=3.0.0,<5.0.0", # Required for WER (Word Error Rate) computation
# Floor >=3.10 (Dependabot #1373, taken): 3.10 fixes 3 download/pathsec CVEs that
# ``pip_audit`` (make quality) flags against 3.9.4 — CVE-2026-12061 / 12074 / 12075
# (zip-slip, signature bypass, SSRF in ``nltk.download`` / ``data.load``). 3.10 also
# ships ``nltk.inisec``, a CWD import-security hook that is incompatible with our
# in-tree ``.venv`` (it blocks NLTK-initiated dep imports resolving inside the CWD →
# breaks eval/BLEU collection from repo root). We disable ONLY that hook via
# ``NLTK_DISABLE_IMPORT_SECURITY=1`` (set in evaluation/scorer.py, the sole nltk import
# site) — the pathsec CVE fixes are in a different module and stay in effect.
"nltk>=3.10.0,<4.0.0", # Required for BLEU computation
# Code complexity analysis (RFC-031, #424). wily dropped (#1014) — it pinned radon<5.2 and was
# near-vestigial (CI/dashboard read radon directly; only local make complexity-track used it).
"radon>=6.0.1,<6.1",
"vulture>=2.16,<3.0.0", # Dead code detection (RFC-031)
"interrogate>=1.7.0,<2.0.0", # Docstring coverage (RFC-031)
"codespell>=2.4.2,<3.0.0", # Spell checking (RFC-031)
"jsonschema>=4.26.0,<5.0.0", # Full JSON Schema validation for eval/GI/KG artifacts
"pydeps>=3.0.6,<4.0.0", # Module dependency visualization (RFC-038, #170)
"pyan3==2.4.0", # Call graph generation (Issue #425); 1.2.0 has CallGraphVisitor root bug
"code2flow>=2.5.1,<3.0.0", # Flowcharts from code (Issue #425)
# Diarization stack pins (RFC-058) — lazy-imported; keeps CI/dev venv aligned with [ml].
# Must mirror the [ml] pin: pyannote 3.x is import-broken under torchaudio>=2.9
# (AudioMetaData removed), which is exactly why [ml] moved to 4.x (#901).
"pyannote.audio>=4.0.7,<5.0",
"torchaudio",
# Must ALSO mirror the [ml] torchcodec cap: it is transitive via torchaudio/pyannote, and
# 0.15.0 is the native-teardown-SIGSEGV build (see [ml] note). CI installs `.[dev]` (not
# `.[ml]`), so without this line the resolver pulls torchcodec 0.15.0 and a worker segfaults
# mid-run once 0.15.0 is available on the index (test-unit xdist INTERNALERROR, #1355).
"torchcodec<0.15",
]
# Documentation - see docs/requirements.txt
[tool.setuptools]
# Package is now in src/ directory (src-layout)
# Use find: to automatically discover all packages (more reliable than explicit listing)
# Exclude non-package directories that might be detected incorrectly
packages = {find = {where = ["src"], exclude = ["*.egg-info", "examples", "docker"]}}
package-dir = {"" = "src"}
# Ship Jinja prompt templates with the wheel — without this setuptools
# only includes ``.py`` files, and Gemini / cloud paths fail at runtime
# with ``PromptNotFoundError`` (``.j2`` files left behind in the source
# tree). Glob covers the prompts/ subtree which is the only non-Python
# resource we ship today.
[tool.setuptools.package-data]
podcast_scraper = [
"prompts/**/*.j2",
"prompts/**/*.md",
"data/pricing_assumptions.yaml",
"enrichment/_schema/*.json",
]
[tool.black]
line-length = 100
target-version = ["py310"]
include = '\.(py|pyi)$'
exclude = '''
(
/(
\.git
| \.mypy_cache
| \.pytest_cache
| \.venv
| \.venv-dev
| \.build
| \.test_outputs
| build
| dist
| __pycache__
)/
)
'''
[tool.isort]
profile = "black"
line_length = 100
known_first_party = ["podcast_scraper"]
combine_as_imports = true
force_alphabetical_sort_within_sections = true
# Match Black/flake8: secondary venv lives in-repo for CI-parity unit tests (.venv-dev).
skip_glob = [".venv/**", ".venv-dev/**"]
[tool.mypy]
python_version = "3.10"
ignore_missing_imports = true
warn_return_any = true
warn_unused_configs = true
# Ignore attr-defined errors for Episode, RssFeed, TranscriptionJob dataclass attributes
# These are false positives - attributes exist at runtime but mypy doesn't recognize them
# when imported via TYPE_CHECKING pattern
# annotation-unchecked: bare ``dict`` / untyped bodies in tests; CI treats notes as errors.
disable_error_code = ["attr-defined", "annotation-unchecked"]
exclude = [
"^scripts/.*", # Scripts use sys.path manipulation, exclude from mypy
"^tools/.*", # Standalone Streamlit tools (not part of src package)
"^\\.venv/.*", # Local venv (make type uses ``mypy .``)
"^\\.venv-dev/.*", # CI-parity venv (see Makefile VENVDEV)
"^autoresearch/.*", # Standalone autoresearch scripts (not part of src package)
"^infra/dgx/.*-server/app\\.py$", # Docker-image app.py files (pyannote-server, whisper-server) — both named "app", not part of src package
]
[[tool.mypy.overrides]]
module = ["requests", "requests.*"]
ignore_missing_imports = true
[tool.coverage.run]
branch = true
# Measure the installed ``podcast_scraper`` package (editable install: ``src/podcast_scraper``).
# Unit, integration, and **pytest E2E** all use this config. E2E measures the full ``podcast_scraper``
# tree (no subtree ``omit``); the E2E job’s percentage is expected in the mid-30s until more E2E
# exercises server, search, GI, and related paths. Threshold: ``COVERAGE_THRESHOLD_E2E`` in the Makefile / CI.
source = ["podcast_scraper"]
# pytest-cov 7+ / coverage.py: measure code run inside ``subprocess.run([python, -m, ...])``.
# Many E2E tests invoke ``python -m podcast_scraper.cli`` in a child process; without this
# patch those runs do not contribute hits while all ``podcast_scraper`` lines still count
# toward the denominator, which drags E2E layer coverage below CI thresholds.
patch = ["subprocess"]
# Suppress "Couldn't parse ... MagicMock/join()/..." warning (fake paths from mocks/pytest-xdist)
disable_warnings = ["couldnt-parse"]
[tool.coverage.report]
show_missing = true
skip_covered = true
precision = 2
# Prevent exit 1 when coverage hits unparseable paths (e.g. MagicMock from pytest-xdist)
ignore_errors = true
# Note: fail_under is NOT set globally because different test types have different coverage:
# - Unit tests: ~70-80% (isolated, high coverage)
# - Integration tests: ~40-60% (component interactions, lower coverage)
# - E2E tests: ~30-50% (happy paths only)
# Per-test-type thresholds are enforced via --cov-fail-under in Makefile and CI:
# - Integration: 42% (COVERAGE_THRESHOLD_INTEGRATION)
# - E2E: 40% (COVERAGE_THRESHOLD_E2E; full package denominator)
# - Combined: 70% (COVERAGE_THRESHOLD_COMBINED) - enforced in make ci coverage-enforce
# See .github/workflows/python-app.yml for CI enforcement.
[tool.pytest.ini_options]
testpaths = ["tests"]
# Default: run only unit tests (fast feedback)
# Note: -m marker filter removed from addopts to avoid conflicts with explicit -m flags
# Use explicit -m flags in Makefile/CI commands instead
# No global -q: local ``make test*/ci`` recipes and ad-hoc pytest show progress (dots/bar).
# Suites that need quiet pass ``-q`` on the command line (see Makefile / pre-commit hooks).
addopts = "-ra"
# Suppress warnings from external libraries that we can't fix
filterwarnings = [
# spaCy/Click deprecation warning (external library, will be fixed in future Click version)
"ignore::DeprecationWarning:spacy.cli._util",
# Whisper FP16 warning (expected on CPU, harmless)
"ignore::UserWarning:whisper.transcribe",
# thinc/spaCy FutureWarning about torch.cuda.amp.autocast — cosmetic,
# not actionable by us. Suppressed at CLI startup (src/podcast_scraper/cli.py)
# for prod; this line is the pytest-side equivalent so acceptance tests
# don't clutter logs with the same message. Track: #436 (upstream fix
# will let us drop both suppressions). See docs/guides/DEPENDENCIES_GUIDE.md.
"ignore:.*torch\\.cuda\\.amp\\.autocast.*deprecated.*:FutureWarning:thinc",
]
# Parallel execution: use -n auto to enable, or -n 0 to disable
# Reruns: use --reruns 2 --reruns-delay 1 for flaky tests
markers = [
"analysis: diagnostic/analysis tools (not run in regular test suites)",
"analytical: analytical/diagnostic test tools (separate from regular tests, in tests/analytical/)",
"unit: marks tests as unit tests (deselect with '-m \"not unit\"')",
"multi_episode: multi-episode tests with multiple short episodes (10-15 seconds each) for testing multi-episode processing logic",
"data_quality: data quality validation tests with multiple episodes (nightly only)",
"nightly: marks tests as nightly-only (comprehensive tests with production models, not run in regular CI)",
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
"integration: integration tests (slower, test component interactions)",
"e2e: end-to-end workflow tests (slowest, test full workflows)",
"network: hits the network (off by default, use -m network to enable)",
"ml_models: requires ML dependencies and real model loading (requires openai-whisper, spacy, transformers)",
"diarization: real pyannote speaker-diarization tests (requires pyannote.audio + a gated HF token; RFC-058)",
"critical_path: marks tests as part of the critical path (RSS → Parse → Download/Transcribe → NER → Summarization → Metadata → Files). These tests run in fast suite regardless of ml_models marker if models are cached.",
"app: consumer-app / server-route integration tests (auto-applied to tests/integration/server/test_app_*.py). Run in the fast PR suite alongside critical_path so app-route coverage lands on PRs, not only post-merge on main.",
"llm: tests that use LLM APIs (may incur costs or rate limits, includes openai and future providers)",
"openai: tests that use OpenAI API specifically (subset of llm marker)",
"gemini: tests that use Gemini API specifically (subset of llm marker)",
"mistral: tests that use Mistral API specifically (subset of llm marker)",
"deepgram: tests that use the Deepgram API specifically (subset of llm marker)",
"anthropic: tests that use Anthropic API specifically (subset of llm marker)",
"deepseek: tests that use DeepSeek API specifically (subset of llm marker)",
"ollama: tests that use Ollama API specifically (subset of llm marker, local/offline)",
"grok: tests that use Grok API specifically (subset of llm marker)",
"golden: golden tests with known stable episodes for regression testing (Issue #379)",
"chaos: chaos tests for error conditions and edge cases (broken RSS, 404, large files, non-English) (Issue #379)",
"offline: offline tests with networking disabled (verifies air-gapped operation) (Issue #379)",
"integration_http: HTTP integration tests (use real HTTP client with test server, not external network)",
"infrastructure: tests for test infrastructure itself (pytest markers, Makefile, CI config)",
"serial: marks tests that must run sequentially (not in parallel) due to resource conflicts or race conditions",
"flaky: marks tests as potentially flaky (may fail intermittently in parallel execution, should use reruns)",
"module_config: tests for config.py and config_constants.py",
"module_cli: tests for cli.py",
"module_service: tests for service.py",
"module_workflow: tests for workflow/*.py",
"module_downloader: tests for rss/downloader.py",
"module_rss_parser: tests for rss/parser.py",
"module_ml_providers: tests for providers/ml/*.py",
"module_openai_providers: tests for providers/openai/*.py",
"module_gemini_providers: tests for providers/gemini/*.py",
"module_anthropic_providers: tests for providers/anthropic/*.py",
"module_mistral_providers: tests for providers/mistral/*.py",
"module_deepseek_providers: tests for providers/deepseek/*.py",
"module_ollama_providers: tests for providers/ollama/*.py",
"module_grok_providers: tests for providers/grok/*.py",
"module_summarization: tests for summarization/*.py",
"module_transcription: tests for transcription/*.py",
"module_speaker_detection: tests for speaker_detectors/*.py",
"module_preprocessing: tests for preprocessing/*.py",
"module_cache: tests for cache/*.py",
"module_evaluation: tests for evaluation/*.py",
"module_utils: tests for utils/*.py",
"module_models: tests for models.py",
"module_exceptions: tests for exceptions.py",
"module_prompts: tests for Jinja prompt templates under prompts/ (offline contract tests)",
]
# Parallel execution configuration (pytest-xdist)
# Use -n auto to automatically detect CPU count, or -n N for specific number
# Use -n 0 to disable parallel execution
# Reruns configuration (pytest-rerunfailures)
# Use --reruns N to retry failed tests N times
# Use --reruns-delay N to wait N seconds between retries
# Network blocking configuration (pytest-socket) for E2E tests
# Block all external network calls except localhost/127.0.0.1
# This is configured per-test-directory in conftest.py files
[tool.interrogate]
ignore-init-module = true
ignore-init-method = true
ignore-magic = true
ignore-semiprivate = true
ignore-private = true
ignore-property-decorators = true
ignore-module = true
ignore-nested-functions = true
fail-under = 100
exclude = ["tests", "scripts"]
verbose = 1