Skip to content

fix: S3 streaming + Range, task heartbeat + after-commit enqueue, token rehash, scaffold fixes, QA follow-ups (#449–#472) - #463

Open
antosubash wants to merge 16 commits into
mainfrom
fix/issue-batch-449-461
Open

antosubash wants to merge 16 commits into
mainfrom
fix/issue-batch-449-461

Conversation

@antosubash

@antosubash antosubash commented Oct 13, 2026 •

Copy link
Copy Markdown
Owner

Six bug fixes. Four of them (#451, #452, #453, #457) come from the gis-data-portal port assessment (tracking issue #460).

Changes

  • file_storage: S3 get() buffers the whole object in memory; no ranged reads #453: file_storage streaming and Range.
    • S3Backend now streams objects in 64 KiB reads instead of buffering the whole object. The body is closed on completion, on error and on client disconnect.
    • New get_range(key, start, end) on the backend contract (inclusive end), implemented for S3 and filesystem.
    • Download endpoints answer Range with 206. They return 416 bytes */size when the range is unsatisfiable, honour If-Range, and fall back to a full 200 for multi-range or malformed headers.
    • A third-party backend without get_range keeps serving full 200s.
    • Found along the way: S3Backend.get was an async generator, but callers await it, so S3 thumbnails and the streamed public path raised TypeError. It is now awaited like the filesystem backend. A missing object returns 404 (was 500).
    • Out of scope: the optional presigned-redirect setting.
  • background_tasks: heartbeat is only stamped at start/end, so any task running >5 min is flagged stuck #451: long tasks flagged stuck.
    • Task bodies can call from background_tasks import heartbeat; heartbeat(). Writes are throttled to one per 30 s. It is a no-op outside a task and never revives a row the sweep already marked stuck.
    • @shared_task(..., stuck_after=<seconds>) sets a per-task threshold. The sweep reads it from the task registry, so no migration is needed.
    • Not done: an automatic heartbeat thread.
  • background_tasks: retry publishes to the broker before the TaskExecution row commits #452: retry published before the commit.
    • Single and bulk retries now publish only after the new TaskExecution rows commit. The bulk path publishes in asyncio.to_thread.
    • New public helper enqueue_after_commit(db, task, *args, **kwargs). It returns the reserved task id and works on request sessions (on_commit) and on plain or worker sessions (SQLAlchemy after_commit / rollback events).
  • users: /auth/token login discards the upgraded password hash #457: token login dropped the hash upgrade. POST /api/users/auth/token now saves the upgraded password hash, as the browser login already did. The users docs list the hash formats accepted on import: Argon2, and bcrypt $2a$/$2b$/$2y$, which is rehashed on the next sign-in.
  • i18n: keys.generated.ts never regenerates in scaffolded apps — app modules' t(keys.<ns>…) can't typecheck #449: i18n keys in scaffolded apps. In dev, when packages/i18n/src is absent, the host writes keys.generated.ts into the installed @simple-module-py/i18n package. It searches up the tree for node_modules (hoisted or the flat client_app/ layout) and keeps the keys the package already ships. Adds smpy host gen-i18n (strict) and a scaffold make gen-i18n, which make install runs.
  • cli: scaffolded make kill pkills every uvicorn/vite on the machine (other projects included) #461: make kill killed other apps' servers. The scaffold make kill now only stops this app's ports: API_PORT ?= 8000, and VITE_PORT read from SM_VITE_DEV_URL in .env (default 5050). The flat host Makefile gains the same target.

Verification

Ship pipeline (optimize, review, QA)

  • Optimize: 20 lines removed net in 2 behaviour-preserving commits. These remove duplicated code, drop dead guards and leave a single ETag source.
  • Review: clean after 3 passes. Pass 1 found that a ranged /download still opened the full object before fetching the range, which on S3 meant an extra full get_object per range read. FileStorageService.download now takes open_body=False, and a test proves a Range request never calls backend.get.
  • QA (browser and HTTP): 47 passed. No bugs in this PR's scope.
    • Covered: Range 206/416, If-Range, a malformed or multi-range header falls back to 200, an object missing on disk returns 404 (not 500), byte-identical 0-byte and 20 MB files, public 304, token login, and the retry endpoints.
    • 206 responses are never gzipped.
  • Verification on 3c051f96: make lint passed, make test-py 4042 passed, make test-js passed, and the e2e suite 49 passed.
  • Pre-existing issues found by QA, to be filed separately: rapid confirm clicks on a single Retry queue duplicates, and created_at has no timezone on SQLite.
  • CI (.github/workflows/pr.yml): the e2e job now sets SM_RATE_LIMIT_PUBLIC=1000/minute. The suite signs in 49 times from one IP, about 120 anonymous requests a minute, which is right at the default limit. A fast runner returned a 429 login page to whichever test signed in last; this failed twice in a row on this branch. Nothing in this PR touches those routes.
  • Pipeline report and QA report. These are private Claude Artifacts; the author can share them.

QA follow-ups fixed here (#467–#472)

Second /ship pass over these fixes:

Closes #449
Closes #451
Closes #452
Closes #453
Closes #457
Closes #461
Closes #467
Closes #468
Closes #469
Closes #470
Closes #471
Closes #472

https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4

…en rehash, scaffold make kill + i18n keys

- file_storage: stream S3 reads, get_range on backends, 206/416 Range
  support on downloads; S3 get() is awaited like filesystem (#453)
- background_tasks: heartbeat() API + per-task stuck_after (#451);
  retries publish after commit, enqueue_after_commit helper (#452)
- users: /auth/token persists the upgraded password hash (#457)
- hosting/cli: regenerate i18n keys into the installed npm package in
  scaffolded apps, smpy host gen-i18n (#449)
- cli: scaffolded make kill stops only this app's ports (#461)

Closes #449
Closes #451
Closes #452
Closes #453
Closes #457
Closes #461

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
🔒 Security Review ✅ Completed 2026-10-13T15:31:45.681441Z 90e69f1 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Deploying simple-module-python with  Cloudflare Pages  Cloudflare Pages

Latest commit: f16f169
Status: ✅  Deploy successful!
Preview URL: https://b978992f.simple-module-python.pages.dev
Branch Preview URL: https://fix-issue-batch-449-461.simple-module-python.pages.dev

View logs

…ag source, merged not-found handling, shared filesystem existence check

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
The suite signs in ~49 times from 127.0.0.1, about 120 anonymous public-route requests a minute — the default rate_limit_public. A fast runner 429'd the last test's login page (test_user_search_treats_like_metacharacters_literally).

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
Columns declared sa_type=DateTime(timezone=True) bypassed SQLModel's UTCDateTime and came back naive on SQLite, so the API sent timestamps with no offset and browsers read UTC as local time. New simple_module_db.AwareDateTime (a UTCDateTime that also accepts naive-as-UTC on write) replaces it in AuditMixin/SoftDeleteMixin and the audit_log, users, permissions and tenants models. Migrations still render sa.DateTime(timezone=True): no schema change.

Closes #468

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
#467)

A burst of confirm clicks sent one POST each, queuing duplicate retries (and, on SQLite, racing into 'database is locked'). retryExecution now shares the in-flight request per execution id; retrying again after it settles still works. Retrying an already-retried row on purpose stays allowed.

Closes #467

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
…all (#469)

The 'executions exist outside the current search' copy showed for any empty filtered view. The index view now passes has_executions (one extra count query only for an empty filtered page) and the page uses it.

Closes #469

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
… deleted' (#470)

Bulk delete compares the API's deleted count with the selection: all gone -> an info toast that the file(s) were already deleted; partial -> 'X of Y files deleted; the rest were already gone'.

Closes #470

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
…#471)

A module inside the host dir gave a bare 'modules/foo', which parse_add_spec treats as a PyPI requirement. Also: env_values ignores indented comments and strips spaces around keys.

Closes #471

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
…472)

Compose interpolates the password raw into SM_DATABASE_URL. The scaffolded .env.example now says it must be URL-safe, make docker-up refuses other characters with a clear message, and the installation guide documents it.

Closes #472

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
Keeps app_project.py within the 300-line cap after #472; _local_env already owns the .env/.env.example content.

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
@antosubash antosubash changed the title fix: S3 streaming + Range, task heartbeat + after-commit enqueue, token rehash, scaffold kill/i18n (#449, #451–#453, #457, #461) fix: S3 streaming + Range, task heartbeat + after-commit enqueue, token rehash, scaffold fixes, QA follow-ups (#449–#472) Oct 14, 2026
…ord guard reads .env like compose

The guard grepped raw lines, so CRLF endings, quoted values and inline comments blocked valid .env files, and it checked the first definition while compose uses the last. It now extracts the value as compose does and tests it by running make docker-up against a stubbed docker.

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment