This commit is contained in:
@@ -303,7 +303,7 @@
|
||||
<li class="md-nav__item">
|
||||
<a href="#mail_intake--quick-start" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Quick start
|
||||
Quick Start
|
||||
</span>
|
||||
</a>
|
||||
|
||||
@@ -1186,7 +1186,7 @@
|
||||
<li class="md-nav__item">
|
||||
<a href="#mail_intake--quick-start" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Quick start
|
||||
Quick Start
|
||||
</span>
|
||||
</a>
|
||||
|
||||
@@ -1246,118 +1246,127 @@
|
||||
|
||||
<div class="doc doc-contents first">
|
||||
|
||||
<p>Mail Intake — provider-agnostic, read-only email ingestion framework.</p>
|
||||
<hr />
|
||||
<h4 id="mail_intake--summary">Summary</h4>
|
||||
<h3 id="mail_intake--summary">Summary</h3>
|
||||
<p>Mail Intake — provider-agnostic, read-only email ingestion framework.</p>
|
||||
<p>Mail Intake is a <strong>contract-first library</strong> designed to ingest, parse, and
|
||||
normalize email data from external providers (such as Gmail) into clean,
|
||||
provider-agnostic domain models.</p>
|
||||
<p>The library is intentionally structured around clear layers, each exposed
|
||||
as a first-class module at the package root:</p>
|
||||
<ul>
|
||||
<li>adapters: provider-specific access (e.g. Gmail)</li>
|
||||
<li>auth: authentication providers and credential lifecycle management</li>
|
||||
<li>credentials: credential persistence abstractions and implementations</li>
|
||||
<li>parsers: extraction and normalization of message content</li>
|
||||
<li>ingestion: orchestration and high-level ingestion workflows</li>
|
||||
<li>models: canonical, provider-agnostic data representations</li>
|
||||
<li>config: explicit global configuration</li>
|
||||
<li>exceptions: library-defined error hierarchy</li>
|
||||
<li><code>adapters</code>: Provider-specific access (e.g., Gmail).</li>
|
||||
<li><code>auth</code>: Authentication providers and credential lifecycle management.</li>
|
||||
<li><code>credentials</code>: Credential persistence abstractions and implementations.</li>
|
||||
<li><code>parsers</code>: Extraction and normalization of message content.</li>
|
||||
<li><code>ingestion</code>: Orchestration and high-level ingestion workflows.</li>
|
||||
<li><code>models</code>: Canonical, provider-agnostic data representations.</li>
|
||||
<li><code>config</code>: Explicit global configuration.</li>
|
||||
<li><code>exceptions</code>: Library-defined error hierarchy.</li>
|
||||
</ul>
|
||||
<p>The package root acts as a <strong>namespace</strong>, not a facade. Consumers are
|
||||
expected to import functionality explicitly from the appropriate module.</p>
|
||||
<hr />
|
||||
<h4 id="mail_intake--installation">Installation</h4>
|
||||
<h3 id="mail_intake--installation">Installation</h3>
|
||||
<p>Install using pip:</p>
|
||||
<div class="language-text highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><code>pip install mail-intake
|
||||
</code></pre></div></td></tr></table></div>
|
||||
<div class="language-bash highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-0-1">1</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-0-1"><a id="__codelineno-0-1" name="__codelineno-0-1"></a>pip<span class="w"> </span>install<span class="w"> </span>mail-intake
|
||||
</span></code></pre></div></td></tr></table></div>
|
||||
<p>Or with Poetry:</p>
|
||||
<div class="language-text highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><code>poetry add mail-intake
|
||||
</code></pre></div></td></tr></table></div>
|
||||
<div class="language-bash highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-1-1">1</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-1-1"><a id="__codelineno-1-1" name="__codelineno-1-1"></a>poetry<span class="w"> </span>add<span class="w"> </span>mail-intake
|
||||
</span></code></pre></div></td></tr></table></div>
|
||||
<p>Mail Intake is pure Python and has no runtime dependencies beyond those
|
||||
required by the selected provider (for example, Google APIs for Gmail).</p>
|
||||
<hr />
|
||||
<h4 id="mail_intake--quick-start">Quick start</h4>
|
||||
<h3 id="mail_intake--quick-start">Quick Start</h3>
|
||||
<p>Minimal Gmail ingestion example (local development):</p>
|
||||
<div class="language-text highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"> 1</span>
|
||||
<span class="normal"> 2</span>
|
||||
<span class="normal"> 3</span>
|
||||
<span class="normal"> 4</span>
|
||||
<span class="normal"> 5</span>
|
||||
<span class="normal"> 6</span>
|
||||
<span class="normal"> 7</span>
|
||||
<span class="normal"> 8</span>
|
||||
<span class="normal"> 9</span>
|
||||
<span class="normal">10</span>
|
||||
<span class="normal">11</span>
|
||||
<span class="normal">12</span>
|
||||
<span class="normal">13</span>
|
||||
<span class="normal">14</span>
|
||||
<span class="normal">15</span>
|
||||
<span class="normal">16</span>
|
||||
<span class="normal">17</span>
|
||||
<span class="normal">18</span></pre></div></td><td class="code"><div><pre><span></span><code>from mail_intake.ingestion import MailIntakeReader
|
||||
from mail_intake.adapters import MailIntakeGmailAdapter
|
||||
from mail_intake.auth import MailIntakeGoogleAuth
|
||||
from mail_intake.credentials import PickleCredentialStore
|
||||
|
||||
store = PickleCredentialStore(path="token.pickle")
|
||||
|
||||
auth = MailIntakeGoogleAuth(
|
||||
credentials_path="credentials.json",
|
||||
store=store,
|
||||
scopes=["https://www.googleapis.com/auth/gmail.readonly"],
|
||||
)
|
||||
|
||||
adapter = MailIntakeGmailAdapter(auth_provider=auth)
|
||||
reader = MailIntakeReader(adapter)
|
||||
|
||||
for message in reader.iter_messages("from:recruiter@example.com"):
|
||||
print(message.subject, message.from_email)
|
||||
</code></pre></div></td></tr></table></div>
|
||||
<div class="language-python highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-2-1"> 1</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-2"> 2</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-3"> 3</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-4"> 4</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-5"> 5</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-6"> 6</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-7"> 7</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-8"> 8</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-9"> 9</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-10">10</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-11">11</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-12">12</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-13">13</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-14">14</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-15">15</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-16">16</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-17">17</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-18">18</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-2-1"><a id="__codelineno-2-1" name="__codelineno-2-1"></a><span class="kn">from</span><span class="w"> </span><span class="nn">mail_intake.ingestion</span><span class="w"> </span><span class="kn">import</span> <span class="n">MailIntakeReader</span>
|
||||
</span><span id="__span-2-2"><a id="__codelineno-2-2" name="__codelineno-2-2"></a><span class="kn">from</span><span class="w"> </span><span class="nn">mail_intake.adapters</span><span class="w"> </span><span class="kn">import</span> <span class="n">MailIntakeGmailAdapter</span>
|
||||
</span><span id="__span-2-3"><a id="__codelineno-2-3" name="__codelineno-2-3"></a><span class="kn">from</span><span class="w"> </span><span class="nn">mail_intake.auth</span><span class="w"> </span><span class="kn">import</span> <span class="n">MailIntakeGoogleAuth</span>
|
||||
</span><span id="__span-2-4"><a id="__codelineno-2-4" name="__codelineno-2-4"></a><span class="kn">from</span><span class="w"> </span><span class="nn">mail_intake.credentials</span><span class="w"> </span><span class="kn">import</span> <span class="n">PickleCredentialStore</span>
|
||||
</span><span id="__span-2-5"><a id="__codelineno-2-5" name="__codelineno-2-5"></a>
|
||||
</span><span id="__span-2-6"><a id="__codelineno-2-6" name="__codelineno-2-6"></a><span class="n">store</span> <span class="o">=</span> <span class="n">PickleCredentialStore</span><span class="p">(</span><span class="n">path</span><span class="o">=</span><span class="s2">"token.pickle"</span><span class="p">)</span>
|
||||
</span><span id="__span-2-7"><a id="__codelineno-2-7" name="__codelineno-2-7"></a>
|
||||
</span><span id="__span-2-8"><a id="__codelineno-2-8" name="__codelineno-2-8"></a><span class="n">auth</span> <span class="o">=</span> <span class="n">MailIntakeGoogleAuth</span><span class="p">(</span>
|
||||
</span><span id="__span-2-9"><a id="__codelineno-2-9" name="__codelineno-2-9"></a> <span class="n">credentials_path</span><span class="o">=</span><span class="s2">"credentials.json"</span><span class="p">,</span>
|
||||
</span><span id="__span-2-10"><a id="__codelineno-2-10" name="__codelineno-2-10"></a> <span class="n">store</span><span class="o">=</span><span class="n">store</span><span class="p">,</span>
|
||||
</span><span id="__span-2-11"><a id="__codelineno-2-11" name="__codelineno-2-11"></a> <span class="n">scopes</span><span class="o">=</span><span class="p">[</span><span class="s2">"https://www.googleapis.com/auth/gmail.readonly"</span><span class="p">],</span>
|
||||
</span><span id="__span-2-12"><a id="__codelineno-2-12" name="__codelineno-2-12"></a><span class="p">)</span>
|
||||
</span><span id="__span-2-13"><a id="__codelineno-2-13" name="__codelineno-2-13"></a>
|
||||
</span><span id="__span-2-14"><a id="__codelineno-2-14" name="__codelineno-2-14"></a><span class="n">adapter</span> <span class="o">=</span> <span class="n">MailIntakeGmailAdapter</span><span class="p">(</span><span class="n">auth_provider</span><span class="o">=</span><span class="n">auth</span><span class="p">)</span>
|
||||
</span><span id="__span-2-15"><a id="__codelineno-2-15" name="__codelineno-2-15"></a><span class="n">reader</span> <span class="o">=</span> <span class="n">MailIntakeReader</span><span class="p">(</span><span class="n">adapter</span><span class="p">)</span>
|
||||
</span><span id="__span-2-16"><a id="__codelineno-2-16" name="__codelineno-2-16"></a>
|
||||
</span><span id="__span-2-17"><a id="__codelineno-2-17" name="__codelineno-2-17"></a><span class="k">for</span> <span class="n">message</span> <span class="ow">in</span> <span class="n">reader</span><span class="o">.</span><span class="n">iter_messages</span><span class="p">(</span><span class="s2">"from:recruiter@example.com"</span><span class="p">):</span>
|
||||
</span><span id="__span-2-18"><a id="__codelineno-2-18" name="__codelineno-2-18"></a> <span class="nb">print</span><span class="p">(</span><span class="n">message</span><span class="o">.</span><span class="n">subject</span><span class="p">,</span> <span class="n">message</span><span class="o">.</span><span class="n">from_email</span><span class="p">)</span>
|
||||
</span></code></pre></div></td></tr></table></div>
|
||||
<p>Iterating over threads:</p>
|
||||
<div class="language-text highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">1</span>
|
||||
<span class="normal">2</span></pre></div></td><td class="code"><div><pre><span></span><code>for thread in reader.iter_threads("subject:Interview"):
|
||||
print(thread.normalized_subject, len(thread.messages))
|
||||
</code></pre></div></td></tr></table></div>
|
||||
<div class="language-python highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-3-1">1</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-2">2</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-3-1"><a id="__codelineno-3-1" name="__codelineno-3-1"></a><span class="k">for</span> <span class="n">thread</span> <span class="ow">in</span> <span class="n">reader</span><span class="o">.</span><span class="n">iter_threads</span><span class="p">(</span><span class="s2">"subject:Interview"</span><span class="p">):</span>
|
||||
</span><span id="__span-3-2"><a id="__codelineno-3-2" name="__codelineno-3-2"></a> <span class="nb">print</span><span class="p">(</span><span class="n">thread</span><span class="o">.</span><span class="n">normalized_subject</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">thread</span><span class="o">.</span><span class="n">messages</span><span class="p">))</span>
|
||||
</span></code></pre></div></td></tr></table></div>
|
||||
<hr />
|
||||
<h4 id="mail_intake--architecture">Architecture</h4>
|
||||
<h3 id="mail_intake--architecture">Architecture</h3>
|
||||
<p>Mail Intake is designed to be extensible via <strong>public contracts</strong> exposed
|
||||
through its modules:</p>
|
||||
<ul>
|
||||
<li>Users MAY implement their own mail adapters by subclassing <code>adapters.MailIntakeAdapter</code></li>
|
||||
<li>Users MAY implement their own authentication providers by subclassing <code>auth.MailIntakeAuthProvider[T]</code></li>
|
||||
<li>Users MAY implement their own credential persistence layers by implementing <code>credentials.CredentialStore[T]</code></li>
|
||||
<li>Users MAY implement their own mail adapters by subclassing
|
||||
<code>adapters.MailIntakeAdapter</code>.</li>
|
||||
<li>Users MAY implement their own authentication providers by subclassing
|
||||
<code>auth.MailIntakeAuthProvider[T]</code>.</li>
|
||||
<li>Users MAY implement their own credential persistence layers by implementing
|
||||
<code>credentials.CredentialStore[T]</code>.</li>
|
||||
</ul>
|
||||
<p>Users SHOULD NOT subclass built-in adapter implementations. Built-in
|
||||
adapters (such as Gmail) are reference implementations and may change
|
||||
internally without notice.</p>
|
||||
<p><strong>Design Guarantees:</strong>
|
||||
- Read-only access: no mutation of provider state
|
||||
- Provider-agnostic domain models
|
||||
- Explicit configuration and dependency injection
|
||||
- No implicit global state or environment reads
|
||||
- Deterministic, testable behavior
|
||||
- Distributed-safe authentication design</p>
|
||||
<p><strong>Design Guarantees:</strong></p>
|
||||
<ul>
|
||||
<li>Read-only access: no mutation of provider state.</li>
|
||||
<li>Provider-agnostic domain models.</li>
|
||||
<li>Explicit configuration and dependency injection.</li>
|
||||
<li>No implicit global state or environment reads.</li>
|
||||
<li>Deterministic, testable behavior.</li>
|
||||
<li>Distributed-safe authentication design.</li>
|
||||
</ul>
|
||||
<p>Mail Intake favors correctness, clarity, and explicitness over convenience
|
||||
shortcuts.</p>
|
||||
<p><strong>Core Philosophy:</strong>
|
||||
<code>Mail Intake</code> is built as a <strong>contract-first ingestion pipeline</strong>:
|
||||
1. <strong>Layered Decoupling</strong>: Adapters handle transport, Parsers handle format normalization, and Ingestion orchestrates.
|
||||
2. <strong>Provider Agnosticism</strong>: Domain models and core logic never depend on provider-specific (e.g., Gmail) API internals.
|
||||
3. <strong>Stateless Workflows</strong>: The library functions as a read-only pipe, ensuring side-effect-free ingestion.</p>
|
||||
<p><strong>Core Philosophy:</strong></p>
|
||||
<p><code>Mail Intake</code> is built as a <strong>contract-first ingestion pipeline</strong>:</p>
|
||||
<ol>
|
||||
<li><strong>Layered Decoupling</strong>: Adapters handle transport, Parsers handle format
|
||||
normalization, and Ingestion orchestrates.</li>
|
||||
<li><strong>Provider Agnosticism</strong>: Domain models and core logic never depend on
|
||||
provider-specific (e.g., Gmail) API internals.</li>
|
||||
<li><strong>Stateless Workflows</strong>: The library functions as a read-only pipe, ensuring
|
||||
side-effect-free ingestion.</li>
|
||||
</ol>
|
||||
<hr />
|
||||
<h4 id="mail_intake--public-api">Public API</h4>
|
||||
<h3 id="mail_intake--public-api">Public API</h3>
|
||||
<p>The supported public API consists of the following top-level modules:</p>
|
||||
<ul>
|
||||
<li>mail_intake.ingestion</li>
|
||||
<li>mail_intake.adapters</li>
|
||||
<li>mail_intake.auth</li>
|
||||
<li>mail_intake.credentials</li>
|
||||
<li>mail_intake.parsers</li>
|
||||
<li>mail_intake.models</li>
|
||||
<li>mail_intake.config</li>
|
||||
<li>mail_intake.exceptions</li>
|
||||
<li><code>mail_intake.ingestion</code></li>
|
||||
<li><code>mail_intake.adapters</code></li>
|
||||
<li><code>mail_intake.auth</code></li>
|
||||
<li><code>mail_intake.credentials</code></li>
|
||||
<li><code>mail_intake.parsers</code></li>
|
||||
<li><code>mail_intake.models</code></li>
|
||||
<li><code>mail_intake.config</code></li>
|
||||
<li><code>mail_intake.exceptions</code></li>
|
||||
</ul>
|
||||
<p>Classes and functions should be imported explicitly from these modules.
|
||||
No individual symbols are re-exported at the package root.</p>
|
||||
@@ -1381,9 +1390,7 @@ No individual symbols are re-exported at the package root.</p>
|
||||
|
||||
</div>
|
||||
|
||||
</div><ul>
|
||||
<li><a href="mail_intake/">Mail Intake</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user