Repository navigation
Run on (and use) the Python 3.12 floor - #967
Merged
Merged
Conversation
`CoverCreateThread.stop()` called `ProcessPoolExecutor.terminate_workers()`, which only exists on 3.14. On the declared 3.12 floor (and 3.13) stopping the librarian raised AttributeError; a type-ignore hid it. Probe for the method, and cancel pending futures in `shutdown()` so the older Pythons still drop the queued tail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sidecar connection used legacy `isolation_level=None` autocommit, where `with conn:` never issues BEGIN, so the schema + version stamp and `_clear_sidecar`'s "single transaction" truncate were never transactions: a failure part-way left a half-cleared sidecar. Switch to 3.12's `autocommit=True` and run multi-statement writes through an explicit BEGIN/COMMIT/ROLLBACK `transaction()`. Single-statement upsert/delete drop their no-op `with conn:`. The backup's `VACUUM INTO` connection gets the same kwarg swap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…basenames `_query_failed_import_deletes` casefolded the whole path and compared it to each sibling's casefolded basename, so it never matched: every untouched failed import was marked missing and deleted after any import. Compare basenames, case sensitively as the comment intends (`exists()` is case insensitive on macOS). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both lookup tables were matched in insertion order by the first `endswith`/`startswith` hit. `b` came before `kb`, so `size:10kb` parsed `10k` as a number and raised; `>` came before `>=`, so `>=5` became `>` with the value `=5`. The search filter swallows the error and silently drops the term. Put the longer keys first and strip the operator with `removeprefix`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed dateutil `dateutil` reached the OPDS v1 entry only transitively (via `dateparser`). `datetime.fromisoformat` (3.11+) reads the ISO 8601 forms Django and SQLite emit. A string input now takes the same UTC isoformat path as a datetime instead of returning a bare datetime, so `updated`/`published` are honestly `str | None`; naive values are read as UTC, as Django stores them. The `types-python-dateutil` stubs go with the import. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One `asyncio.timeout` around the drain loop replaces the hand-kept deadline and per-iteration `wait_for`. A timeout mid-`get()` drops only the getter, never a queued worker, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 17 `for start in range(0, len(seq), N): batch = seq[start:start + N]` loops become `for batch in batched(seq, N):`; abort-event guards stay first in the body. `tuple(...)` wrappers that existed only to allow slicing go, except where the snapshot or a type-ignore still earns its keep. Batches are tuples, which every consumer (`__in` lookups, `in_bulk`, cursor params, `len()`) already accepts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e, frozen dataclasses
- watcher `expand_dir_added` walks with `Path.walk()` (3.12). Unfollowed
directory symlinks now land in `filenames` instead of `dirnames`; they
still yield no event unless named like a comic archive.
- `is_relative_to` replaces two try/`relative_to`/except ValueError probes.
- `datetime.UTC` replaces `ZoneInfo("UTC")`.
- `str.removeprefix` strips the ASGI root path.
- `functools.cache` for the zero-arg OPDS schema loaders.
- `BookmarkKey` is `frozen` (it already wrote through `object.__setattr__`
and keeps its explicit hash/eq); `_LastRouteKey` gains `slots`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nment - `_coalesce[T]` and `timed_step[T]` replace `Any` round-trips. - `cache_control_2xx` gains a `[**P, R: HttpResponseBase]` signature, the codebase's first ParamSpec. - `type Resolution = ...` for the online-tag answer tuple. - `-> Self` on `ComicACL.for_user` and `SidecarStore.in_memory`. - 3.12's `value in EnumClass` replaces three frozensets of member values. Two of them gate telemetry buckets: the vocabularies are the same closed Codex enums as before, so nothing new is collected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- `a | b` replaces `{**a, **b}` where both sides are plain dicts. The admin
URL action maps and the reader settings lookups keep the unpack literal:
DRF's invariant `dict[str, str | ViewSetAction]` parameter and the
base view's `FILTER_ARGS: Mapping` need the literal's inferred type.
- Durations time with `perf_counter()` and the import write-wait deadline
and mock-comic progress interval with `monotonic()`, so a wall-clock
step can't skew them. The importer keeps its wall-clock `start_time`
(stamped into the DB) and gains a `started` counter for the finish log.
- f-strings replace `+ str(...)` concatenation in model reprs and
`get_sort_name`, the last a PEP 701 nested-quote f-string.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`Notifications` becomes a `StrEnum` like `ChannelGroups` and `WebsocketMessages`, dropping `.value` at all 28 sites. Members hash, compare, pickle and `json.dumps` as their string values, so the notifier dedup, the typed-payload lookup and the websocket wire format are unchanged. 27 hand-rolled `if self._x is None:` memo properties become `functools.cached_property`, dropping their backing attributes and the `__init__` lines (and four whole `__init__`s) that only seeded them: - `params` on the browser view and its three load-only overrides; `set_params` assigns the cached value directly. - `model_collection` and its `BrowserView` override. - `is_admin`, losing `init_is_admin` (only ever run from `__init__`). - `user_agent_name`, `admin_flags` (the publications preview still shares it by assignment), and the OPDS facet, validation, ordering, breadcrumb and stats memos. `OPDS2LinksView.num_pages` stays a plain property, reading the memoized `collection_and_books`, because it overrides a property. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
12 codex and 47 test modules have no annotation that needs deferring: no `TYPE_CHECKING`-only name and no forward reference in a runtime-evaluated annotation. Each was checked by removing the import, running ruff (TC004/F821) and importing the module on Python 3.12, where annotations are still evaluated eagerly; the three perf scripts, which need the debug-only silk stack to import, were checked statically. The other 42 modules keep the import because ruff moved their annotation-only imports under `TYPE_CHECKING`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… state the floor - Drop `tomli` (its `< 3.11` marker is always false on the 3.12 floor; every TOML reader uses `tomllib`) and `typing-extensions` (no direct imports) from the runtime dependencies. - Classifiers name Python 3.12, 3.13 and 3.14, and Django 6.0 instead of 5.1. - basedpyright fails on a missing `@override` (already zero violations). - The README and the Windows guide state the Python 3.12 minimum. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Codex declares
requires-python = ">=3.12", but the venv, Docker and CI all run 3.14, so nothing ever ran on the floor. This branch fixes the one place where that mattered, fixes four more bugs found along the way, and adopts the 3.9–3.12 stdlib and typing features the code wasn't using yet. The bug fixes come first so each one cherry-picks on its own.Bug fixes
CoverThread.stop()calledProcessPoolExecutor.terminate_workers(), which only exists on 3.14 (a type-ignore hid it), so stopping the librarian raisedAttributeErroron 3.12/3.13.stop()now checks for the method first. Shutdown on 3.12/3.13: pending renders are cancelled, and any render already in progress finishes before the pool shuts down. On 3.14 in-flight workers are still SIGTERMed as before.isolation_level=None, wherewith conn:never issuesBEGIN. The schema stamp and_clear_sidecar's "single transaction" truncate were never transactions, so a failure part-way left a half-cleared sidecar. It now usesautocommit=Trueplus an explicittransaction().size:10kbparsed10kas a number and failed (thebsuffix matched first), and>=5/<=5parsed as>/<with the value=5. In both cases the term was silently dropped.dateutil, which is only a transitive dependency. They now usedatetime.fromisoformat, and a string date gets the same UTC isoformat output as a datetime.Modernization
asyncio.timeout;itertools.batched(17 loops);Path.walk,is_relative_to,datetime.UTC,removeprefix,functools.cache, frozen dataclasses; PEP 695 generics (incl. the first ParamSpec),typealias,Self,value in EnumClass; dict|;perf_counter/monotonicinstead oftime()for durations; f-strings;Notifications→StrEnum; 27 memo properties →cached_property; 59 unneededfrom __future__ import annotationsremoved.Packaging and tooling
tomli(3.11 marker) andtyping-extensionsdependencies, and thetypes-python-dateutilstubs.reportImplicitOverride = "error"(there were already zero violations).docs/WINDOWS.mdstate the 3.12 minimum.Telemetry
Two telemetry buckets (
count_statsidentifier types,admin_statsdefault effort) now checkvalue in IdentifierType/in EffortChoicesinstead of a frozenset of those enums' values. The vocabularies are the same closed Codex enums, so nothing new is collected. The telemetry privacy rule is unaffected.Where the plan didn't hold
{**a, **b}. DRF'sas_view()parameter is an invariantdict[str, str | ViewSetAction], and the base view declaresFILTER_ARGS: Mapping, soa | bdoesn't type-check at those sites.codex/settings/db.pykeeps its__future__import, since its model types are imported only underTYPE_CHECKING. That makes 59 removals, not 60.Test plan
make fix,make lint(ruff, basedpyright with the new gate, vulture, complexipy, radon, codespell),make tymake test-pythonon 3.14_clear_sidecaratomicity, untouched failed-import row survives,>=10kb/<=5parsingNot changed
multiprocessingstart method is stillfork, which 3.12 deprecates from multi-threaded processes (3.14 defaults toforkserver). That's a behavior and performance decision, so it's left alone here.views/browser/annotate/cover.pythat predate this branch:_DYNAMIC_COVERS: Final = Truefrom Pin five settings on and take their toggles away #910.🤖 Generated with Claude Code