<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: unauthorized</title>
    <description>The latest articles on DEV Community by unauthorized (@unauthorized).</description>
    <link>https://dev.to/unauthorized</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3618584%2F7030da28-25f5-43e8-916d-7d21872a7519.png</url>
      <title>DEV Community: unauthorized</title>
      <link>https://dev.to/unauthorized</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/unauthorized"/>
    <language>en</language>
    <item>
      <title>SQL as Contract vs. Python as Procedure — a Short Note on Atlas vs. Alembic</title>
      <dc:creator>unauthorized</dc:creator>
      <pubDate>Wed, 19 Nov 2025 04:15:38 +0000</pubDate>
      <link>https://dev.to/unauthorized/sql-as-contract-vs-python-as-procedure-a-short-note-on-atlas-vs-alembic-36io</link>
      <guid>https://dev.to/unauthorized/sql-as-contract-vs-python-as-procedure-a-short-note-on-atlas-vs-alembic-36io</guid>
      <description>&lt;p&gt;&lt;strong&gt;SQL as Contract vs. Python as Procedure — a Short Note on Atlas vs. Alembic&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The useful question isn’t “which tool is better?” but &lt;strong&gt;“what is our source of truth?”&lt;/strong&gt; If your team wants the &lt;strong&gt;database schema itself&lt;/strong&gt;—its tables, constraints, and indexes—to be the contract you review and protect, you’ll likely prefer a &lt;strong&gt;declarative, SQL‑first&lt;/strong&gt; approach (Atlas‑style). If your team prefers to encode change as &lt;strong&gt;Python steps&lt;/strong&gt; that live next to application code, you’ll likely prefer an &lt;strong&gt;imperative, script‑first&lt;/strong&gt; approach (Alembic‑style).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Declarative (state‑based) thinking&lt;/strong&gt; starts from the destination. You write down the &lt;strong&gt;desired schema&lt;/strong&gt; and let the tool compute a plan from current → desired. Review happens in the &lt;strong&gt;database’s language (DDL)&lt;/strong&gt;, not through an ORM’s interpretation. Because the plan is derived, it tends to be &lt;strong&gt;predictable&lt;/strong&gt;: the same desired state should produce the same kind of SQL, and policy checks can block destructive changes before they ship. Your mental model becomes simple: &lt;em&gt;read the schema, read the plan, approve&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Imperative (history‑based) thinking&lt;/strong&gt; starts from the journey. You maintain a &lt;strong&gt;timeline of migrations&lt;/strong&gt; (&lt;code&gt;upgrade()&lt;/code&gt;/&lt;code&gt;downgrade()&lt;/code&gt;) often generated from ORM metadata. This favors &lt;strong&gt;expressiveness&lt;/strong&gt;—you can mix DDL with data backfills and write logic in Python. The cost is managing the &lt;strong&gt;timeline itself&lt;/strong&gt;: version directories, multiple heads, merges, and the subtle gap between &lt;strong&gt;what the ORM infers&lt;/strong&gt; and &lt;strong&gt;what the database enforces&lt;/strong&gt;. Dry runs help, but accuracy can depend on runtime context and project‑specific conventions.&lt;/p&gt;

&lt;p&gt;Choosing &lt;strong&gt;SQL DDL vs. Python (sqlmodel)&lt;/strong&gt; is really choosing &lt;strong&gt;where you pay complexity&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;With &lt;strong&gt;SQL‑first&lt;/strong&gt;, complexity moves into &lt;strong&gt;planning and policy&lt;/strong&gt;: you invest in deterministic diffs, lint rules for unsafe changes, and small, explicit configuration. You optimize for &lt;strong&gt;clarity in review&lt;/strong&gt; and &lt;strong&gt;predictability at apply time&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;With &lt;strong&gt;Python‑first&lt;/strong&gt;, complexity moves into &lt;strong&gt;authoring and orchestration&lt;/strong&gt;: you own autogenerate quirks, environment glue (&lt;code&gt;env.py&lt;/code&gt;, &lt;code&gt;.ini&lt;/code&gt;), and the hygiene of your migration graph, in exchange for &lt;strong&gt;programmable migrations&lt;/strong&gt; close to your app.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both models work at scale; they simply optimize for different truths. If you’re tired of debating what the ORM &lt;em&gt;meant&lt;/em&gt; and want reviewers to reason directly about the database, &lt;strong&gt;declarative SQL&lt;/strong&gt; keeps the conversation honest: the contract is the DDL. If your changes routinely involve nontrivial data moves or app‑level logic, &lt;strong&gt;imperative Python&lt;/strong&gt; keeps everything in one language and one place.&lt;/p&gt;

&lt;p&gt;A quick heuristic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pick &lt;strong&gt;declarative/SQL‑first (Atlas)&lt;/strong&gt; when you value &lt;strong&gt;predictable plans, schema‑as‑contract, and minimal config&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Pick &lt;strong&gt;imperative/Python‑first (Alembic)&lt;/strong&gt; when you value &lt;strong&gt;programmable data migrations, tight ORM integration, and explicit step control&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words: &lt;strong&gt;pick the mental model first&lt;/strong&gt;. The tool follows.&lt;/p&gt;

</description>
      <category>database</category>
      <category>sql</category>
      <category>architecture</category>
      <category>python</category>
    </item>
  </channel>
</rss>
