<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Fastapi on SpaceShaman</title>
    <link>https://spaceshaman.github.io/tags/fastapi/</link>
    <description>Recent content in Fastapi on SpaceShaman</description>
    <generator>Hugo</generator>
    <language>en-US</language>
    <copyright>SpaceShaman</copyright>
    <lastBuildDate>Wed, 01 Jul 2026 17:19:53 +0000</lastBuildDate>
    <atom:link href="https://spaceshaman.github.io/tags/fastapi/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>I built UserHarbor — a framework-agnostic user management library for Python</title>
      <link>https://spaceshaman.github.io/posts/i-built-userharbor-a-framework-agnostic-user-management-library-for-python/</link>
      <pubDate>Wed, 01 Jul 2026 17:19:53 +0000</pubDate>
      <guid>https://spaceshaman.github.io/posts/i-built-userharbor-a-framework-agnostic-user-management-library-for-python/</guid>
      <description>Why I built UserHarbor and separated user-management logic from web frameworks, databases, ORMs and email providers.</description>
      <content:encoded><![CDATA[<p>While working on a SaaS application recently, I once again had to implement the same user account flow:</p>
<ul>
<li>registration</li>
<li>login</li>
<li>sessions</li>
<li>email verification</li>
<li>password reset</li>
<li>password change</li>
<li>account deletion</li>
<li>basic roles and permissions</li>
</ul>
<p>None of that was especially hard.</p>
<p>But it was repetitive.</p>
<p>I had written similar code before, and I did not want to keep rebuilding the same user-management boilerplate in every new Python project.</p>
<p>So I started working on <strong>UserHarbor</strong>.</p>
<h2 id="what-is-userharbor">What is UserHarbor?</h2>
<p><strong>UserHarbor</strong> is a framework-agnostic Python library for user account management.</p>
<p>The idea is simple:</p>
<blockquote>
<p>keep the core small, predictable, and independent from any specific web framework, database, ORM, or email provider.</p>
</blockquote>
<p>The core handles the account-management logic.
Integrations are handled by separate adapter packages.</p>
<p>So instead of building something only for FastAPI, Flask, Django, or one specific stack, I wanted a core that could be used in different kinds of Python applications.</p>
<p>For example:</p>
<ul>
<li>FastAPI apps</li>
<li>Flask apps</li>
<li>Django apps</li>
<li>CLI tools</li>
<li>internal tools</li>
<li>custom Python services</li>
</ul>
<h2 id="why-not-just-use-a-framework-specific-library">Why not just use a framework-specific library?</h2>
<p>There are already good tools for specific frameworks.</p>
<p>But I wanted something slightly different.</p>
<p>I did not want the user-management logic to be tightly coupled to:</p>
<ul>
<li>a web framework</li>
<li>a database layer</li>
<li>an email provider</li>
<li>a specific request/response model</li>
</ul>
<p>Instead, UserHarbor uses small interfaces for things like storage and email delivery.</p>
<p>The main interfaces are:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">UserStore</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">EmailSender</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span></code></pre></div><p>The core does not care how users are stored or how emails are sent.</p>
<p>That part belongs to adapters.</p>
<h2 id="installation">Installation</h2>
<p>Install only the core package if you want to provide your own <code>UserStore</code> and <code>EmailSender</code> implementations:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install userharbor
</span></span></code></pre></div><p>Install the core package with the official SQLAlchemy, SMTP, and FastAPI adapters:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install <span class="s2">&#34;userharbor[sqlalchemy,smtp,fastapi]&#34;</span>
</span></span></code></pre></div><p>Or install all official integrations at once:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install <span class="s2">&#34;userharbor[all]&#34;</span>
</span></span></code></pre></div><h2 id="official-adapters">Official adapters</h2>
<p>At the moment, there are a few official adapter packages:</p>
<ul>
<li><code>userharbor-sqlalchemy</code> — SQLAlchemy storage</li>
<li><code>userharbor-smtp</code> — SMTP email sender</li>
<li><code>userharbor-fastapi</code> — FastAPI integration</li>
</ul>
<p>This keeps the core small while still making the common setup easy to install and use.</p>
<h2 id="quick-example">Quick example</h2>
<p>Here is a longer example using SQLAlchemy storage and SMTP email delivery:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">sqlalchemy</span> <span class="kn">import</span> <span class="n">create_engine</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">sqlalchemy.orm</span> <span class="kn">import</span> <span class="n">sessionmaker</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">userharbor</span> <span class="kn">import</span> <span class="n">UserHarbor</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">userharbor_sqlalchemy</span> <span class="kn">import</span> <span class="n">SQLAlchemyUserStore</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">userharbor_smtp</span> <span class="kn">import</span> <span class="n">SMTPEmailSender</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">engine</span> <span class="o">=</span> <span class="n">create_engine</span><span class="p">(</span><span class="s2">&#34;sqlite:///users.db&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">SessionLocal</span> <span class="o">=</span> <span class="n">sessionmaker</span><span class="p">(</span><span class="n">bind</span><span class="o">=</span><span class="n">engine</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">store</span> <span class="o">=</span> <span class="n">SQLAlchemyUserStore</span><span class="p">(</span><span class="n">SessionLocal</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">store</span><span class="o">.</span><span class="n">metadata</span><span class="o">.</span><span class="n">create_all</span><span class="p">(</span><span class="n">engine</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">email_sender</span> <span class="o">=</span> <span class="n">SMTPEmailSender</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">host</span><span class="o">=</span><span class="s2">&#34;smtp.example.com&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">port</span><span class="o">=</span><span class="mi">587</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">username</span><span class="o">=</span><span class="s2">&#34;smtp-user&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">password</span><span class="o">=</span><span class="s2">&#34;smtp-password&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">from_email</span><span class="o">=</span><span class="s2">&#34;noreply@example.com&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">harbor</span> <span class="o">=</span> <span class="n">UserHarbor</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">secret_key</span><span class="o">=</span><span class="s2">&#34;your-secret-key&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">store</span><span class="o">=</span><span class="n">store</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">email_sender</span><span class="o">=</span><span class="n">email_sender</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Register a user</span>
</span></span><span class="line"><span class="cl"><span class="n">harbor</span><span class="o">.</span><span class="n">register</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">username</span><span class="o">=</span><span class="s2">&#34;jane&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">email</span><span class="o">=</span><span class="s2">&#34;jane@example.com&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">password</span><span class="o">=</span><span class="s2">&#34;StrongPassword123!&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Verify email address</span>
</span></span><span class="line"><span class="cl"><span class="n">harbor</span><span class="o">.</span><span class="n">verify_email</span><span class="p">(</span><span class="s2">&#34;verification-token-from-email&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Login</span>
</span></span><span class="line"><span class="cl"><span class="n">session_token</span> <span class="o">=</span> <span class="n">harbor</span><span class="o">.</span><span class="n">login</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">username</span><span class="o">=</span><span class="s2">&#34;jane&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">password</span><span class="o">=</span><span class="s2">&#34;StrongPassword123!&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Verify session</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="n">harbor</span><span class="o">.</span><span class="n">verify_session</span><span class="p">(</span><span class="n">session_token</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;User is logged in&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get current user</span>
</span></span><span class="line"><span class="cl"><span class="n">current_user</span> <span class="o">=</span> <span class="n">harbor</span><span class="o">.</span><span class="n">get_current_user</span><span class="p">(</span><span class="n">session_token</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">current_user</span><span class="o">.</span><span class="n">username</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Create roles and permissions</span>
</span></span><span class="line"><span class="cl"><span class="n">harbor</span><span class="o">.</span><span class="n">roles</span><span class="o">.</span><span class="n">create</span><span class="p">(</span><span class="s2">&#34;admin&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">harbor</span><span class="o">.</span><span class="n">permissions</span><span class="o">.</span><span class="n">create</span><span class="p">(</span><span class="s2">&#34;users.delete&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">harbor</span><span class="o">.</span><span class="n">roles</span><span class="o">.</span><span class="n">grant_permission</span><span class="p">(</span><span class="s2">&#34;admin&#34;</span><span class="p">,</span> <span class="s2">&#34;users.delete&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">harbor</span><span class="o">.</span><span class="n">grant_role</span><span class="p">(</span><span class="s2">&#34;jane&#34;</span><span class="p">,</span> <span class="s2">&#34;admin&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Check access</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="n">harbor</span><span class="o">.</span><span class="n">has_permission</span><span class="p">(</span><span class="n">session_token</span><span class="p">,</span> <span class="s2">&#34;users.delete&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;User can delete users&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">current_admin</span> <span class="o">=</span> <span class="n">harbor</span><span class="o">.</span><span class="n">require_role</span><span class="p">(</span><span class="n">session_token</span><span class="p">,</span> <span class="s2">&#34;admin&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">current_admin</span><span class="o">.</span><span class="n">username</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Logout</span>
</span></span><span class="line"><span class="cl"><span class="n">harbor</span><span class="o">.</span><span class="n">logout</span><span class="p">(</span><span class="n">session_token</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Change password</span>
</span></span><span class="line"><span class="cl"><span class="n">session_token</span> <span class="o">=</span> <span class="n">harbor</span><span class="o">.</span><span class="n">login</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">username</span><span class="o">=</span><span class="s2">&#34;jane&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">password</span><span class="o">=</span><span class="s2">&#34;StrongPassword123!&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">harbor</span><span class="o">.</span><span class="n">change_password</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">old_password</span><span class="o">=</span><span class="s2">&#34;StrongPassword123!&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">new_password</span><span class="o">=</span><span class="s2">&#34;EvenStrongerPassword123!&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">session_token</span><span class="o">=</span><span class="n">session_token</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Send password reset email</span>
</span></span><span class="line"><span class="cl"><span class="n">harbor</span><span class="o">.</span><span class="n">send_password_reset</span><span class="p">(</span><span class="s2">&#34;jane@example.com&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Reset password</span>
</span></span><span class="line"><span class="cl"><span class="n">harbor</span><span class="o">.</span><span class="n">reset_password</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">new_password</span><span class="o">=</span><span class="s2">&#34;NewStrongPassword123!&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">reset_token</span><span class="o">=</span><span class="s2">&#34;reset-token-from-email&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Delete account</span>
</span></span><span class="line"><span class="cl"><span class="n">session_token</span> <span class="o">=</span> <span class="n">harbor</span><span class="o">.</span><span class="n">login</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">username</span><span class="o">=</span><span class="s2">&#34;jane&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">password</span><span class="o">=</span><span class="s2">&#34;NewStrongPassword123!&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">harbor</span><span class="o">.</span><span class="n">delete_account</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">password</span><span class="o">=</span><span class="s2">&#34;NewStrongPassword123!&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">session_token</span><span class="o">=</span><span class="n">session_token</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></div><h2 id="full-fastapi-example-with-official-integrations">Full FastAPI example with official integrations</h2>
<p>If you want to try the full setup with FastAPI, SQLAlchemy, and SMTP, install all official integrations:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install <span class="s2">&#34;userharbor[all]&#34;</span>
</span></span></code></pre></div><p>Then create a FastAPI application:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">fastapi</span> <span class="kn">import</span> <span class="n">FastAPI</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">sqlalchemy</span> <span class="kn">import</span> <span class="n">create_engine</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">sqlalchemy.orm</span> <span class="kn">import</span> <span class="n">sessionmaker</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">userharbor</span> <span class="kn">import</span> <span class="n">UserHarbor</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">userharbor_fastapi</span> <span class="kn">import</span> <span class="n">UserHarborFastAPI</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">userharbor_smtp</span> <span class="kn">import</span> <span class="n">SMTPEmailSender</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">userharbor_sqlalchemy</span> <span class="kn">import</span> <span class="n">SQLAlchemyUserStore</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">engine</span> <span class="o">=</span> <span class="n">create_engine</span><span class="p">(</span><span class="s2">&#34;sqlite:///users.db&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">SessionLocal</span> <span class="o">=</span> <span class="n">sessionmaker</span><span class="p">(</span><span class="n">bind</span><span class="o">=</span><span class="n">engine</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">store</span> <span class="o">=</span> <span class="n">SQLAlchemyUserStore</span><span class="p">(</span><span class="n">SessionLocal</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">store</span><span class="o">.</span><span class="n">metadata</span><span class="o">.</span><span class="n">create_all</span><span class="p">(</span><span class="n">engine</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">email_sender</span> <span class="o">=</span> <span class="n">SMTPEmailSender</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">host</span><span class="o">=</span><span class="n">os</span><span class="o">.</span><span class="n">getenv</span><span class="p">(</span><span class="s2">&#34;HOST&#34;</span><span class="p">,</span> <span class="s2">&#34;smtp.example.com&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">port</span><span class="o">=</span><span class="nb">int</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">getenv</span><span class="p">(</span><span class="s2">&#34;PORT&#34;</span><span class="p">,</span> <span class="mi">587</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">    <span class="n">username</span><span class="o">=</span><span class="n">os</span><span class="o">.</span><span class="n">getenv</span><span class="p">(</span><span class="s2">&#34;USERNAME&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">password</span><span class="o">=</span><span class="n">os</span><span class="o">.</span><span class="n">getenv</span><span class="p">(</span><span class="s2">&#34;PASSWORD&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">from_email</span><span class="o">=</span><span class="n">os</span><span class="o">.</span><span class="n">getenv</span><span class="p">(</span><span class="s2">&#34;USERNAME&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">harbor</span> <span class="o">=</span> <span class="n">UserHarbor</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">secret_key</span><span class="o">=</span><span class="s2">&#34;your-secret-key&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">store</span><span class="o">=</span><span class="n">store</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">email_sender</span><span class="o">=</span><span class="n">email_sender</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">auth</span> <span class="o">=</span> <span class="n">UserHarborFastAPI</span><span class="p">(</span><span class="n">harbor</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">FastAPI</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span><span class="o">.</span><span class="n">include_router</span><span class="p">(</span><span class="n">auth</span><span class="o">.</span><span class="n">router</span><span class="p">,</span> <span class="n">prefix</span><span class="o">=</span><span class="s2">&#34;/auth&#34;</span><span class="p">,</span> <span class="n">tags</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;auth&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="kn">import</span> <span class="nn">uvicorn</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">uvicorn</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="n">host</span><span class="o">=</span><span class="s2">&#34;0.0.0.0&#34;</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="mi">8000</span><span class="p">)</span>
</span></span></code></pre></div><h2 id="design-principles">Design principles</h2>
<p>The project is built around a few constraints.</p>
<h3 id="the-core-should-stay-small">The core should stay small</h3>
<p>UserHarbor is not meant to become a full identity platform.</p>
<p>The core focuses on basic account-management flows:</p>
<ul>
<li>registration</li>
<li>login</li>
<li>sessions</li>
<li>email verification</li>
<li>password reset</li>
<li>password change</li>
<li>account deletion</li>
<li>simple role-based access control</li>
</ul>
<p>Anything highly application-specific should stay outside the core.</p>
<h3 id="adapters-should-live-outside-the-core">Adapters should live outside the core</h3>
<p>Database, ORM, email, and framework integrations should be separate packages.</p>
<p>That keeps the core independent and makes it easier for other people to build their own integrations.</p>
<p>For example, someone could build:</p>
<ul>
<li><code>userharbor-redis</code></li>
<li><code>userharbor-mongodb</code></li>
<li><code>userharbor-sendgrid</code></li>
<li><code>userharbor-resend</code></li>
<li><code>userharbor-django</code></li>
<li><code>userharbor-flask</code></li>
</ul>
<p>without changing the main package.</p>
<h3 id="the-api-should-be-boring">The API should be boring</h3>
<p>I am trying to keep the public API explicit and predictable.</p>
<p>No hidden framework magic.
No forced database model.
No dependency on one specific way of building Python applications.</p>
<h2 id="current-status">Current status</h2>
<p>The project is still early.</p>
<p>The basic flows work, but I do not consider the API fully stable yet. It is not something I would call production-ready today.</p>
<p>Right now I am mostly looking for feedback around:</p>
<ul>
<li>the public API</li>
<li>the adapter architecture</li>
<li>the boundary between core and integrations</li>
<li>the SQLAlchemy integration</li>
<li>the FastAPI integration</li>
<li>whether simple RBAC belongs in the core</li>
<li>what a good developer experience for custom adapters should look like</li>
</ul>
<h2 id="links">Links</h2>
<p>Documentation:<br>
<a href="https://userharbor.github.io/userharbor/">https://userharbor.github.io/userharbor/</a></p>
<p>Repository:<br>
<a href="https://github.com/userharbor/userharbor">https://github.com/userharbor/userharbor</a></p>
<p>FastAPI integration:<br>
<a href="https://github.com/userharbor/userharbor-fastapi">https://github.com/userharbor/userharbor-fastapi</a></p>
<p>SQLAlchemy adapter:<br>
<a href="https://github.com/userharbor/userharbor-sqlalchemy">https://github.com/userharbor/userharbor-sqlalchemy</a></p>
<p>SMTP adapter:<br>
<a href="https://github.com/userharbor/userharbor-smtp">https://github.com/userharbor/userharbor-smtp</a></p>
<h2 id="feedback-welcome">Feedback welcome</h2>
<p>I would appreciate any feedback, especially from people who have built user-management flows multiple times in Python projects.</p>
<p>Does this adapter-based approach make sense?</p>
<p>Would you expect simple roles and permissions to be part of the core, or should they live in a separate package?</p>
<p>And if you were integrating this into your own project, what would you want the API to look like?</p>
<p><em>Update: I later wrote <a href="/posts/building-userharbor-framework-agnostic-user-management-for-python/">a deeper architectural follow-up about UserHarbor&rsquo;s adapters and executable storage contract</a>.</em></p>
<p><em>Originally published on <a href="https://dev.to/spaceshaman/i-built-userharbor-a-framework-agnostic-user-management-library-for-python-1mkj">DEV Community</a>.</em></p>
]]></content:encoded>
    </item>
  </channel>
</rss>
