Files
Spoolman/pyproject.toml
T
Donkie 07396abbcb client_v2: authentication plumbing, sign-in and claim screens
Wires the client to the auth API and adds the two screens that let someone
sign in or claim an unclaimed instance.

The load-bearing property is that when the server reports authentication is
disabled, the store answers true to every capability getter. No call site has
to know whether auth is on, and the default configuration renders exactly as
it did before any of this existed.

http.ts
  All six verb helpers now send credentials, and the four that carry a body
  attach the CSRF token from the readable companion cookie. ensureOk routes
  401 to a sign-out and 403 to a toast, except under /auth/ — a rejected
  sign-in is an answer, not a sign-out, and routing it globally would make a
  typo'd password look like an expired session.

  The handlers are registered by the store rather than imported here, because
  the store already imports this module and the reverse would be a cycle.

search.ts
  Moved onto getJson. It was the only hand-rolled fetch left in the app, so
  with auth on it would have sent no cookie and silently returned nothing.
  Its one caller already guards with isAbortError; the thrown type widens
  from Error to HttpError, which carries status.

live.ts
  onclose ignored the close code entirely, so an auth rejection would have
  been retried on the backoff loop forever against a server that can never
  accept it. 4401 and 4403 now stop the socket and hand off to the store.
  Subscribers are kept, so the new rearm() brings every view back after
  signing in without re-subscribing — deliberately unlike teardown(), which
  drops the map entry. This only works because the backend accepts the
  handshake before closing; a pre-accept close never delivers its code.

+layout.svelte
  The data effect is split. Live-sync no longer starts until the user can
  actually read, since otherwise it opens sockets the server closes on sight.
  Nothing renders at all until auth state is known, because showing the app
  and then replacing it flashes an interface at someone who cannot use it.
  Auth screens swap in place of the shell rather than redirecting to a route,
  which keeps the deep link the user arrived on.

Screens are components rather than routes for that reason. They are the
first real form in this codebase — everything else is change-on-blur
auto-save — so PasswordInput, the checkbox and the submit button are new.
They use a real <form> for Enter-to-submit and password-manager support,
carry proper autocomplete attributes, and keep every control at the 44px tap
target the mobile a11y audit enforces.

36 auth.* strings added to the English locale only; every other locale
belongs to Weblate.

Also exempts S105/S106 in tests. Those rules flag string literals that look
like passwords, and authentication tests have to name the credentials they
sign in with; the list already exempts S101 for the same reason.

Verified: svelte-check reports 0 errors and 0 warnings across 1321 files,
eslint is clean, prettier is clean on all tracked source, and the production
build succeeds.
2026-07-28 10:08:58 +02:00

116 lines
3.3 KiB
TOML

[project]
name = "spoolman"
version = "0.24.0"
description = "A web service that keeps track of 3D printing spools."
license = "MIT"
readme = "README.md"
authors = [{ name = "Donkie", email = "daniel.cf.hultgren@gmail.com" }]
dependencies = [
"uvicorn~=0.34",
"httptools>=0.6.4; platform_machine != \"armv7l\"",
"uvloop!=0.15.0,!=0.15.1,>=0.21.0; platform_machine != \"armv7l\" and sys_platform != \"win32\" and (sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\")",
"fastapi~=0.115",
"SQLAlchemy[aiomysql,aiosqlite,asyncio,postgresql_asyncpg]~=2.0",
"pydantic~=2.10",
"platformdirs~=4.3",
"alembic~=1.15",
"scheduler~=0.8",
"sqlalchemy-cockroachdb~=2.0",
"asyncpg~=0.30",
"psycopg2-binary~=2.9",
"setuptools~=78.1",
"WebSockets~=15.0",
"prometheus-client~=0.21",
"httpx~=0.28",
"hishel~=0.1",
"aiomysql[rsa]~=0.3",
]
requires-python = ">=3.14"
[dependency-groups]
dev = [
"ruff==0.14.10",
"pytest~=9.1.1",
"pytest-asyncio~=1.4.0",
"httpx~=0.28.1",
"poethepoet>=0.40.0",
]
[tool.poe.tasks.generate-docs]
script = "spoolman.docs:generate_docs"
help = "Generate API documentation for Spoolman."
[tool.poe.tasks.bump]
script = "spoolman.bump:bump"
help = "Bump Spoolman version."
[tool.poe.tasks.run]
cmd = "uvicorn spoolman.main:app"
help = "Start Spoolman."
[tool.poe.tasks.itest]
cmd = "python tests_integration/run.py"
help = "Builds Spoolman and runs integration tests on the backend against all supported databases."
[tool.poe.tasks.itest-frontend]
cmd = "python tests_frontend/run.py"
help = "Builds Spoolman and runs Playwright frontend integration tests against a real deployment."
[tool.bumpversion]
current_version = "0.22.1"
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
serialize = ["{major}.{minor}.{patch}"]
allow_dirty = false
[[tool.bumpversion.files]]
filename = "pyproject.toml"
search = 'version = "{current_version}"'
replace = 'version = "{new_version}"'
[tool.ruff]
line-length = 120
target-version = "py314"
extend-exclude = ["client_v2"]
[tool.ruff.lint]
select = ["ALL"]
ignore = [
"A003",
"COM812",
"D101",
"D104",
"D203",
"D213",
"D406",
"D407",
"DTZ003",
"EM101",
"EM102",
"FA100",
"FIX002",
"PLR0913",
"S104",
"S603",
"SIM108",
# TC001/2/3 want type-only imports moved into `if TYPE_CHECKING:` blocks. That is
# wrong here: FastAPI resolves `Annotated[AsyncSession, Depends(...)]` and websocket
# parameters at runtime, Pydantic resolves field types at runtime, and SQLAlchemy
# resolves `Mapped[...]` at mapper-configuration time. A TYPE_CHECKING-only import is
# absent from the runtime namespace, so applying these breaks dependency injection.
# Ruff marks all of them as unsafe fixes for exactly this reason. They only began
# firing when target-version moved to py314 (PEP 649 deferred annotations).
"TC001",
"TC002",
"TC003",
"TD002",
"TD003",
"TRY003",
"TRY201",
]
[tool.ruff.lint.per-file-ignores]
# S105/S106 flag string literals that look like passwords. Authentication tests have to
# name the credentials they sign in with, so the rule only produces noise there.
"tests*/*" = ["ANN201", "ASYNC210", "S101", "S105", "S106", "PLR2004", "D103", "TID252"]
"migrations/versions/*" = ["N999"]