<?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: Rushikesh</title>
    <description>The latest articles on DEV Community by Rushikesh (@rushikesh_shedage).</description>
    <link>https://dev.to/rushikesh_shedage</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4111092%2F761ad324-52b1-4544-9420-d99b29f363b7.jpg</url>
      <title>DEV Community: Rushikesh</title>
      <link>https://dev.to/rushikesh_shedage</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rushikesh_shedage"/>
    <language>en</language>
    <item>
      <title>I Built a Search Engine With Zero Dependencies — Just Python 3.14's Standard Library</title>
      <dc:creator>Rushikesh</dc:creator>
      <pubDate>Sat, 05 Sep 2026 12:11:57 +0000</pubDate>
      <link>https://dev.to/rushikesh_shedage/i-built-a-search-engine-with-zero-dependencies-just-python-314s-standard-library-2ob2</link>
      <guid>https://dev.to/rushikesh_shedage/i-built-a-search-engine-with-zero-dependencies-just-python-314s-standard-library-2ob2</guid>
      <description>&lt;p&gt;Every modern web app eventually hits the moment it needs search. And almost universally, the playbook looks the same:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;elasticsearch whoosh fastapi uvicorn nltk click
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few minutes later you have an 80MB virtual environment, a daemon process running somewhere, network socket overhead, and a software supply chain that just grew five links longer — all to search a few hundred markdown files or API docs.&lt;/p&gt;

&lt;p&gt;For the &lt;strong&gt;Zero Dependency Hackathon 2026 (Track F: Wildcard)&lt;/strong&gt;, I set out to prove something simpler: &lt;strong&gt;the Python 3.14 standard library already has everything you need to build a fast, local, embeddable search engine from scratch.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The result is &lt;strong&gt;SwiftSearch&lt;/strong&gt; — no pip installs, no lockfiles, no external daemons. Just &lt;code&gt;python -m swiftsearch serve&lt;/code&gt; and it runs.&lt;/p&gt;

&lt;p&gt;Here's what it actually took to replace the standard stack, the stdlib corner that saved the weekend, and the quirks the docs conveniently don't mention.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Normally you'd reach for...&lt;/th&gt;
&lt;th&gt;SwiftSearch uses...&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Elasticsearch / Whoosh&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;collections.defaultdict&lt;/code&gt; inverted index&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;nltk (tokenization)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;unicodedata.normalize()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FastAPI + uvicorn&lt;/td&gt;
&lt;td&gt;&lt;code&gt;http.server&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BM25 ranking config&lt;/td&gt;
&lt;td&gt;A 4-term linear scoring formula&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;requirements.txt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nothing. It's empty.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  1. Replacing Whoosh &amp;amp; Elasticsearch: The Inverted Index
&lt;/h2&gt;

&lt;p&gt;Under the hood, full-text retrieval boils down to two primitives: tokenizing text and building an inverted index. That's it — the rest is optimization.&lt;/p&gt;

&lt;p&gt;Instead of pulling in a dedicated indexing library, the core engine runs entirely on &lt;code&gt;collections.defaultdict&lt;/code&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;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;defaultdict&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Posting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;doc_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;term_frequency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InvertedIndex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# term -&amp;gt; list of Postings
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doc_frequency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By keeping postings and document frequencies in memory, a query like &lt;code&gt;python backend&lt;/code&gt; never scans every document sequentially. The engine jumps straight to the relevant postings lists and computes the union/intersection in sub-millisecond time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transparent ranking beats a black box
&lt;/h3&gt;

&lt;p&gt;Rather than wiring up BM25 configuration knobs, SwiftSearch scores results with a formula you can read in one line:&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="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title_match&lt;/span&gt; &lt;span class="err"&gt;×&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exact_match&lt;/span&gt; &lt;span class="err"&gt;×&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content_match&lt;/span&gt; &lt;span class="err"&gt;×&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;frequency&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No tuning a &lt;code&gt;k1&lt;/code&gt; or &lt;code&gt;b&lt;/code&gt; parameter you don't fully understand. A term in the title will &lt;em&gt;always&lt;/em&gt; outrank the same term buried on page four — and you can explain why to anyone who asks.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The Stdlib Corner That Saved the Weekend: &lt;code&gt;unicodedata&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The usual excuse for reaching for &lt;code&gt;nltk&lt;/code&gt; or a heavy regex tokenizer library is Unicode handling — accents, umlauts, non-ASCII input that breaks naive string slicing.&lt;/p&gt;

