Rewrite Python comments in Google developer documentation style (#12)

* Rewrite comments in Google developer documentation style

Rewrite the comments and docstrings across the backend core modules so they
read plainly. The previous prose was accurate but dense and figurative, which
made it slow to skim.

Applies the Google developer documentation style guide: short sentences, active
voice, present tense, American spelling, and no metaphors, idioms, or
rhetorical asides. Replaces em-dash chains with separate sentences.
This commit is contained in:
Parth
2026-08-26 15:37:25 +05:30
committed by GitHub
parent cf6161a5ee
commit e7d75c3b05
83 changed files with 4605 additions and 3988 deletions
+35 -31
View File
@@ -1,10 +1,11 @@
"""Retention policy for throwaway guest accounts.
In multi-user mode every first visit mints a `users` row (GET /api/auth/me),
so a public demo accumulates one account per curious visitor — most of whom
never come back, each leaving behind whatever scenarios, adventures, actions
and memories they generated. This drops guests that have gone quiet for
AIDND_GUEST_RETENTION_DAYS (default 5) along with everything they made.
In multi-user mode every first visit creates a `users` row through
`GET /api/auth/me`, so a public demo accumulates one account per visitor. Most
of those visitors never return, and each one leaves behind whatever scenarios,
adventures, actions, and memories they generated. This module deletes guests
that have been inactive for `AIDND_GUEST_RETENTION_DAYS`, which defaults to 5,
along with everything they made.
Why this is safe to run unattended:
@@ -12,25 +13,26 @@ Why this is safe to run unattended:
clauses are checked rather than either alone. Registering upgrades the row
in place (is_guest -> False), so a guest who signs up keeps everything;
local mode's implicit single user is also is_guest=False.
- Idle time is COALESCE(last_seen_at, created_at). `auth._touch` only writes
last_seen_at once an hour, and a guest minted by /auth/me has NULL until
its *second* request, so created_at is the honest floor for a brand-new
visitor — without the coalesce those rows look infinitely old.
- Nothing a guest owns is reachable by anyone else: `is_public` is an
output-only field (see schemas.ScenarioBase), so only seeded scenarios —
which have user_id NULL and are therefore outside this filter entirely —
are shared. Deleting a guest can't take content away from another user.
- Idle time is `COALESCE(last_seen_at, created_at)`. `auth._touch` writes
`last_seen_at` at most once an hour, and a guest created by `/auth/me` has
NULL there until its second request, so `created_at` is the correct floor for
a new visitor. Without the coalesce, those rows look arbitrarily old.
- Nothing a guest owns is reachable by anyone else. `is_public` is an
output-only field, as `schemas.ScenarioBase` shows, so the only shared
scenarios are the seeded ones, which have a NULL `user_id` and are outside
this filter. Deleting a guest cannot remove content from another user.
Why one Core DELETE instead of an ORM cascade: `db.delete(user)` would SELECT
every adventure, action, memory and story card into Python purely to delete
them, which on Neon is exactly the egress pattern that has already cost this
project once. Every foreign key from users downwards is ON DELETE CASCADE
(users -> scenarios/adventures/scripts/settings -> actions/memories/cards), so
the database does the whole graph in one statement and ships back a row count.
The sweep uses one Core DELETE rather than an ORM cascade. `db.delete(user)`
would SELECT every adventure, action, memory, and story card into Python only to
delete them, which on Neon is the egress pattern that has already cost this
project once. Every foreign key from `users` downward is ON DELETE CASCADE, from
users to scenarios, adventures, scripts, and settings, and from those to actions,
memories, and cards, so the database deletes the whole graph in one statement and
returns a row count.
No index is added for the scan: the sweep runs a handful of times a day
against a table with at most a few thousand rows, which is not worth a
migration and the schema surface that comes with it.
The scan gets no index. The sweep runs a few times a day against a table holding
at most a few thousand rows, which does not justify a migration and the schema
surface it adds.
"""
import asyncio
@@ -56,8 +58,8 @@ def _int_env(name: str, default: int) -> int:
return default
# Days of inactivity before a guest account is dropped. 0 or less disables the
# policy entirely, for a deployment that would rather keep everything.
# Days of inactivity before a guest account is deleted. A value of 0 or less
# disables the policy, for a deployment that keeps everything.
RETENTION_DAYS = _int_env("AIDND_GUEST_RETENTION_DAYS", 5)
# How often a long-lived process re-checks. Hours, not minutes: nothing here is
@@ -86,10 +88,11 @@ def delete_stale_guests(db: Session, *, now: datetime | None = None) -> int:
"""
if RETENTION_DAYS <= 0:
return 0
# Stored timestamps are naive UTC on both backends (SQLite drops tzinfo;
# Postgres columns are TIMESTAMP WITHOUT TIME ZONE with the session pinned
# to UTC in database.py). Match that exactly so the comparison can't hinge
# on how a given dialect renders an aware value.
# Stored timestamps are UTC without a timezone on both backends. SQLite
# drops the timezone, and the Postgres columns are TIMESTAMP WITHOUT TIME
# ZONE with the session pinned to UTC in `database.py`. Match that, so the
# comparison does not depend on how a dialect renders a value that carries a
# timezone.
reference = now or models.utcnow()
cutoff = reference.replace(tzinfo=None) - timedelta(days=RETENTION_DAYS)
@@ -100,9 +103,10 @@ def delete_stale_guests(db: Session, *, now: datetime | None = None) -> int:
models.User.email.is_(None),
func.coalesce(models.User.last_seen_at, models.User.created_at) < cutoff,
)
# Without this, "auto" can't evaluate coalesce in Python and falls back
# to fetching every matching primary key first — a second round trip
# for nothing, since this session holds no User objects to synchronize.
# Without this option, the "auto" strategy cannot evaluate coalesce in
# Python and falls back to fetching every matching primary key first.
# That is a second round trip for no benefit, because this session holds
# no User objects to synchronize.
.execution_options(synchronize_session=False)
)
removed = db.execute(stmt).rowcount or 0