<?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: Atanu Saha</title>
    <description>The latest articles on DEV Community by Atanu Saha (@atanusaha143).</description>
    <link>https://dev.to/atanusaha143</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%2F2646295%2F101eb34a-2a4c-4fa0-b7a1-fab19435359f.jpg</url>
      <title>DEV Community: Atanu Saha</title>
      <link>https://dev.to/atanusaha143</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/atanusaha143"/>
    <language>en</language>
    <item>
      <title>Modularizing SQLAlchemy Models with Mixins and Annotations</title>
      <dc:creator>Atanu Saha</dc:creator>
      <pubDate>Fri, 03 Jan 2025 11:14:10 +0000</pubDate>
      <link>https://dev.to/atanusaha143/modularizing-sqlalchemy-models-with-mixins-and-annotations-3kp1</link>
      <guid>https://dev.to/atanusaha143/modularizing-sqlalchemy-models-with-mixins-and-annotations-3kp1</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjc0jgn77kxsyl5dgzi75.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjc0jgn77kxsyl5dgzi75.jpg" alt="Modularizing SQLAlchemy Models" width="800" height="800"&gt;&lt;/a&gt;&lt;br&gt;
&lt;small&gt;Building scalable and maintainable models often requires a modular approach, especially when handling shared behaviors or common column types across multiple models. In this blog, we'll walk through how we can modularize models using SQLAlchemy's &lt;strong&gt;mixins&lt;/strong&gt; and &lt;strong&gt;annotations&lt;/strong&gt;.&lt;/small&gt;&lt;br&gt;
   &lt;/p&gt;
&lt;h4&gt;
  
  
  Why Modularize?
&lt;/h4&gt;

&lt;p&gt;&lt;small&gt;When working on projects, we frequently encounter repetitive tasks such as adding &lt;code&gt;created_at&lt;/code&gt; and &lt;code&gt;updated_at&lt;/code&gt; timestamps to models or defining common column types like &lt;code&gt;UUID&lt;/code&gt; primary keys. Modularizing these concerns into separate components has several benefits:&lt;/small&gt;&lt;br&gt;
 &lt;br&gt;
&lt;small&gt;1. &lt;strong&gt;Reusability&lt;/strong&gt;: Shared behaviors and column definitions can be used across multiple models.&lt;/small&gt;&lt;br&gt;
&lt;small&gt;2. &lt;strong&gt;Maintainability&lt;/strong&gt;: Changes in one place propagate to all dependent models.&lt;/small&gt;&lt;br&gt;
&lt;small&gt;3. &lt;strong&gt;Readability&lt;/strong&gt;: Clear separation of concerns makes the code easier to understand.&lt;/small&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h4&gt;
  
  
  Creating a &lt;em&gt;Timestamp Mixin&lt;/em&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;small&gt;&lt;a href="https://docs.sqlalchemy.org/en/13/orm/extensions/declarative/mixins.html" rel="noopener noreferrer"&gt;Mixins&lt;/a&gt; provide reusable logic or fields for models. Let's define a TimestampMixin that automatically adds &lt;code&gt;created_at&lt;/code&gt; and &lt;code&gt;updated_at&lt;/code&gt; fields to any model that inherits from it.&lt;/small&gt;&lt;br&gt;
 &lt;br&gt;
&lt;small&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;timestamp_mixin.py&lt;/code&gt;&lt;/small&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy.ext.declarative&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;declared_attr&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TimestampMixin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nd"&gt;@declared_attr&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@declared_attr&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;updated_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;onupdate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Explanation
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;&lt;small&gt;&lt;code&gt;@declared_attr&lt;/code&gt;: Ensures that the attributes are dynamically added to the inheriting models.&lt;/small&gt;&lt;/li&gt;
&lt;li&gt;&lt;small&gt;&lt;code&gt;default&lt;/code&gt; and &lt;code&gt;onupdate&lt;/code&gt;: Automatically set timestamps for creation and updates.&lt;/small&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h4&gt;
  
  
  Defining &lt;em&gt;Common Annotations&lt;/em&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;small&gt;SQLAlchemy’s &lt;a href="https://docs.sqlalchemy.org/en/20/orm/declarative_tables.html#mapping-whole-column-declarations-to-python-types" rel="noopener noreferrer"&gt;Annotated&lt;/a&gt; types, available via Python’s &lt;code&gt;typing.Annotated&lt;/code&gt;, let you define reusable column properties. For example, you can define a &lt;code&gt;UUID&lt;/code&gt; primary key or a &lt;code&gt;String&lt;/code&gt; column with specific constraints.&lt;/small&gt;&lt;br&gt;
 &lt;br&gt;