&lt;p&gt;Turns out &lt;code&gt;unicodedata&lt;/code&gt;, sitting quietly in the standard library, handles all of it:&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;import&lt;/span&gt; &lt;span class="n"&gt;unicodedata&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tokenize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&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="c1"&gt;# Normalize to canonical composed form
&lt;/span&gt;    &lt;span class="n"&gt;normalized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;unicodedata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;NFC&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;char&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;normalized&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;char&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isalnum&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;char&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Twenty lines. No compiled regex, no backtracking overhead, no external dependency — and it splits cleanly on punctuation while staying Unicode-safe.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. What Turned Out Harder Than the Docs Made It Look
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;http.server&lt;/code&gt; has sharp edges
&lt;/h3&gt;

&lt;p&gt;Everyone reaches for FastAPI or Flask when they think "Python web API." &lt;code&gt;http.server&lt;/code&gt; has a reputation for being "not for production" — and building an embeddable engine on top of it surfaced exactly why, in two specific ways.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dual routing (API + static assets).&lt;/strong&gt; &lt;code&gt;SimpleHTTPRequestHandler&lt;/code&gt; serves files; &lt;code&gt;BaseHTTPRequestHandler&lt;/code&gt; handles custom routes. Getting &lt;code&gt;GET /search?q=...&lt;/code&gt; to return raw JSON while &lt;code&gt;GET /&lt;/code&gt; serves the command-palette demo — without tripping CORS — meant manually intercepting paths and setting headers by hand:&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/demo.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text/html; charset=utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end_headers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;web/demo.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The shortcut collision.&lt;/strong&gt; In the frontend, pressing &lt;code&gt;/&lt;/code&gt; was supposed to focus the search palette — a common command-palette pattern. But browsers fire &lt;code&gt;keydown&lt;/code&gt; &lt;em&gt;before&lt;/em&gt; the input gains focus, so the literal &lt;code&gt;/&lt;/code&gt; character leaked straight into the query string. Every shortcut press sent a request like &lt;code&gt;/search?q=%2Fauth&lt;/code&gt;. The fix was one line — &lt;code&gt;e.preventDefault()&lt;/code&gt; on the global listener — but finding it wasn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The Zero-Dependency Receipt
&lt;/h2&gt;

&lt;p&gt;At submission time, the dependency check was one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Verify no external imports exist across the codebase&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-rE&lt;/span&gt; &lt;span class="s2"&gt;"^import |^from "&lt;/span&gt; swiftsearch/ | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every import resolved to a Python 3.14 built-in: &lt;code&gt;argparse&lt;/code&gt;, &lt;code&gt;collections&lt;/code&gt;, &lt;code&gt;dataclasses&lt;/code&gt;, &lt;code&gt;html&lt;/code&gt;, &lt;code&gt;http.server&lt;/code&gt;, &lt;code&gt;json&lt;/code&gt;, &lt;code&gt;pathlib&lt;/code&gt;, &lt;code&gt;time&lt;/code&gt;, &lt;code&gt;unicodedata&lt;/code&gt;, &lt;code&gt;urllib.parse&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;No virtualenv. No &lt;code&gt;pip install&lt;/code&gt;. No lockfile to audit for vulnerabilities. Clone the repo, run &lt;code&gt;python -m swiftsearch serve&lt;/code&gt;, and it's already searching.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaway
&lt;/h2&gt;

&lt;p&gt;We reach for packages out of habit more often than out of necessity. Building SwiftSearch was a reminder that the standard library in a modern runtime is more capable than most of us give it credit for — and that stripping away the abstraction layers to build an inverted index from first principles isn't just feasible, it's genuinely satisfying.&lt;/p&gt;

&lt;p&gt;If you've replaced a "you obviously need a library for that" tool with nothing but the standard library, I'd like to hear about it in the comments.&lt;/p&gt;

</description>
      <category>python</category>
      <category>showdev</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
