Repository navigation
Expand file tree
/
Copy pathpyproject.toml
More file actions
141 lines (133 loc) · 6.51 KB
/
Copy pathpyproject.toml
File metadata and controls
141 lines (133 loc) · 6.51 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
[project]
name = "sturnus"
version = "0.17.0" # x-release-please-version
description = "Discord voice transcription with Outline document output"
readme = "README.md"
requires-python = ">=3.12"
authors = [{ name = "OneLiteFeather" }]
license = "AGPL-3.0-or-later"
dependencies = [
"sqlalchemy>=2.0.44",
"asyncpg>=0.31.0",
"alembic>=1.14.0",
# asyncpg drives the application's own queries, but Alembic runs its
# migrations synchronously and the worker runs them at startup before
# anything else (`sturnus.entrypoints.worker`), so the sync driver is a
# runtime dependency of the shipped image, not just of the test suite.
"psycopg[binary]>=3.2",
"pydantic-settings>=2.12.0",
"cryptography>=44.0",
"soxr>=0.5",
"numpy>=2.1",
"boto3>=1.35",
"discord-py>=2.6.4",
"discord-ext-voice-recv>=0.5.2a179",
"aiohttp>=3.10",
"jinja2>=3.1",
"httpx>=0.28",
# Transcription runs against an OpenAI-compatible `/audio/transcriptions`
# endpoint (`sturnus.infrastructure.openai_transcription`). The official
# SDK rather than a hand-rolled `httpx` POST: vLLM documents this client
# against its own transcription endpoint, so "OpenAI-compatible" is tested
# by somebody other than us, and it supplies the typed `TranscriptionVerbose`
# this `mypy --strict` tree would otherwise have to narrow out of
# `dict[str, Any]`, retries honouring `Retry-After`, per-request timeouts
# and a native `async` call. Its added weight is `distro`, `jiter` and
# `sniffio`; `httpx`, `pydantic`, `anyio` and `tqdm` are already here.
"openai>=1.60",
# Error reporting (sturnus.infrastructure.observability). A runtime
# dependency rather than an optional extra because one image serves
# operators with and without a Sentry instance: optionality has to be a
# runtime decision (is STURNUS_SENTRY_DSN set?), not an install-time one.
# Without a DSN `sentry_sdk.init()` is never called, so nothing is
# patched and the processes behave exactly as they do without it.
"sentry-sdk>=2.68",
# Traces and metrics (sturnus.infrastructure.telemetry). Runtime
# dependencies for the same reason as sentry-sdk above: one image serves
# operators with and without a collector, so optionality is a runtime
# decision (is STURNUS_OTEL_EXPORTER_OTLP_ENDPOINT set?) rather than an
# install-time one. With no endpoint no provider is constructed, so the
# API degrades to NonRecordingSpan and no-op instruments and the
# processes behave exactly as they do without it.
#
# `-proto-http` rather than `-proto-grpc`: Alloy accepts both 4317 and
# 4318, so the choice is free, and the gRPC exporter drags in grpcio --
# a large per-arch binary wheel and a known source of fork/thread hangs.
#
# **Deliberately no `opentelemetry-instrumentation-*` package.** Every
# plausible one is disqualifying in this codebase specifically:
# -sqlalchemy adds `db.statement`, and the statement that writes a
# transcript *is* the protected content; -botocore adds `aws.s3.key`,
# and the key embeds a Discord user id; -httpx adds `url.full`, which
# for S3 is a presigned URL carrying X-Amz-Signature; -aiohttp-server
# would attach `link`'s `/oauth/callback?code=...`. None of the four
# packages below instruments anything on its own.
"opentelemetry-api>=1.44",
"opentelemetry-sdk>=1.44",
"opentelemetry-semantic-conventions>=0.65b0",
"opentelemetry-exporter-otlp-proto-http>=1.44",
]
[project.scripts]
sturnus-api = "sturnus.entrypoints.api:main"
sturnus-bot = "sturnus.entrypoints.bot:main"
sturnus-link = "sturnus.entrypoints.link:main"
sturnus-worker = "sturnus.entrypoints.worker:main"
[build-system]
requires = ["uv_build>=0.12.1,<0.13.0"]
build-backend = "uv_build"
[dependency-groups]
lint = ["mypy>=1.19.0", "ruff>=0.14.7"]
test = [
"moto[s3]>=5.0",
"pytest>=9.0.1",
"pytest-asyncio>=1.3.0",
"testcontainers[postgres]>=4.9.0",
]
[tool.ruff]
line-length = 100
target-version = "py312"
# ruff >=0.15 lints/formats *.md by default; docs/ holds prose specs and plans,
# not source, and their embedded code fences are illustrative, not real modules.
# migrations/versions/ is regenerated verbatim by `alembic revision --autogenerate`;
# hand-editing it to satisfy the lint config would only drift from what the tool
# produces next time. migrations/env.py is hand-written logic (URL resolution) and
# stays linted like the rest of the source tree.
extend-exclude = ["docs", "migrations/versions"]
[tool.ruff.lint]
# `G` (flake8-logging-format) and `LOG` are privacy controls here, not style
# rules. Sentry's logging integration forwards `LogRecord.msg` -- the raw
# format string -- and `sturnus.infrastructure.observability` keeps only that
# field. That guarantee holds only while `msg` is a literal written in
# reviewable source: `log.error(f"failed for {transcript}")` would put the
# transcript itself into `msg`, and no scrubbing hook can tell it apart from
# a template. `G001`-`G004` forbid exactly those spellings (`.format()`,
# `%`, `+`, f-string), leaving `%s`-style lazy formatting as the only way to
# get runtime values into a log line. Zero violations across the tree today.
select = ["E", "F", "I", "UP", "B", "SIM", "ARG", "G", "LOG"]
[tool.mypy]
python_version = "3.12"
strict = true
# scripts/ is included because check_lock_version.py runs in CI: a script
# the pipeline depends on deserves the same checking as the package.
files = ["src", "tests", "scripts"]
# pydantic-settings populates its fields from the environment, so a
# zero-argument Settings() is correct. Without this plugin mypy reads it as a
# call missing every required argument.
plugins = ["pydantic.mypy"]
# These three ship no type information. Strictness stays intact for our own
# code; without this block the test run fails on import-untyped without an
# actual defect in the code.
[[tool.mypy.overrides]]
module = ["testcontainers.*", "alembic.*", "psycopg.*"]
ignore_missing_imports = true
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
# The repository root, so `scripts/` is importable. `scripts` is not part of
# the installed package -- it holds tooling the CI jobs run -- but
# `component_credentials.py` decides which credentials the chart hands each
# container, which is worth a test rather than only a workflow.
pythonpath = ["."]
markers = [
"slow: exercises real model inference (e.g. downloads a Whisper model from HuggingFace); deselected on pull requests, run on push to main and on a schedule -- see .github/workflows/build.yml",
]