Skip to content

Migrating from SQLAlchemy: show sessions, make every block run #717

Closed

Description

@0x054

Parent

#698

What to build

The page says Ferro has "no session to manage", and every Ferro snippet on it raises RuntimeError: No database route on 0.25.0, including transaction() blocks. Its comparison table says migrations are Alembic. The page explains Ferro's session against SQLAlchemy's, every Ferro block runs (moved into an example pair or marked to run), each SQLAlchemy/Ferro pair shows what the query returns, and the table names ferro migrate with the Alembic bridge as an option.

Pages: howto/migrate-from-sqlalchemy.md

Acceptance criteria

  • Every fact on the page matches the code at the time of the PR; each High finding in the audit rows below is fixed, and each Medium/Low finding is fixed or answered in the PR body with the reason it was left.
  • Each page is one kind (tutorial, how-to, reference or explanation) and opens with one sentence saying what it covers, then an example on the first screen (writing-for-humans rules 1–2).
  • Each page uses one running example, declared once near the top and reused; each section shows only what changed (rule 3).
  • Every code block has a lead-in sentence ending in a colon and a result sentence after it, and shows its result: printed output, or the asserted value inside the embedded snippet range (rules 7–8). Snippet end: markers sit below the assert or print that holds the result.
  • Every copyable block either shows the async with engines.session(): wrapper or sits under a sentence that says it runs inside one and links to Connections › Sessions; the page's first block shows the wrapper.
  • Every inline Python block that does real work runs under the harness ({test=run prelude=<name>}) or moves into an example script; a block left compile-only says why in its fence marker.
  • Every lambda passed to a query method names its parameter after the model in lowercase singular (AGENTS.md I-8); python -m tests._docs_checks reports no violation for the owned files.
  • Every error check in an owned example fails when the expected error is not raised (try/except/else: raise AssertionError, or the harness helper).
  • Field-declaring examples show both declaration styles as tabs, each backed by a runnable script (AGENTS.md I-7), and the twins use the one Annotated idiom recorded in the docs conventions.
  • Headings are short sentence-case noun phrases naming the feature (rule 5); how-to titles name the goal (rule 24); no minimising words (rule 17); admonitions follow rule 20.
  • No ADR numbers, issue numbers, codenames, dated or release-bound notes, or contributor-only references in page text; version markers are self-contained blocks at the end of a section.
  • Reference material that has a home in Reference is linked, not repeated; each topic ends with a pointer to its reference entry (rule 4).

Files owned

  • Create: docs/examples/from_sqlalchemy.py, docs/examples/from_sqlalchemy_annotated.py
  • Modify: docs/pages/howto/migrate-from-sqlalchemy.md

Interfaces

Test scenarios

  • Happy path: the strict site build succeeds and every snippet range on the owned pages renders without leading indentation.
  • Happy path: every owned example script runs to completion under tests/test_docs_examples.py, and every inline block marked {test=run ...} runs.
  • Error path: deleting the raise an owned example's error check expects makes that example's test fail.
  • Edge case: python -m tests._docs_checks over the owned files exits 0; renaming one lambda parameter to t makes it exit 1 naming the file and line.

Verify

uv run --no-sync zensical build --clean --strict
uv run --no-sync pytest tests/test_docs_examples.py -q --no-cov -p no:cacheprovider -n 8
uv run --no-sync python -m tests._docs_checks docs/pages/howto/migrate-from-sqlalchemy.md docs/examples/from_sqlalchemy.py

Blocked by

Audit findings

Rows from the docs audit this ticket covers (file, line, rule, fix)

docs/pages/howto/migrate-from-sqlalchemy.md

  • Kind (rule 1): how-to / comparison map (fits the kind).
  • Running example (rule 3): User (then User/Post for relationships), reasonably held.
  • Verdict: The page says Ferro has "no session to manage", and every Ferro snippet on it raises
    RuntimeError: No database route for this operation when run as shown. I reproduced this with both
    User.create(...) and async with transaction(): against a connected SQLite database. The migrations
    row is also stale, and it uses lambda t:. Grade: C.