&lt;small&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;common_annotations.py&lt;/code&gt;&lt;/small&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Annotated&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uuid4&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy.dialects.postgresql&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy.orm&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mapped_column&lt;/span&gt;

&lt;span class="n"&gt;uuid4pk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
    &lt;span class="nf"&gt;mapped_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;as_uuid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Annotated&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;mapped_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Explanation
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;&lt;small&gt;&lt;strong&gt;UUID Primary Key&lt;/strong&gt;: The &lt;code&gt;uuid4pk&lt;/code&gt; annotation defines a universally unique identifier for primary keys.&lt;/small&gt;&lt;/li&gt;
&lt;li&gt;&lt;small&gt;&lt;strong&gt;Name Column&lt;/strong&gt;: The &lt;code&gt;name&lt;/code&gt; annotation ensures a &lt;code&gt;String&lt;/code&gt; column with a maximum length of 30 characters and no &lt;code&gt;NULL&lt;/code&gt; values.&lt;/small&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h4&gt;
  
  
  Building Models with Mixins and Annotations
&lt;/h4&gt;

&lt;p&gt;&lt;small&gt;Using the &lt;em&gt;mixins and annotations&lt;/em&gt;, we can define models that inherit shared behavior and properties while keeping the implementation concise and readable.&lt;/small&gt;&lt;br&gt;
 &lt;br&gt;
&lt;small&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;user.py&lt;/code&gt;&lt;/small&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy.orm&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy.ext.declarative&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;declarative_base&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mixins&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TimestampMixin&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;annotations&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uuid4pk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;

&lt;span class="n"&gt;Base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;declarative_base&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TimestampMixin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;__tablename__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;users&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;uuid4pk&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;last_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Explanation
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;&lt;small&gt;&lt;strong&gt;Declarative Base&lt;/strong&gt;: The &lt;code&gt;Base&lt;/code&gt; serves as the foundation for all SQLAlchemy models.&lt;/small&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h4&gt;
  
  
  Creating Models with Mixins and Annotations
&lt;/h4&gt;

&lt;p&gt;&lt;small&gt;Now that we’ve designed our &lt;code&gt;User&lt;/code&gt; model using &lt;code&gt;TimestampMixin&lt;/code&gt; and &lt;code&gt;Annotated&lt;/code&gt;, the next step is to ensure it’s reflected in the database. Upon a successful creation of a table from the model, the columns should look like below:&lt;/small&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnk4i2xh8900jdlazm6dx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnk4i2xh8900jdlazm6dx.png" alt="User DB Columns" width="357" height="147"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h4&gt;
  
  
  Benefits of This Approach
&lt;/h4&gt;

&lt;p&gt;&lt;small&gt;1. &lt;strong&gt;Clear Separation of Concerns&lt;/strong&gt;&lt;/small&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;small&gt;&lt;code&gt;timestamp_mixin.py&lt;/code&gt;: Contains reusable logic (e.g., timestamps).&lt;/small&gt;&lt;/li&gt;
&lt;li&gt;&lt;small&gt;&lt;code&gt;common_annotations.py&lt;/code&gt;: Defines common column properties (e.g., UUIDs, strings).&lt;/small&gt;&lt;/li&gt;
&lt;li&gt;&lt;small&gt;&lt;code&gt;user.py&lt;/code&gt;: Combines these building blocks into concrete models.&lt;/small&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;small&gt;2. &lt;strong&gt;Ease of Maintenance&lt;/strong&gt;&lt;/small&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;small&gt;If it is needed to change how &lt;em&gt;timestamps&lt;/em&gt; work or update column constraints, it is only needed to modify the &lt;code&gt;timestamp_mixin.py&lt;/code&gt; or &lt;code&gt;common_annotations.py&lt;/code&gt; files. The changes automatically reflect across all dependent models.&lt;/small&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;small&gt;3. &lt;strong&gt;Scalability&lt;/strong&gt;&lt;/small&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;small&gt;As project grows, this structure makes it easier to add new behaviors or field types without introducing redundancy.&lt;/small&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;   &lt;/p&gt;

&lt;h4&gt;
  
  
  Wrap Up Thoughts
&lt;/h4&gt;

&lt;p&gt;&lt;small&gt;Modularizing models with SQLAlchemy's &lt;em&gt;mixins&lt;/em&gt; and &lt;em&gt;annotations&lt;/em&gt; is a good strategy for handling shared functionality and properties. This approach not only reduces duplication but also aligns with best practices for clean, maintainable code.&lt;/small&gt;&lt;/p&gt;

</description>
      <category>sqlalchemy</category>
      <category>python</category>
      <category>codingbestpractices</category>
    </item>
  </channel>
</rss>
