audio streaming app
plyr.fm
1[project]
2name = "backend"
3dynamic = ["version"]
4description = "decentralized music streaming platform built on ATProto"
5authors = [{ name = "zzstoatzz", email = "thrast36@gmail.com" }]
6dependencies = [
7 "fastapi>=0.115.0",
8 "uvicorn[standard]>=0.34.0",
9 "httpx>=0.28.0",
10 "pydantic>=2.11.0",
11 "pydantic-settings>=2.7.0",
12 "python-dotenv>=1.1.0",
13 "sqlalchemy>=2.0.36",
14 "alembic>=1.14.0",
15 "asyncpg>=0.30.0",
16 "atproto @ git+https://github.com/zzstoatzz/atproto@oauth-full",
17 "boto3>=1.37.0",
18 "python-multipart>=0.0.20",
19 "python-jose[cryptography]>=3.3.0",
20 "passlib[bcrypt]>=1.7.4",
21 "psycopg[binary]>=3.2.12",
22 "greenlet>=3.2.4",
23 "logfire[fastapi,sqlalchemy]>=4.14.2",
24 "cachetools>=6.2.1",
25 "pytest-asyncio>=0.25.3",
26 "aioboto3>=15.5.0",
27 "slowapi @ git+https://github.com/zzstoatzz/slowapi.git@fix-deprecation",
28 "orjson>=3.11.4",
29 "mutagen>=1.47.0",
30 "pydocket>=0.15.2",
31 "redis>=7.1.0",
32 "beartype>=0.22.8",
33 "turbopuffer>=0.5.0",
34 "Pillow>=11.0.0",
35 "feedgen>=1.0.0",
36]
37
38requires-python = ">=3.11"
39readme = "README.md"
40license = "Apache-2.0"
41
42keywords = ["music", "streaming", "atproto", "bluesky", "decentralized"]
43
44[dependency-groups]
45dev = [
46 "dirty-equals>=0.9.0",
47 "ipython>=8.12.3",
48 "pdbpp>=0.10.3",
49 "plyrfm @ git+https://github.com/zzstoatzz/plyr-python-client#subdirectory=packages/plyrfm",
50 "prek>=0.2.13",
51 "pytest>=8.3.3",
52 "pytest-asyncio>=1.0.0",
53 "pytest-cov>=6.1.1",
54 "pytest-env>=1.1.5",
55 "pytest-timeout>=2.4.0",
56 "pytest-xdist>=3.6.1",
57 "ruff>=0.12.0",
58 "httpx>=0.28.0",
59 "ty>=0.0.1a25",
60]
61
62[build-system]
63requires = ["hatchling", "uv-dynamic-versioning>=0.7.0"]
64build-backend = "hatchling.build"
65
66[tool.hatch.version]
67source = "uv-dynamic-versioning"
68
69[tool.hatch.metadata]
70allow-direct-references = true
71
72[tool.uv-dynamic-versioning]
73vcs = "git"
74style = "pep440"
75bump = true
76fallback-version = "0.0.0"
77
78[tool.pytest.ini_options]
79asyncio_mode = "auto"
80asyncio_default_fixture_loop_scope = "function"
81timeout = 10
82env = [
83 "RELAY_TEST_MODE=1",
84 "OAUTH_ENCRYPTION_KEY=hnSkDmgbbuK0rt7Ab3eJHAktb18gmebsdwKdTmq9mes=",
85 "LOGFIRE_IGNORE_NO_CONFIG=1",
86 # reduce connection pool for tests to avoid exhausting Neon's connection limit
87 "DATABASE_POOL_SIZE=2",
88 "DATABASE_MAX_OVERFLOW=0",
89 # redis URL for cache tests (uses test-redis from docker-compose)
90 # D: prefix means don't override if already set (e.g., by CI workflow)
91 "D:DOCKET_URL=redis://localhost:6380/0",
92 # disable automatic perpetual task scheduling in tests to avoid event loop issues
93 "DOCKET_SCHEDULE_AUTOMATIC_TASKS=false",
94]
95markers = [
96 "integration: marks tests as integration tests (deselect with '-m \"not integration\"')",
97]
98pythonpath = ["."]
99testpaths = ["tests"]
100python_files = ["test_*.py", "*_test.py"]
101python_classes = ["Test*"]
102python_functions = ["test_*"]
103filterwarnings = [
104 "ignore::pydantic.warnings.UnsupportedFieldAttributeWarning",
105]
106
107[tool.ruff.lint]
108fixable = ["ALL"]
109ignore = [
110 "COM812",
111 "PLR0913", # Too many arguments
112 "SIM102", # Dont require combining if statements
113]
114extend-select = [
115 "B", # flake8-bugbear
116 "C4", # flake8-comprehensions
117 "I", # isort
118 "PIE", # flake8-pie
119 "RUF", # Ruff-specific
120 "SIM", # flake8-simplify
121 "UP", # pyupgrade
122]
123
124[tool.ruff.lint.per-file-ignores]
125"__init__.py" = ["F401", "I001"]
126"tests/**/*.py" = ["S101"] # Allow assert in tests
127"src/backend/api/**/*.py" = [
128 "B008",
129] # Allow Depends() and File() in FastAPI route defaults
130"src/backend/main.py" = ["E402"] # Need warnings filter before imports
131
132[tool.coverage.run]
133source = ["src"]
134omit = ["tests/*", "sandbox/*"]
135
136[tool.coverage.report]
137exclude_lines = [
138 "pragma: no cover",
139 "def __repr__",
140 "raise AssertionError",
141 "raise NotImplementedError",
142 "if __name__ == .__main__.:",
143 "if TYPE_CHECKING:",
144]
145
146[tool.ty.src]
147include = ["src", "tests"]
148exclude = [
149 "**/node_modules",
150 "**/__pycache__",
151 ".venv",
152 ".git",
153 "dist",
154 "frontend",
155]
156
157[tool.ty.environment]
158python-version = "3.11"
159
160[tool.ty.rules]
161# start with basic checks, can tighten later
162unknown-argument = "ignore"
163no-matching-overload = "ignore"