<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Prod on ✰Vicki Boykis✰</title><link>https://vickiboykis.com/tags/prod/</link><description>Recent content in Prod on ✰Vicki Boykis✰</description><generator>Hugo</generator><language>en-US</language><copyright>Copyright © 2026, Vicki Boykis.</copyright><lastBuildDate>Tue, 14 Jan 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://vickiboykis.com/tags/prod/index.xml" rel="self" type="application/rss+xml"/><item><title>How FastAPI path operations work</title><link>https://vickiboykis.com/2025/01/14/how-fastapi-path-operations-work/</link><pubDate>Tue, 14 Jan 2025 00:00:00 +0000</pubDate><guid>https://vickiboykis.com/2025/01/14/how-fastapi-path-operations-work/</guid><description>&lt;p>If you&amp;rsquo;re building a new Python web app these days, there&amp;rsquo;s a good chance you&amp;rsquo;re using FastAPI. There are a lot of features that make FastAPI easy to get started with. There are also a lot of nuances that take a while to understand. One feature I&amp;rsquo;ve been untangling is the way FastAPI manages calls to API routes &lt;a href="https://fastapi.tiangolo.com/tutorial/path-params/">via decorated path parameters.&lt;/a> The new year is a perfect time to take a deeper dive.&lt;/p>
&lt;h1 id="what-happens-in-a-web-server">What happens in a web server&lt;/h1>
&lt;p>When we build a web app, one of the critical components is &lt;a href="https://newsletter.vickiboykis.com/archive/when-you-write-a-web-server-but-you-get-served/">a web server&lt;/a>, a program that listens for incoming requests from the network. It then translates those requests into methods that are called in the backend.&lt;/p>
&lt;p>To better understand what&amp;rsquo;s going on under the covers, we can first implement a simple web server using the &lt;code>http.server&lt;/code> module &lt;a href="https://github.com/python/cpython/blob/main/Lib/http/server.py">included in Python&amp;rsquo;s standard library&lt;/a>.&lt;/p>
&lt;p>We need to write a program that listens on a port and accepts HTTP requests. It accepts the request, parses the path route, and parses any data attached to the HTTP call. Or, &lt;a href="https://crawshaw.io/blog/programming-with-llms">&amp;ldquo;All I want is to cURL and parse a JSON object&amp;rdquo;&lt;/a>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> json
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> http.server &lt;span style="color:#f92672">import&lt;/span> BaseHTTPRequestHandler, HTTPServer
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> urllib.parse &lt;span style="color:#f92672">import&lt;/span> urlparse, parse_qs
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">RequestHandler&lt;/span>(BaseHTTPRequestHandler):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">parse_path&lt;/span>(self, request_path: str)&lt;span style="color:#f92672">-&amp;gt;&lt;/span> dict:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> Parse request path
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> parsed &lt;span style="color:#f92672">=&lt;/span> urlparse(request_path)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> params_dict &lt;span style="color:#f92672">=&lt;/span> parse_qs(parsed&lt;span style="color:#f92672">.&lt;/span>query)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> params_dict
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">store_urls&lt;/span>(self, request_path: str)&lt;span style="color:#f92672">-&amp;gt;&lt;/span> &lt;span style="color:#66d9ef">None&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> Parse URLs and store them
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> params &lt;span style="color:#f92672">=&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>parse_path(request_path)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> key, val &lt;span style="color:#f92672">in&lt;/span> params&lt;span style="color:#f92672">.&lt;/span>items():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>data_store&lt;span style="color:#f92672">.&lt;/span>put_data(val[&lt;span style="color:#ae81ff">0&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">return_k_json&lt;/span>(self, k:dict)&lt;span style="color:#f92672">-&amp;gt;&lt;/span> BinaryIO:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> Return json response
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>send_response(&lt;span style="color:#ae81ff">200&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>send_header(&lt;span style="color:#e6db74">&amp;#34;Content-type&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;application/json&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>end_headers()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># Contains the output stream for writing a response back to the client. &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># BufferedIOBase that writes to a stream&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># See https://docs.python.org/3/library/io.html#io.BufferedIOBase.write&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>wfile&lt;span style="color:#f92672">.&lt;/span>write(json&lt;span style="color:#f92672">.&lt;/span>dumps(k)&lt;span style="color:#f92672">.&lt;/span>encode(&lt;span style="color:#e6db74">&amp;#39;utf-8&amp;#39;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">bad_request&lt;/span>(self):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> Handle bad request
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>send_response(&lt;span style="color:#ae81ff">400&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>send_header(&lt;span style="color:#e6db74">&amp;#34;Content-type&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;application/json&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>end_headers()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">do_GET&lt;/span>(self):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> request_path &lt;span style="color:#f92672">=&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>path
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>path &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;/&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>return_k_json({&lt;span style="color:#e6db74">&amp;#34;ciao&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;mondo&amp;#34;&lt;/span>})
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> request_path&lt;span style="color:#f92672">.&lt;/span>startswith(&lt;span style="color:#e6db74">&amp;#34;/get&amp;#34;&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> key &lt;span style="color:#f92672">=&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>parse_path(request_path)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>return_k_json({&lt;span style="color:#e6db74">&amp;#34;jars&amp;#34;&lt;/span>: key[&lt;span style="color:#e6db74">&amp;#34;key&amp;#34;&lt;/span>]})
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>send_response(&lt;span style="color:#ae81ff">200&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>bad_request()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>end_headers()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">do_POST&lt;/span>(self):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> request_path &lt;span style="color:#f92672">=&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>path
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> request_path&lt;span style="color:#f92672">.&lt;/span>startswith(&lt;span style="color:#e6db74">&amp;#34;/set&amp;#34;&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>store_urls(request_path)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>send_response(&lt;span style="color:#ae81ff">200&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>bad_request()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> __name__ &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;__main__&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> host &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;localhost&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> port &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">8000&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> server &lt;span style="color:#f92672">=&lt;/span> HTTPServer((host, port), RequestHandler)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;Server started http://&lt;/span>&lt;span style="color:#e6db74">%s&lt;/span>&lt;span style="color:#e6db74">:&lt;/span>&lt;span style="color:#e6db74">%s&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#f92672">%&lt;/span> (host, port))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> server&lt;span style="color:#f92672">.&lt;/span>serve_forever()
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>What&amp;rsquo;s going on here?&lt;/p>
&lt;p>Let’s say that we produce Nulltella, an artisinal hazlenut spread for statisticians, and are looking to build a web app that keeps track of &lt;a href="https://vickiboykis.com/2024/02/28/gguf-the-long-way-around/">all of our Nulltella jars so we can later stand up a prediction service.&lt;/a>&lt;/p>
&lt;figure>&lt;img src="https://vickiboykis.com/images/308394339-66ca00e6-1baf-4eb0-9d3d-112966beb797.png" width="200">
&lt;/figure>

&lt;p>We would start by designing a super simple API: As users,&lt;/p>
&lt;ul>
&lt;li>we want to test the server and get back a simple response&lt;/li>
&lt;li>we&amp;rsquo;d like to add jars to our inventory, and&lt;/li>
&lt;li>to see the jars we added.&lt;/li>
&lt;/ul>
&lt;p>We translate these actions to GET and PUT requests so we can write HTTP calls for them. For simplicity&amp;rsquo;s sake, we won&amp;rsquo;t actually store them server-side but we will write them so we can can very simply see how to send data to our app:&lt;/p>
&lt;p>We want to test the server:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&amp;gt; python serve.py
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;gt; curl -X POST http://localhost:8000/
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;gt; &lt;span style="color:#f92672">{&lt;/span>&lt;span style="color:#e6db74">&amp;#34;ciao&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;mondo&amp;#34;&lt;/span>&lt;span style="color:#f92672">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>We want to store items:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&amp;gt; curl -X POST http://localhost:8000/set&lt;span style="color:#ae81ff">\?&lt;/span>key&lt;span style="color:#ae81ff">\=&lt;/span>&lt;span style="color:#ae81ff">8&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">200&lt;/span> OK
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And get back the stored items:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&amp;gt; curl -X GET http://localhost:8000/get&lt;span style="color:#ae81ff">\?&lt;/span>key&lt;span style="color:#ae81ff">\=&lt;/span>&lt;span style="color:#ae81ff">8&lt;/span> 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;gt; &lt;span style="color:#f92672">{&lt;/span>&lt;span style="color:#e6db74">&amp;#34;jars&amp;#34;&lt;/span>: &lt;span style="color:#f92672">[&lt;/span>&lt;span style="color:#e6db74">&amp;#34;8&amp;#34;&lt;/span>&lt;span style="color:#f92672">]}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Our server needs a way to parse the key pieces of information it receives:&lt;/p>
&lt;ol>
&lt;li>They type of request. &lt;code>do_GET&lt;/code> and &lt;code>do_POST&lt;/code> &lt;a href="https://stackoverflow.com/a/50944691">handle this implicitly&lt;/a> in the &lt;a href="https://docs.python.org/3/library/http.server.html#http.server.SimpleHTTPRequestHandler.do_GET">HTTP implementation&lt;/a>.&lt;/li>
&lt;li>The parameters we pass to the path request so that we can do something with them&lt;/li>
&lt;li>A route to a method inside our application itself that processes the data&lt;/li>
&lt;/ol>
&lt;p>In our simple server, the heart of the routing happens at the method level. If we send a base path, we return &lt;code>{&amp;quot;ciao&amp;quot;: &amp;quot;mondo&amp;quot;}&lt;/code> . Otherwise, we return the amount of jars we&amp;rsquo;ve passed in via the request path by parsing the parameters in the path.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">do_GET&lt;/span>(self) &lt;span style="color:#f92672">-&amp;gt;&lt;/span> &lt;span style="color:#66d9ef">None&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> request_path &lt;span style="color:#f92672">=&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>path
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>path &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;/&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>return_k_json({&lt;span style="color:#e6db74">&amp;#34;ciao&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;mondo&amp;#34;&lt;/span>})
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> request_path&lt;span style="color:#f92672">.&lt;/span>startswith(&lt;span style="color:#e6db74">&amp;#34;/get&amp;#34;&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> key &lt;span style="color:#f92672">=&lt;/span> self&lt;span style="color:#f92672">.&lt;/span>parse_path(request_path)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># action performed within the web app here&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>return_k_json({&lt;span style="color:#e6db74">&amp;#34;jars&amp;#34;&lt;/span>: key[&lt;span style="color:#e6db74">&amp;#34;key&amp;#34;&lt;/span>]})
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>send_response(&lt;span style="color:#ae81ff">200&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>bad_request()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self&lt;span style="color:#f92672">.&lt;/span>end_headers()
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>We can see how this can become complicated quickly. For example, what if we have multiple operations we perform during a &lt;code>GET&lt;/code> : what if we get data from a database, or a cache, or we retrieve assets? We&amp;rsquo;ll have different methods that we process depending on how the path is parsed. What if we also have &lt;code>PUT/DELETE&lt;/code> verbs? What if we need authentication? To write to a database? Static pages? Our code complexity relative to our starting point starts to grow, and we now need a framework.&lt;/p>
&lt;h2 id="starlette">Starlette&lt;/h2>
&lt;p>Early Python web dev frameworks include juggernauts &lt;a href="https://www.david-dahan.com/blog/comparing-fastapi-and-django">Django&lt;/a> and Flask. More recently, since Python&amp;rsquo;s async story has grown stronger, frameworks like &lt;a href="https://www.starlette.io/">Starlette&lt;/a> have come onto the scene to include async functionality out of the box.&lt;/p>
&lt;p>Starlette was built by the creator of Django Rest Framework and includes lightweight operations for the core functionality of HTTP calls and additional operations like web sockets, with the added bonus of being async by default.&lt;/p>
&lt;p>To manage an HTTP call the same way we would with our simple server, we can do the following with Starlette:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> starlette.applications &lt;span style="color:#f92672">import&lt;/span> Starlette
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> starlette.responses &lt;span style="color:#f92672">import&lt;/span> JSONResponse
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> starlette.routing &lt;span style="color:#f92672">import&lt;/span> Route
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">async&lt;/span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">homepage&lt;/span>(request):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> JSONResponse({&lt;span style="color:#e6db74">&amp;#39;ciao&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;mondo&amp;#39;&lt;/span>})
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app &lt;span style="color:#f92672">=&lt;/span> Starlette(debug&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">True&lt;/span>, routes&lt;span style="color:#f92672">=&lt;/span>[Route(&lt;span style="color:#e6db74">&amp;#39;/&amp;#39;&lt;/span>, homepage),])
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>We start an instance of a Starlette application, which has &lt;a href="https://github.com/encode/starlette/blob/7c0d1e6d1a499e6eeb68d447321838be3927e83b/starlette/routing.py#L208">processes routes.&lt;/a> &lt;a href="https://www.starlette.io/routing/">Each route is linked&lt;/a>, at the path level, to the actual method it calls. If Starlette sees that specific route, it calls the method, taking into account logic for parsing and reading HTTP request headers and bodies.&lt;/p>
&lt;p>What if we want to add a second method call based on a different route, getting our jar count again?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> starlette.applications &lt;span style="color:#f92672">import&lt;/span> Starlette
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> starlette.responses &lt;span style="color:#f92672">import&lt;/span> JSONResponse
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> starlette.routing &lt;span style="color:#f92672">import&lt;/span> Route
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">async&lt;/span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">homepage&lt;/span>(request):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> JSONResponse({&lt;span style="color:#e6db74">&amp;#39;ciao&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;mondo&amp;#39;&lt;/span>})
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">async&lt;/span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">get_jars&lt;/span>(request):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> JSONResponse({&lt;span style="color:#e6db74">&amp;#39;jars&amp;#39;&lt;/span>: [&lt;span style="color:#e6db74">&amp;#39;8&amp;#39;&lt;/span>]})
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app &lt;span style="color:#f92672">=&lt;/span> Starlette(debug&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">True&lt;/span>, routes&lt;span style="color:#f92672">=&lt;/span>[
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Route(&lt;span style="color:#e6db74">&amp;#39;/&amp;#39;&lt;/span>, homepage),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Route(&lt;span style="color:#e6db74">&amp;#39;/get_jars&amp;#39;&lt;/span>, get_jars)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>])
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>We see that we are also passing and processing params, and there is &lt;a href="https://github.com/encode/starlette/blob/7c0d1e6d1a499e6eeb68d447321838be3927e83b/starlette/_utils.py#L85">logic that processes the path params&lt;/a> based on the method as &lt;a href="https://github.com/encode/starlette/blob/7c0d1e6d1a499e6eeb68d447321838be3927e83b/docs/requests.md?plain=1#L5">they come in from the request&lt;/a>.&lt;/p>
&lt;h2 id="fastapis-implementation">FastAPI&amp;rsquo;s implementation&lt;/h2>
&lt;p>FastAPI wraps Starlette - &amp;ldquo;as it is basically Starlette on steroids&amp;rdquo; per the docs - and &lt;a href="https://fastapi.tiangolo.com/alternatives/#intro">includes Pydantic type validation&lt;/a> at the logical boundaries of the application.&lt;/p>
&lt;p>Under the covers, when we instantiate a FastAPI application, it&amp;rsquo;s really &amp;ldquo;just&amp;rdquo; an instance of a Starlette application with properties that we override at the application level.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> fastapi &lt;span style="color:#f92672">import&lt;/span> FastAPI
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>app &lt;span style="color:#f92672">=&lt;/span> FastAPI()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@app.get&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;/&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">async&lt;/span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">root&lt;/span>():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> {&lt;span style="color:#e6db74">&amp;#34;ciao&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;mondo&amp;#34;&lt;/span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">@app.get&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;/jars/&lt;/span>&lt;span style="color:#e6db74">{id}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">async&lt;/span> &lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">get_jars&lt;/span>(id):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> {&lt;span style="color:#e6db74">&amp;#34;message&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;jars: &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>id&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In development, FastAPI uses &lt;a href="https://www.uvicorn.org/"> &lt;code>uvicorn&lt;/code> &lt;/a>, an &lt;a href="https://asgi.readthedocs.io/en/latest/">ASGI server&lt;/a> to listen for incoming requests and handle them according to the routes defined in your application.&lt;/p>
&lt;p>Uvicorn initializes the ASGI server, binds it to a &lt;a href="https://github.com/encode/uvicorn/blob/ae8253f10b9f73f10e92be52a0d9f70696b77c62/uvicorn/server.py#L115">socket connections&lt;/a> on &lt;a href="https://github.com/encode/uvicorn/blob/ae8253f10b9f73f10e92be52a0d9f70696b77c62/uvicorn/main.py#L73">port &lt;code>8000&lt;/code> &lt;/a>, and starts listening for incoming connections. So, when we send a &lt;code>GET&lt;/code> request to the main route hosted by default on port 8000, we expect to get back &lt;code>ciao mondo&lt;/code> as a response.&lt;/p>
&lt;p>Like our previous applications, FastAPI is still delegating path operations and methods to a router that processes them and parses parameters, but it wraps these in a &lt;a href="https://nedbatchelder.com/blog/202210/decorator_shortcuts.html">Python decorator&lt;/a>. This is easier to write, but adds a level of complexity at the layer of understanding how the path processing actually happens.&lt;/p>
&lt;p>When we perform a path operation in FastAPI, we&amp;rsquo;re performing the equivalent work of routing that we do with our simple method, but with a lot more rigor and nested definitions.&lt;/p>
&lt;p>Within our simple server, we:&lt;/p>
&lt;ol>
&lt;li>Start the server&lt;/li>
&lt;li>Listen on port &lt;code>8000&lt;/code> for incoming requests&lt;/li>
&lt;li>When we receive a request, we route it to the &lt;code>do_GET &lt;/code>method&lt;/li>
&lt;li>Depending on the path of the request, we route it to &lt;code>&amp;quot;/&amp;quot;&lt;/code>&lt;/li>
&lt;li>We return the results to the client via a &lt;code>200&lt;/code> status&lt;/li>
&lt;/ol>
&lt;p>In FastAPI, we:&lt;/p>
&lt;ol>
&lt;li>Start the uvicorn web server (if in development mode, if production we have to choose gunicorn using the &lt;a href="https://stackoverflow.com/a/71546833">compatible worker class&lt;/a>)&lt;/li>
&lt;li>Listen on port &lt;code>8000&lt;/code> for incoming requests&lt;/li>
&lt;li>We instantiate an instance of the FastAPI application&lt;/li>
&lt;li>This in turn instantiates an instance of Starlette&lt;/li>
&lt;li>When we receive a &lt;code>GET&lt;/code> request, it&amp;rsquo;s routed to the application&amp;rsquo;s &lt;a href="https://github.com/fastapi/fastapi/blob/144f09ea146b2cc026bf317f730aa0e0dbc3de24/fastapi/applications.py#L1460">&lt;code>self.get&lt;/code>&lt;/a> method&lt;/li>
&lt;li>This in turn calls &lt;code>self.router.get&lt;/code> with &lt;a href="https://github.com/fastapi/fastapi/blob/144f09ea146b2cc026bf317f730aa0e0dbc3de24/fastapi/applications.py#L1807">the path operation&lt;/a>&lt;/li>
&lt;li>The router is an instance of &lt;a href="https://github.com/fastapi/fastapi/blob/144f09ea146b2cc026bf317f730aa0e0dbc3de24/fastapi/applications.py#L932">&lt;code>routing.APIRouter&lt;/code>&lt;/a>&lt;/li>
&lt;li>The &lt;code>.get&lt;/code> &lt;a href="https://github.com/fastapi/fastapi/blob/144f09ea146b2cc026bf317f730aa0e0dbc3de24/fastapi/routing.py#L1366">method on &lt;code>APIRouter&lt;/code> takes the path&lt;/a> and retunrs &lt;code>return self.api_route&lt;/code>. This is the point where the decorater is actually called - &lt;a href="https://github.com/fastapi/fastapi/blob/144f09ea146b2cc026bf317f730aa0e0dbc3de24/fastapi/routing.py#L963">we can see the decorator in that method&lt;/a> takes a &lt;code>DecoratedCallable&lt;/code> function as input and returns a decorated &lt;code>add_api_route&lt;/code>, which actually &lt;a href="https://github.com/fastapi/fastapi/blob/144f09ea146b2cc026bf317f730aa0e0dbc3de24/fastapi/routing.py#L961">appends the route to the list of routes.&lt;/a>&lt;/li>
&lt;/ol>
&lt;p>This is purely the set of steps that happens for correct routing - and we didn&amp;rsquo;t yet address how the path parameters in the path are processed.&lt;/p>
&lt;h1 id="path-parameter-routing">Path Parameter Routing&lt;/h1>
&lt;p>Path parameter routing happens in Starlette, where &lt;a href="https://github.com/encode/starlette/blob/0109dce29b76c64e93c56c01fa5020860f935ed3/starlette/requests.py#L182">path parameters are parsed out&lt;/a> of the &lt;a href="https://github.com/encode/starlette/blob/0109dce29b76c64e93c56c01fa5020860f935ed3/starlette/requests.py#L76">request&lt;/a> into a dictionary (just like we do in our simple web application), via the magic of &lt;a href="https://github.com/encode/starlette/blob/0109dce29b76c64e93c56c01fa5020860f935ed3/starlette/templating.py#L123">Jinja Templating.&lt;/a>&lt;/p>
&lt;h2 id="tl-dr">TL; DR&lt;/h2>
&lt;p>When we write a route in FastAPI that accepts path parameters, we are creating a lengthy callstack that goes through several levels of logic in FastAPI using decorators as input into an application that routes requests and appends methods using decorators to a group of route methods; those requests are then passed onto Starlette which does the work of parsing the path variables, using Jinja templates, into dictionaries which the application can then work with and return data to you!&lt;/p></description></item></channel></rss>