Sev Line Rule Finding Suggested fix
H 3, 13, 238 fact (stale API) "there is no session to manage", "Sessions: None — connections are managed for you", "Same shape, no session". Since 0.13/0.14 an operation with no ambient session and no using= raises RuntimeError (reproduced on 0.25.0, including inside async with transaction():). A SQLAlchemy user who copies these blocks hits that error on the first call. Say what is true: Ferro has a session, but it is a routing and identity-map scope, not a unit of work. There is no add(), flush() or commit(). Table row: "async with ferro.engines.session(): once per request; no add/commit". Show it once in Queries, and use it in Creating Records and Transactions.
H 89, 103, 118–123, 140, 213–215, 233–235 9 Every Ferro block runs outside a session and would raise. Inline blocks are only compile-checked, so the tests cannot catch it. Wrap them (or state once "inside a session, as above"). Better: move the Ferro side into docs/examples/migrate_from_sqlalchemy.py with --8<-- sections so it runs.
H 103, 215, 267 I-8 lambda t: t.age, order_by(lambda t: t.id, "desc"), t.amount.sum(). lambda user: user.age >= 18, lambda post: post.id, lambda order: order.amount.sum().
M 17 fact (stale) The table row says "Migrations: Alembic (via ferro.migrations.get_metadata)". Ferro now has first-party migrations (ferro migrate), as the page itself says at line 259. "ferro migrate (own migrations), or Alembic via get_metadata() + ferro_options()".
M 71 humanizer/17 "you get validation, serialization, and FastAPI integration for free" is sales language. "Validation and serialization come from Pydantic, so the model doubles as your API schema."
M 76–141 7, 13 The SQLAlchemy/Ferro pairs are good, but they are two separate blocks with a # SQLAlchemy / # Ferro comment instead of tabs, and they have no result sentence. Use content tabs "SQLAlchemy" / "Ferro" for each pair (rule 12). Add one sentence after each on what comes back (list[User], an instance with id set).
L 5, 128, 261, 272 5 Title-case headings ("Quick Comparison", "Creating Records", "What Has No Ferro Equivalent Yet", "See Also"). Sentence case. "Gaps" or "Features without a Ferro equivalent" for the third.
L 268 clarity "Postgres .merge() is shipped on the recipe door; .concat() is still coming." "Recipe door" is internal jargon. "update() recipes support Postgres .merge(); .concat() is planned."
L 172–208 I-7 Both tabs are present but inline, and therefore only compile-checked, not run. Back them with docs/examples/ files, as I-7 asks for snippet-embedded models.

I-7 otherwise satisfied (Models and Relationships both tabbed).


Activity

  1. added
    documentationImprovements or additions to documentation
    ready-for-agentUnblocked and fully specified; an agent may claim it
    size:SOne context window, <= 3 files; cloud-eligible
    on Oct 19, 2026
  2. added theissue type on Oct 19, 2026
  3. self-assigned this
    on Oct 19, 2026
  4. 0x054 commented on Oct 19, 2026

    @0x054
    ContributorAuthor

    Status: verify-passed
    Worker: sdd worker (PR #743), reviewed by reviewer-717
    Branch: sdd/717-from-sqlalchemy
    PR: #743
    Verify: passed c24afa5

    Review posted on #743: Spec PASS, Quality PASS (2 Minor).
    Verify passed on the PR head and on the trial merge into feat/docs-rewrite at acd1127.

  5. 0x054 commented on Oct 19, 2026

    @0x054
    ContributorAuthor

    Merged in #743 into feat/docs-rewrite

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

Metadata

Metadata

Assignees

Labels

documentationImprovements or additions to documentationready-for-agentUnblocked and fully specified; an agent may claim itsize:SOne context window, <= 3 files; cloud-eligible

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions