<?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>Forem: Bocadillo</title>
    <description>The latest articles on Forem by Bocadillo (@bocadillo).</description>
    <link>https://forem.com/bocadillo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F459%2F365d9932-0aab-4886-8e64-4f3ede90cad3.png</url>
      <title>Forem: Bocadillo</title>
      <link>https://forem.com/bocadillo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/bocadillo"/>
    <language>en</language>
    <item>
      <title>Building a real-time chatbot server in Python with WebSocket, ChatterBot and Bocadillo</title>
      <dc:creator>Florimond Manca</dc:creator>
      <pubDate>Tue, 19 Mar 2019 09:15:18 +0000</pubDate>
      <link>https://forem.com/bocadillo/building-a-real-time-chatbot-server-in-python-with-websocket-chatterbot-and-bocadillo-482g</link>
      <guid>https://forem.com/bocadillo/building-a-real-time-chatbot-server-in-python-with-websocket-chatterbot-and-bocadillo-482g</guid>
      <description>&lt;p&gt;&lt;em&gt;This post is an adaptation of the &lt;a href="https://bocadilloproject.github.io/getting-started/tutorial.html" rel="noopener noreferrer"&gt;official Bocadillo tutorial&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Hi everyone! Today's post is going to be a little special. Some of you may remember this post I wrote some months back: &lt;a href="https://dev.to/florimondmanca/how-i-built-a-python-web-framework-and-became-an-open-source-maintainer-3okd"&gt;How I Built A Python Web Framework And Became An Open Source Maintainer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Since then, I kept working on &lt;a href="https://github.com/bocadilloproject/bocadillo" rel="noopener noreferrer"&gt;Bocadillo&lt;/a&gt;, and it's been a great time! In fact, I learnt just last week that &lt;strong&gt;I will be flying to Munich at the end of May to give a talk at &lt;a href="https://pyconweb.com" rel="noopener noreferrer"&gt;PyConWeb 2019&lt;/a&gt;!&lt;/strong&gt; This will be my first conference and talk ever, so needless to say that I'm SUPER EXCITED! 🙌🤩&lt;/p&gt;

&lt;p&gt;Another great news is that &lt;strong&gt;Bocadillo v0.13 has just been released&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1107029038923743232-902" src="https://platform.twitter.com/embed/Tweet.html?id=1107029038923743232"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1107029038923743232-902');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1107029038923743232&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;With all these good vibes in the air, I finally decided to go ahead and publish a thorough tutorial.&lt;/p&gt;

&lt;p&gt;Without further ado, here's the plot: we're going to try and build a &lt;strong&gt;chatbot server&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;Bocadillo has many features built-in, so this is a great opportunity to go through some aspects of building web services with Bocadillo.&lt;/p&gt;

&lt;p&gt;In this tutorial, you'll get to play with chatbots but also learn how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;WebSocket&lt;/strong&gt; to handle multiple connections in real-time.&lt;/li&gt;
&lt;li&gt;Create REST endpoints.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;providers&lt;/strong&gt; to inject reusable resources into views.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test&lt;/strong&gt; a Bocadillo application using &lt;a href="https://docs.pytest.org" rel="noopener noreferrer"&gt;pytest&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Still wondering how we'll build something seemingly as complex as a chatbot? Well, you probably know that Python has a &lt;em&gt;gigantic&lt;/em&gt; data science ecosystem. I would've bet actual money there would be a chatbot framework somewhere out there.&lt;/p&gt;

&lt;p&gt;Turns out — there was! After some research, I stumbled upon &lt;a href="https://github.com/gunthercox/ChatterBot" rel="noopener noreferrer"&gt;ChatterBot&lt;/a&gt;. It looks pretty solid and popular, so we'll use it to build &lt;strong&gt;Diego&lt;/strong&gt;, a friendly conversational agent. Don't worry, this won't require &lt;em&gt;any&lt;/em&gt; background in data science nor chatbot technology!&lt;/p&gt;

&lt;p&gt;Sounds exciting? Alright, let's dive in! 🙌&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the project
&lt;/h2&gt;

&lt;p&gt;First things first: let's set up our project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open up a terminal, and create an empty directory somewhere on your computer, then &lt;code&gt;cd&lt;/code&gt; to it:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; ~/dev/bocadillo-chatbot
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/dev/bocadillo-chatbot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Install Bocadillo and ChatterBot. We're using &lt;a href="https://pipenv.readthedocs.io" rel="noopener noreferrer"&gt;pipenv&lt;/a&gt; to install dependencies here, but you can also use plain ol' &lt;code&gt;pip&lt;/code&gt; + &lt;code&gt;virtualenv&lt;/code&gt; too.&lt;/li&gt;
&lt;/ul&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Note: pytz is required by chatterbot.&lt;/span&gt;
pipenv &lt;span class="nb"&gt;install &lt;/span&gt;bocadillo chatterbot pytz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create an empty &lt;code&gt;app.py&lt;/code&gt; script. This is where we'll create the application later on:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;touch &lt;/span&gt;app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We should now have the following directory structure:&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="nv"&gt;$ &lt;/span&gt;tree
&lt;span class="nb"&gt;.&lt;/span&gt;
├── Pipfile
├── Pipfile.lock
└── app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bootstrapping the application
&lt;/h2&gt;

&lt;p&gt;Now, let's write the app skeleton in &lt;code&gt;app.py&lt;/code&gt;. Hang tight — first decent bit of code incoming:&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="c1"&gt;# app.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bocadillo&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;App&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;App&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;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you've ever worked with Flask or, well, nearly any Python web framework really, this should look oddly familiar. Nearly boring. Who cares? It works! Check for yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you go to &lt;a href="http://localhost:8000" rel="noopener noreferrer"&gt;http://localhost:8000&lt;/a&gt; and get a &lt;code&gt;404 Not Found&lt;/code&gt; response, you're all good! Enter &lt;code&gt;Ctrl+C&lt;/code&gt; in your terminal to stop the server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing the WebSocket endpoint
&lt;/h2&gt;

&lt;p&gt;We're now ready to get to the meat of it! The first thing we'll build is the &lt;strong&gt;WebSocket endpoint&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you're not familiar with WebSocket, don't worry — here's a 10-word summary: it allows a server and a client to exchange messages in a bidirectional way. It's good old sockets reinvented for the web.&lt;/p&gt;

&lt;p&gt;Due to their &lt;strong&gt;bidirectional nature&lt;/strong&gt;, they're very suitable for the kind of application we're building here — some sort of &lt;em&gt;conversation&lt;/em&gt; between a client and a server (i.e. our chatbot).&lt;/p&gt;

&lt;p&gt;If you're interested in learning more about WebSockets in Python, I strongly recommend this talk: &lt;a href="https://www.youtube.com/watch?v=PjiXkJ6P9pQ&amp;amp;frags=pl%2Cwn" rel="noopener noreferrer"&gt;A beginner's guide to WebSockets&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Alright, so we won't plug the chatbot in yet. Instead, let's make the server send back any message it receives — a behavior also known as an "echo" endpoint.&lt;/p&gt;

&lt;p&gt;Add the following between the &lt;code&gt;app&lt;/code&gt; object declaration and the &lt;code&gt;app.run()&lt;/code&gt; block in &lt;code&gt;app.py&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="nd"&gt;@app.websocket_route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/conversation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;converse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few minimal explanations here, for the curious:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This defines a WebSocket endpoint which will be accessible at the &lt;code&gt;ws://localhost:8000/conversation&lt;/code&gt; location.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;async for message in ws:&lt;/code&gt; line iterates over messages received over the WebSocket.&lt;/li&gt;
&lt;li&gt;Lastly, &lt;code&gt;await ws.send(message)&lt;/code&gt; sends the received &lt;code&gt;message&lt;/code&gt; as-is back to the client.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Trying out the WebSocket endpoint
&lt;/h2&gt;

&lt;p&gt;How about we try this out by creating a WebSocket client? Fear not — we won't need to write any JavaScript. We'll stick to Python and use the &lt;a href="https://websockets.readthedocs.io" rel="noopener noreferrer"&gt;websockets&lt;/a&gt; library, which comes installed with Bocadillo.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;client.py&lt;/code&gt; file and paste the following code there. What it does is connect to the WebSocket endpoint and run a simple REPL:&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="c1"&gt;# client.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;contextlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;suppress&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;websockets&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&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="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;websockets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&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;websocket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&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;suppress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;KeyboardInterrupt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# See asyncio docs for the Python 3.6 equivalent to .run().
&lt;/span&gt;    &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ws://localhost:8000/conversation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the server-side application with &lt;code&gt;python app.py&lt;/code&gt; and, in a separate terminal, start the &lt;code&gt;client.py&lt;/code&gt; script. You should be greeted with a &lt;code&gt;&amp;gt;&lt;/code&gt; prompt. If so, start chatting!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python client.py
&amp;gt; Hi!
Hi!
&amp;gt; Is there anyone here?
Is there anyone here?
&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pretty cool, isn't it? 🤓&lt;/p&gt;

&lt;p&gt;Type &lt;code&gt;Ctrl+C&lt;/code&gt; to exit the session and close the WebSocket connection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hello, Diego!
&lt;/h2&gt;

&lt;p&gt;Now that we're able to make the server and a client communicate, how about we replace the echo implementation with an actual, intelligent and friendly chatbot?&lt;/p&gt;

&lt;p&gt;This is where &lt;a href="https://github.com/gunthercox/ChatterBot" rel="noopener noreferrer"&gt;ChatterBot&lt;/a&gt; comes in! We'll create a chatbot rightfully named &lt;strong&gt;Diego&lt;/strong&gt; — a chatbot speaking the asynchronous salsa. 🕺&lt;/p&gt;

&lt;p&gt;Go ahead and create a &lt;code&gt;chatbot.py&lt;/code&gt; file, and add Diego in there:&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="c1"&gt;# chatbot.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;chatterbot&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatBot&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;chatterbot.trainers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatterBotCorpusTrainer&lt;/span&gt;

&lt;span class="n"&gt;diego&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChatBot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Diego&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;trainer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChatterBotCorpusTrainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;diego&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;trainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;train&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chatterbot.corpus.english.greetings&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;chatterbot.corpus.english.conversations&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(ChatterBot's chatbots are quite dumb out of the box, so the code above trains Diego on an English corpus to make him a bit smarter.)&lt;/p&gt;

&lt;p&gt;At this point, you can try out the chatbot in a Python interpreter:&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="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;chatbot&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;diego&lt;/span&gt;  &lt;span class="c1"&gt;# Be patient — this may take a few seconds to load!
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;diego&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hi, there!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Statement&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;There&lt;/span&gt; &lt;span class="n"&gt;should&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="n"&gt;one&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;preferably&lt;/span&gt; &lt;span class="n"&gt;only&lt;/span&gt; &lt;span class="n"&gt;one&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;obvious&lt;/span&gt; &lt;span class="n"&gt;way&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;do&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Hmm. Interesting response! 🐍)&lt;/p&gt;

&lt;p&gt;Let's now plug Diego into the WebSocket endpoint: each time we receive a new &lt;code&gt;message&lt;/code&gt;, we'll give it to Diego and send his response back.&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="c1"&gt;# app.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;chatbot&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;diego&lt;/span&gt;

&lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="nd"&gt;@app.websocket_route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/conversation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;converse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;diego&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run the server/client setup from earlier, you can now see that Diego converses with us over the WebSocket!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python client.py
&amp;gt; Hi there!
I am a chat bot. I am the original chat bot. Did you know that I am incapable of error?
&amp;gt; Where are you?
I am on the Internet.
&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks like Diego is a jokester. 😉&lt;/p&gt;

&lt;h2&gt;
  
  
  Refactoring the chatbot as a provider
&lt;/h2&gt;

&lt;p&gt;Clients are now able to chat with Diego over a WebSocket connection. That's great!&lt;/p&gt;

&lt;p&gt;However, there are a few non-functional issues with our current setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Loading Diego is quite expensive: it takes about ten seconds on a regular laptop.&lt;/li&gt;
&lt;li&gt;Because of the &lt;code&gt;import&lt;/code&gt; at the top of the script, we'd load Diego every time we import the &lt;code&gt;app&lt;/code&gt; module. Not great!&lt;/li&gt;
&lt;li&gt;Diego is injected as a global dependency into the WebSocket endpoint: we can't swap it with another implementation (especially useful during tests), and it's not immediately clear that the endpoint depends on it at first sight.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you think about it, Diego is a &lt;strong&gt;resource&lt;/strong&gt; — ideally, it should only be made available to the WebSocket endpoint at the time of processing a connection request.&lt;/p&gt;

&lt;p&gt;So, there must be a better way… and there is: &lt;a href="https://bocadilloproject.github.io/guides/injection/" rel="noopener noreferrer"&gt;providers&lt;/a&gt;. ✨&lt;/p&gt;

&lt;p&gt;Providers are a unique feature of Bocadillo. They were inspired by &lt;a href="https://docs.pytest.org/en/latest/fixture.html" rel="noopener noreferrer"&gt;pytest fixtures&lt;/a&gt; and offer an elegant, modular and flexible way to &lt;strong&gt;manage and inject resources into web views&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let's use them to fix the code, shall we?&lt;/p&gt;

&lt;p&gt;First, let's move Diego to a &lt;code&gt;providerconf.py&lt;/code&gt; script:&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="c1"&gt;# providerconf.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;chatterbot&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatBot&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;chatterbot.trainers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatterBotCorpusTrainer&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bocadillo&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;

&lt;span class="nd"&gt;@provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app&lt;/span&gt;&lt;span class="sh"&gt;"&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;diego&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;diego&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChatBot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Diego&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;trainer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChatterBotCorpusTrainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;diego&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;trainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;train&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chatterbot.corpus.english.greetings&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;chatterbot.corpus.english.conversations&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&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;diego&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above declares a &lt;code&gt;diego&lt;/code&gt; provider which we can now &lt;strong&gt;inject&lt;/strong&gt; into the WebSocket view. All we have to do is declare it as a &lt;strong&gt;parameter&lt;/strong&gt; to the view.&lt;/p&gt;

&lt;p&gt;Let's do just that by updating the &lt;code&gt;app.py&lt;/code&gt; script. Here, you get it in full:&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;bocadillo&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;App&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@app.websocket_route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/conversation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;converse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;diego&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# &amp;lt;-- 👋, Diego!
&lt;/span&gt;    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;diego&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&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;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No imports required — Diego will &lt;em&gt;automagically&lt;/em&gt; get injected in the WebSocket view when processing the WebSocket connection request. ✨&lt;/p&gt;

&lt;p&gt;Alright, ready to try things out?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run the &lt;code&gt;app.py&lt;/code&gt; script. You should see additional logs corresponding to Bocadillo setting up Diego on startup:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;python app.py
INFO: Started server process &lt;span class="o"&gt;[&lt;/span&gt;29843]
INFO: Waiting &lt;span class="k"&gt;for &lt;/span&gt;application startup.
&lt;span class="o"&gt;[&lt;/span&gt;nltk_data] Downloading package averaged_perceptron_tagger to
&lt;span class="o"&gt;[&lt;/span&gt;nltk_data]     /Users/Florimond/nltk_data...
&lt;span class="o"&gt;[&lt;/span&gt;nltk_data]   Package averaged_perceptron_tagger is already up-to-
&lt;span class="o"&gt;[&lt;/span&gt;nltk_data]       &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;nltk_data] Downloading package punkt to /Users/Florimond/nltk_data...
&lt;span class="o"&gt;[&lt;/span&gt;nltk_data]   Package punkt is already up-to-date!
&lt;span class="o"&gt;[&lt;/span&gt;nltk_data] Downloading package stopwords to
&lt;span class="o"&gt;[&lt;/span&gt;nltk_data]     /Users/Florimond/nltk_data...
&lt;span class="o"&gt;[&lt;/span&gt;nltk_data]   Package stopwords is already up-to-date!
Training greetings.yml: &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="c"&gt;####################] 100%&lt;/span&gt;
Training conversations.yml: &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="c"&gt;####################] 100%&lt;/span&gt;
INFO: Uvicorn running on http://127.0.0.1:8000 &lt;span class="o"&gt;(&lt;/span&gt;Press CTRL+C to quit&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Run the &lt;code&gt;client.py&lt;/code&gt; script, and start chatting! You shouldn't see any difference from before. In particular, Diego responds just as fast.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python client.py
&amp;gt; Hello!
Hi
&amp;gt; I would like to order a sandwich
Yes it is.
&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There you go! Beautiful, modular and flexible &lt;a href="https://en.wikipedia.org/wiki/Dependency_injection" rel="noopener noreferrer"&gt;dependency injection&lt;/a&gt; with Bocadillo providers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping track of clients
&lt;/h2&gt;

&lt;p&gt;Let's go one step further. True, we have quite elegantly implemented conversation with a chatbot over WebSocket. Now, how about we keep track of how many clients are currently talking to the chatbot?&lt;/p&gt;

&lt;p&gt;If you were wondering — yes, we can implement this with providers too!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Let's add a &lt;code&gt;clients&lt;/code&gt; provider to &lt;code&gt;providerconf.py&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# providerconf.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bocadillo&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;

&lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="nd"&gt;@provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app&lt;/span&gt;&lt;span class="sh"&gt;"&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;clients&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Now, let's add another provider which returns a context manager that takes care of registering the &lt;code&gt;ws&lt;/code&gt; connection to the set of clients. FYI, this is an example of a &lt;a href="https://bocadilloproject.github.io/guides/injection/factory.html" rel="noopener noreferrer"&gt;factory provider&lt;/a&gt;, but you don't really need to understand the whole code at this point.&lt;/li&gt;
&lt;/ul&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# providerconf.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;contextlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;contextmanager&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bocadillo&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;
&lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="nd"&gt;@provider&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;save_client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clients&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nd"&gt;@contextmanager&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;clients&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;
        &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;clients&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ws&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;_register&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;In the WebSocket view, use the new &lt;code&gt;save_client&lt;/code&gt; provider to register the WebSocket client:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app.py
&lt;/span&gt;
&lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="nd"&gt;@app.websocket_route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/conversation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;converse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;diego&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;save_client&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;save_client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;diego&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! While the client is chatting with Diego, it will be present in the set of &lt;code&gt;clients&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;How about we do something with this information?&lt;/p&gt;

&lt;h2&gt;
  
  
  Exposing client count via a REST endpoint
&lt;/h2&gt;

&lt;p&gt;As a final feature, let's step aside from WebSocket for a moment and go back to the good old HTTP protocol. We'll create a simple REST endpoint to view the number of currently connected clients.&lt;/p&gt;

&lt;p&gt;Go back to &lt;code&gt;app.py&lt;/code&gt; and add the following code:&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="c1"&gt;# app.py
&lt;/span&gt;
&lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/client-count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;client_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clients&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;media&lt;/span&gt; &lt;span class="o"&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;count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clients&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, if you've ever worked with Flask or Falcon, this code shouldn't come as a surprise. All we do here is send the number of &lt;code&gt;clients&lt;/code&gt; (obtained from the &lt;code&gt;clients&lt;/code&gt; provider) in a JSON response.&lt;/p&gt;

&lt;p&gt;Go ahead! Run &lt;code&gt;python app.py&lt;/code&gt; and run a few &lt;code&gt;python client.py&lt;/code&gt; instances. Check out how many clients are connected by opening &lt;a href="http://localhost:8000/client-count" rel="noopener noreferrer"&gt;http://localhost:8000/client-count&lt;/a&gt; in a web browser. Press &lt;code&gt;Ctrl+C&lt;/code&gt; for one of the clients, and see the client count go down!&lt;/p&gt;

&lt;p&gt;Did it work? Congrats! ✨&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;We're mostly done in terms of the features we wanted to cover together. I've got some ideas you can explore as exercises, of course, but before getting to that let's write some tests.&lt;/p&gt;

&lt;p&gt;One of Bocadillo's design principles is to make it easy to write high-quality applications. As such, Bocadillo has all the tools built-in to write tests for this chatbot server.&lt;/p&gt;

&lt;p&gt;You can write those with your favorite test framework. We'll choose &lt;a href="https://docs.pytest.org" rel="noopener noreferrer"&gt;pytest&lt;/a&gt; for the purpose of this tutorial. Let's install it first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pipenv &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--dev&lt;/span&gt; pytest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's setup our testing environment. We'll write a &lt;a href="https://docs.pytest.org/en/latest/fixture.html" rel="noopener noreferrer"&gt;pytest fixture&lt;/a&gt; that sets up a test client. The test client exposes a Requests-like API as well as helpers to test WebSocket endpoints. Besides, we don't actually need to test the chatbot here, so we'll override the &lt;code&gt;diego&lt;/code&gt; provider with an "echo" mock — this will have the nice side effect of greatly speeding up the tests.&lt;/p&gt;

&lt;p&gt;So, go ahead and create a &lt;code&gt;conftest.py&lt;/code&gt; script, and place the following in there:&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="c1"&gt;# conftest.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bocadillo&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bocadillo.testing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;create_client&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;

&lt;span class="nd"&gt;@provider&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;diego&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EchoDiego&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;get_response&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;query&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;query&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;EchoDiego&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;create_client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now is the time to write some tests! Create a &lt;code&gt;test_app.py&lt;/code&gt; file at the project root directory:&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="nb"&gt;touch &lt;/span&gt;test_app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, let's test that we can connect to the WebSocket endpoint, and that we get a response from Diego if we send a message:&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="c1"&gt;# test_app.py
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_connect_and_converse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;websocket_connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/conversation&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;ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;receive_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's test the incrementation of the client counter when clients connect to the WebSocket endpoint:&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="c1"&gt;# test_app.py
&lt;/span&gt;&lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_client_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/client-count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;websocket_connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/conversation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/client-count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/client-count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run these tests using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pytest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And, well, guess what?&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="o"&gt;====================&lt;/span&gt; &lt;span class="nb"&gt;test &lt;/span&gt;session starts &lt;span class="o"&gt;=====================&lt;/span&gt;
platform darwin &lt;span class="nt"&gt;--&lt;/span&gt; Python 3.7.2, pytest-4.3.1, py-1.8.0, pluggy-0.9.0
rootdir: ..., inifile: pytest.ini
collected 2 items

test_app.py ..                                         &lt;span class="o"&gt;[&lt;/span&gt;100%]

&lt;span class="o"&gt;==================&lt;/span&gt; 2 passed &lt;span class="k"&gt;in &lt;/span&gt;0.08 seconds &lt;span class="o"&gt;==================&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tests pass! ✅🎉&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;If you've made it so far — congratulations! You've just built a &lt;strong&gt;chatbot server&lt;/strong&gt; powered by WebSocket, &lt;a href="https://github.com/gunthercox/ChatterBot" rel="noopener noreferrer"&gt;ChatterBot&lt;/a&gt; and Bocadillo.&lt;/p&gt;

&lt;p&gt;In this article, we've seen how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Setup a Bocadillo project.&lt;/li&gt;
&lt;li&gt;Write a WebSocket endpoint.&lt;/li&gt;
&lt;li&gt;Write an HTTP endpoint.&lt;/li&gt;
&lt;li&gt;Use providers to decouple resources and their consumers.&lt;/li&gt;
&lt;li&gt;Test WebSocket and HTTP endpoints.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The complete code for this tutorial is available on the Bocadillo repo on GitHub: &lt;a href="https://github.com/bocadilloproject/bocadillo/blob/release/docs/docs/getting-started/tutorial" rel="noopener noreferrer"&gt;get the code!&lt;/a&gt; All in all, the server and &lt;code&gt;providerconf.py&lt;/code&gt; only add up to about 60 lines of code — pretty good bang for the buck!&lt;/p&gt;

&lt;p&gt;Obviously, we've only scratched the surface of what you can do with Bocadillo. The goal of this tutorial was to take you through the steps of building a &lt;em&gt;Minimum Meaningful Application&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;You can iterate upon this chatbot server we've built together very easily. I'd be interested to see what you come up with!&lt;/p&gt;

&lt;p&gt;Want to challenge yourself? Here are a few ideas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a home page rendered with &lt;a href="https://bocadilloproject.github.io/guides/agnostic/templates.html" rel="noopener noreferrer"&gt;templates&lt;/a&gt;. The web browser should connect to the chatbot server via a JavaScript program. You'll probably also need to serve &lt;a href="https://bocadilloproject.github.io/guides/http/static-files.html" rel="noopener noreferrer"&gt;static files&lt;/a&gt; to achieve this.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://chatterbot.readthedocs.io/en/stable/training.html" rel="noopener noreferrer"&gt;Train Diego&lt;/a&gt; to answers questions like "How many people are you talking to currently?"&lt;/li&gt;
&lt;li&gt;Currently, all clients talk to the same instance of Diego. Yet, it would be nice if each client had their own Diego to ensure a bespoke conversation. You may want to investigate &lt;a href="https://bocadilloproject.github.io/guides/agnostic/sessions.html" rel="noopener noreferrer"&gt;cookie-based sessions&lt;/a&gt; and &lt;a href="https://bocadilloproject.github.io/guides/injection/factory.html" rel="noopener noreferrer"&gt;factory providers&lt;/a&gt; to implement this behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope you enjoyed this tutorial! If you'd like to support the project, be sure to &lt;a href="https://github.com/bocadilloproject/bocadillo" rel="noopener noreferrer"&gt;star the repo&lt;/a&gt;. If you don't want to miss on new releases and announcements, feel free to follow &lt;a href="https://twitter.com/bocadillopy" rel="noopener noreferrer"&gt;@bocadillopy&lt;/a&gt; on Twitter!&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>webdev</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How I Built A Python Web Framework And Became An Open Source Maintainer</title>
      <dc:creator>Florimond Manca</dc:creator>
      <pubDate>Sun, 23 Dec 2018 19:21:42 +0000</pubDate>
      <link>https://forem.com/bocadillo/how-i-built-a-python-web-framework-and-became-an-open-source-maintainer-3okd</link>
      <guid>https://forem.com/bocadillo/how-i-built-a-python-web-framework-and-became-an-open-source-maintainer-3okd</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally published at &lt;a href="https://blog.florimond.dev/how-i-built-a-web-framework-and-became-an-open-source-maintainer"&gt;blog.florimond.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It's been a while since I've written a blog post. Nearly two months, actually. So, where have I been?&lt;/p&gt;

&lt;p&gt;For one, the final year of my engineering studies has been taking up more of my time than I thought it would. I'm not complaining, though — I've been learning a ton of interesting and useful things, both theoretical and practical. Plus, I want to take the opportunity to live this final year fully.&lt;/p&gt;

&lt;p&gt;The other side of the story is — I've been working on building &lt;a href="https://bocadilloproject.github.io"&gt;Bocadillo&lt;/a&gt;, an open source asynchronous Python web framework. The adventure, which started as a way for me to learn about the internals of a web framework, has been thrilling so far.&lt;/p&gt;

&lt;p&gt;This means that, because a day is only 24 hours, I had to put blogging aside for a bit. But now that I'm on holiday, I can finally take the time to reflect on what's been going on. 🥳&lt;/p&gt;

&lt;p&gt;In this blog post, I want to put on paper my thoughts about the whole process of building a web framework and especially &lt;strong&gt;launching an open source project&lt;/strong&gt;. I've already learnt a lot in the process, from programming techniques to project management and development tooling, so I wanted to share my experience with you!&lt;/p&gt;

&lt;p&gt;What's on the menu?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
What is Bocadillo?: introducing Bocadillo in order to set up some context.&lt;/li&gt;
&lt;li&gt;
The story behind it all: I'll tell you the story of how it became my first open source project!&lt;/li&gt;
&lt;li&gt;
Tips to start your own open source project: a series &lt;strong&gt;tips&lt;/strong&gt; I gathered from experience.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First things first — let me introduce you to Bocadillo! (This will be short and sweet, I promise.)&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Bocadillo?
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/bocadilloproject"&gt;
        bocadilloproject
      &lt;/a&gt; / &lt;a href="https://github.com/bocadilloproject/bocadillo"&gt;
        bocadillo
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      (UNMAINTAINED) Fast, scalable and real-time capable web APIs for everyone
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
NOTICE&lt;/h1&gt;
&lt;p&gt;Bocadillo is now &lt;strong&gt;UNMAINTAINED&lt;/strong&gt;. This repository should be archived soon. We recommend users to migrate to other well-supported alternatives, such as &lt;a href="https://www.starlette.io" rel="nofollow"&gt;Starlette&lt;/a&gt; or &lt;a href="https://fastapi.tiangolo.com" rel="nofollow"&gt;FastAPI&lt;/a&gt;. Please see &lt;a href="https://github.com/bocadilloproject/bocadillo/issues/334"&gt;#334&lt;/a&gt; for more information.&lt;/p&gt;

&lt;p&gt;
    &lt;a rel="noopener noreferrer" href="https://github.com/bocadilloproject/bocadillo/blob/master/.github/banner.png?raw=true"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tfGZG_Ly--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/bocadilloproject/bocadillo/raw/master/.github/banner.png%3Fraw%3Dtrue"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
    &lt;a href="https://travis-ci.org/bocadilloproject/bocadillo" rel="nofollow"&gt;
        &lt;img src="https://camo.githubusercontent.com/0b2a8c68d1d907db1a168e1d89570ba49949f2a97ff49ca3bd2a62f37f518436/68747470733a2f2f7472617669732d63692e6f72672f626f636164696c6c6f70726f6a6563742f626f636164696c6c6f2e7376673f6272616e63683d6d6173746572" alt="Build status"&gt;
    &lt;/a&gt;
    &lt;a href="https://codecov.io/gh/bocadilloproject/bocadillo" rel="nofollow"&gt;
        &lt;img src="https://camo.githubusercontent.com/5571f90fa49e0274fa6820b7dffcbea0f82a6f4f564682c2df7b4294a9941d21/68747470733a2f2f636f6465636f762e696f2f67682f626f636164696c6c6f70726f6a6563742f626f636164696c6c6f2f6272616e63682f6d61737465722f67726170682f62616467652e737667" alt="Test coverage"&gt;
    &lt;/a&gt;
    &lt;a href="https://pypi.org/project/bocadillo" rel="nofollow"&gt;
        &lt;img src="https://camo.githubusercontent.com/9bfedafd9e80d3ea900791ffcc98a240dba9ac6359cfd3ba57a05cdec2f542b8/68747470733a2f2f62616467652e667572792e696f2f70792f626f636164696c6c6f2e737667" alt="pypi version"&gt;
    &lt;/a&gt;
    &lt;a href="https://github.com/ambv/black"&gt;
        &lt;img src="https://camo.githubusercontent.com/aaa8def7ee55156a7bce1eb408897b26f9bad3977342abd9e6ad554af48e1ae5/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f636f64655f7374796c652d626c61636b2d3030303030302e737667" alt="code style"&gt;
    &lt;/a&gt;
&lt;/p&gt;




&lt;p&gt;Documentation: &lt;a href="https://bocadilloproject.github.io" rel="nofollow"&gt;https://bocadilloproject.github.io&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Bocadillo is a &lt;strong&gt;Python async web framework&lt;/strong&gt; that makes building performant and highly concurrent web APIs fun and accessible to everyone.&lt;/p&gt;

&lt;h2&gt;
Requirements&lt;/h2&gt;
&lt;p&gt;Python 3.6+&lt;/p&gt;
&lt;h2&gt;
Installation&lt;/h2&gt;
&lt;div class="highlight highlight-source-shell position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;pip install bocadillo&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
Example&lt;/h2&gt;
&lt;div class="highlight highlight-source-python position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s1"&gt;bocadillo&lt;/span&gt; &lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-v"&gt;App&lt;/span&gt;, &lt;span class="pl-s1"&gt;configure&lt;/span&gt;

&lt;span class="pl-s1"&gt;app&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-v"&gt;App&lt;/span&gt;()
&lt;span class="pl-en"&gt;configure&lt;/span&gt;(&lt;span class="pl-s1"&gt;app&lt;/span&gt;)

&lt;span class="pl-en"&gt;@&lt;span class="pl-s1"&gt;app&lt;/span&gt;.&lt;span class="pl-en"&gt;route&lt;/span&gt;(&lt;span class="pl-s"&gt;"/"&lt;/span&gt;)&lt;/span&gt;
&lt;span class="pl-k"&gt;async&lt;/span&gt; &lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;index&lt;/span&gt;(&lt;span class="pl-s1"&gt;req&lt;/span&gt;, &lt;span class="pl-s1"&gt;res&lt;/span&gt;):
    &lt;span class="pl-s1"&gt;res&lt;/span&gt;.&lt;span class="pl-s1"&gt;json&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; {&lt;span class="pl-s"&gt;"hello"&lt;/span&gt;: &lt;span class="pl-s"&gt;"world"&lt;/span&gt;}&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Save this as &lt;code&gt;app.py&lt;/code&gt;, then start a &lt;a href="https://www.uvicorn.org" rel="nofollow"&gt;uvicorn&lt;/a&gt; server (hot reload enabled!):&lt;/p&gt;
&lt;div class="highlight highlight-source-shell position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;uvicorn app:app --reload&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Say hello!&lt;/p&gt;
&lt;div class="highlight highlight-source-shell position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;$ curl http://localhost:8000
{&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;hello&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;world&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;}&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Ready to dive in? &lt;a href="https://bocadilloproject.github.io" rel="nofollow"&gt;Visit the documentation site&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
Changelog&lt;/h2&gt;
&lt;p&gt;All changes to Bocadillo are recorded in the…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/bocadilloproject/bocadillo"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;h3&gt;
  
  
  Elevator pitch
&lt;/h3&gt;

&lt;p&gt;Bocadillo is a &lt;strong&gt;modern Python web framework&lt;/strong&gt; that provides a sane toolkit for building performant web applications and services using &lt;strong&gt;asynchronous programming&lt;/strong&gt;. It is compatible with Python 3.6+ and MIT-licensed.&lt;/p&gt;

&lt;p&gt;Want to get started? &lt;a href="https://bocadilloproject.github.io"&gt;Read the documentation&lt;/a&gt;!&lt;/p&gt;
&lt;h3&gt;
  
  
  Key figures
&lt;/h3&gt;

&lt;p&gt;Bocadillo's initial commit was on November 3rd, 2018. As of December 21st, i.e. a month and a half later, here's where Bocadillo stands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;500+ commits and 31 stars on the &lt;a href="https://github.com/bocadilloproject/bocadillo"&gt;bocadilloproject/bocadillo&lt;/a&gt; repo — feel free to add yours!&lt;/li&gt;
&lt;li&gt;10 releases to &lt;a href="https://pypi.org/project/bocadillo"&gt;PyPI&lt;/a&gt; — latest being v0.7&lt;/li&gt;
&lt;li&gt;8k+ downloads as measured by &lt;a href="https://pepy.tech/project/bocadillo"&gt;PePy&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;About 30 pages of &lt;a href="https://bocadilloproject.github.io"&gt;docs&lt;/a&gt;!&lt;/li&gt;
&lt;li&gt;2 open source contributors — and many others welcome! 🥳&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These figures are of course very modest, but I'm already very happy with them. Of course, if you want to support Bocadillo, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/bocadilloproject/bocadillo"&gt;Star the repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/bocadilloproject/bocadillo/blob/master/CONTRIBUTING.md"&gt;Become a contributor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/bocadillopy"&gt;Follow Bocadillo on Twitter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, if you've already tried Bocadillo out, please get in touch via Twitter or the &lt;a href="https://gitter.im/bocadilloproject/bocadillo"&gt;Gitter&lt;/a&gt; chat room — I'd LOVE to hear from you!&lt;/p&gt;
&lt;h3&gt;
  
  
  Philosophy
&lt;/h3&gt;

&lt;p&gt;In terms of philosophy, Bocadillo is &lt;strong&gt;beginner-friendly&lt;/strong&gt;, but it aims at giving power-users the flexibility they need. It focuses on &lt;strong&gt;developer experience&lt;/strong&gt; while encouraging best practices.&lt;/p&gt;

&lt;p&gt;Besides, Bocadillo is not meant to be minimalist (but not a mastodon either). The idea is to include &lt;strong&gt;a carefully chosen set of included batteries&lt;/strong&gt;, with sensible defaults, so that you can &lt;strong&gt;solve common problems&lt;/strong&gt; and &lt;strong&gt;be productive right away&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Goals
&lt;/h3&gt;

&lt;p&gt;My goal is that people embrace the &lt;strong&gt;new possibilities of async Python&lt;/strong&gt; and that Bocadillo becomes &lt;strong&gt;a tool that helps people solve real-world problems more easily and efficiently&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There's still a lot of work ahead to get there, but the marathon has started!&lt;/p&gt;

&lt;p&gt;Speaking of &lt;em&gt;async&lt;/em&gt; Python, let me address a question you may have already asked yourself…&lt;/p&gt;
&lt;h3&gt;
  
  
  What with async?
&lt;/h3&gt;

&lt;p&gt;Generally, a web app instance spends a lot (if not most) of its request processing time waiting for I/O to complete — API calls, database queries, filesystem operations. etc. Most of these operations are &lt;em&gt;blocking&lt;/em&gt;, which typically limits performance if multiple clients are requesting the server.&lt;/p&gt;

&lt;p&gt;The idea with async frameworks like Bocadillo is to build &lt;strong&gt;apps that do not block on I/O operations&lt;/strong&gt;. To achieve this, we leverage &lt;strong&gt;asynchronous programming&lt;/strong&gt; and recent additions to the Python language such as &lt;a href="https://docs.python.org/3/library/asyncio.html"&gt;asyncio&lt;/a&gt; and &lt;a href="https://www.python.org/dev/peps/pep-0492/"&gt;async/await&lt;/a&gt; — available from Python 3.4+ and 3.6+ respectively. This allows us to consider processing a request as a task which is "scheduled" to run in a near future, i.e. when the CPU is avaiable.&lt;/p&gt;

&lt;p&gt;As a result, beyond making better use of the CPU, this architecture has a very interesting advantage — we can now &lt;strong&gt;handle multiple requests &lt;em&gt;concurrently&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;(Note: I didn't write &lt;em&gt;in parallel&lt;/em&gt;, as async still uses a single thread. &lt;a href="https://www.youtube.com/watch?v=cN_DpYBzKso"&gt;Concurrency is not parallelism&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;This property ultimately results in &lt;strong&gt;more stable throughput and performance&lt;/strong&gt; as the number of concurrent clients increases. From my own (yet to be published) benchmarks, Bocadillo keeps a steady processing rate whether it talks to 10 or 10,000 clients. On the other hand, "sync" frameworks like Flask or Django show a significant drop in reqs/sec in high concurrency settings.&lt;/p&gt;

&lt;p&gt;Eager for more on Python asynchronous programming? Here are a few talks I recommend, perhaps to be watched in this order:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=iG6fr81xHKA"&gt;Asynchronous Python for the Complete Beginner&lt;/a&gt;, Miguel Grinberg, Pycon 2017.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=m28fiN9y_r8&amp;amp;t=132s"&gt;Async/await in Python 3.5 and why it is awesome&lt;/a&gt;, Yuri Selivanov, EuroPython 2016.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=E-1Y4kSsAFc"&gt;Fear and Awaiting in Async: A Savage Journey to the Heart of the Coroutine Dream&lt;/a&gt;, David Beazley, PyOhio 2016.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;p&gt;Bocadillo is built on &lt;a href="https://www.uvicorn.org"&gt;Uvicorn&lt;/a&gt;, the lightning-fast ASGI web server, and &lt;a href="https://www.starlette.io"&gt;Starlette&lt;/a&gt;, a handy ASGI toolkit. Both were created by &lt;a href="https://github.com/tomchristie"&gt;Tom Christie&lt;/a&gt;, a core contributor to the Django REST Framework (among other things).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: &lt;a href="https://asgi.readthedocs.io"&gt;ASGI&lt;/a&gt; is the asynchronous equivalent to WSGI, i.e. a specification for how web servers should communicate with &lt;em&gt;asynchronous&lt;/em&gt; Python web applications.&lt;/p&gt;
&lt;h3&gt;
  
  
  Features
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What Bocadillo already has&lt;/strong&gt;: requests, responses, views (function-based and classed-based), routes and route parameters, media types, redirections, templates, static files, background tasks, CORS, HSTS, GZip, "recipes" (a.k.a. blueprints), middleware, hooks, and even a CLI. There are more in the release pipeline!&lt;/p&gt;

&lt;p&gt;One thing that Bocadillo does &lt;em&gt;not&lt;/em&gt; have (yet), though, is a &lt;strong&gt;database layer&lt;/strong&gt;. Most web apps or APIs I've built needed to persist data in some way, so I believe this (or at least an official recommendation for how to integrate an async database layer such as &lt;a href="https://tortoise-orm.readthedocs.io/en/latest/"&gt;Tortoise ORM&lt;/a&gt;) should land into the framework at some point.&lt;/p&gt;
&lt;h3&gt;
  
  
  Hello, world!
&lt;/h3&gt;

&lt;p&gt;Let's finish with the traditional "Hello, World" script!&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="c1"&gt;# api.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;bocadillo&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;API&lt;/span&gt;

&lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;API&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hello, World!"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"__main__"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  The story behind it all
&lt;/h2&gt;

&lt;p&gt;Alright, enough of pitching Bocadillo! Now that you know what it is, I want to share with you the story that led me to write this very blog post.&lt;/p&gt;

&lt;p&gt;How and why did it begin? What were some of the most meaningful events? Let's figure this out.&lt;/p&gt;

&lt;h3&gt;
  
  
  A learn-by-doing project
&lt;/h3&gt;

&lt;p&gt;Bocadillo started as &lt;strong&gt;a way for me to learn more about the internals of a web framework&lt;/strong&gt;. I wanted to get behind the scenes after nearly 2 years of using various Python and JS web frameworks. I wanted to know how it &lt;em&gt;actually&lt;/em&gt; all worked.&lt;/p&gt;

&lt;p&gt;To be clear, Bocadillo didn't start with a very detailed plan. Heck, I didn't even think about whether there was a need for an(other) Python async web framework. I wanted to &lt;strong&gt;learn&lt;/strong&gt; more than anything else.&lt;/p&gt;

&lt;h3&gt;
  
  
  Let's reinvent the wheel, and release it ASAP
&lt;/h3&gt;

&lt;p&gt;So there I was, on the 3rd of November, implementing features so common it felt like &lt;strong&gt;reinventing the wheel&lt;/strong&gt;. These were features like requests, responses, views, routes or the application server. "Hundreds of web frameworks out there already solved these problems before", I thought…&lt;/p&gt;

&lt;p&gt;But I didn't really care. As &lt;a class="mentioned-user" href="https://dev.to/funkybob"&gt;@funkybob&lt;/a&gt; kindly &lt;a href="https://twitter.com/BunkyFob/status/1059960013689516032"&gt;twitted to me&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Reinventing the wheel is an awesome way to learn… and sometimes what you learn is just how much your existing frameworks are doing for you.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A solid argument in favour of reinventing the wheel which made me realize how much Django is an absolute massive beast.&lt;/p&gt;

&lt;p&gt;Anyway, this initial endeavour led me to release &lt;a href="https://pypi.org/project/bocadillo/0.1.0/"&gt;v0.1&lt;/a&gt; on PyPI on the 4th of November. &lt;strong&gt;Just two days after the initial commit&lt;/strong&gt;, people could already &lt;code&gt;pip install bocadillo&lt;/code&gt; and build a minimal async web app. (Who said Python packaging was a pain? 🐍)&lt;/p&gt;

&lt;h3&gt;
  
  
  First signs of potential
&lt;/h3&gt;

&lt;p&gt;After v0.1 was released, I carried on with implementing more features such as new types of responses or error handling.&lt;/p&gt;

&lt;p&gt;On November 6th, v0.2.1 was out. That's when I began to realize that Bocadillo was a good candidate for my first &lt;strong&gt;full-blown open source project&lt;/strong&gt;. The idea seemed appealing to me, so I went for it!&lt;/p&gt;

&lt;p&gt;At that point, I hadn't disclosed anything about Bocadillo yet, not even to friends, so I wanted to make a first announcement. I chose to do it on Twitter.&lt;/p&gt;

&lt;p&gt;Because Bocadillo's initial code design and implementation took heavy inspiration from &lt;a href="https://python-responder.org"&gt;Responder&lt;/a&gt;, Kenneth Reitz's own async framework, &lt;a href="https://twitter.com/kennethreitz/status/1059942147342942209"&gt;I decided to give a shoutout&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--O8UCeKaR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://florimondmanca-personal-website.s3.amazonaws.com/media/markdownx/22e7446d-09a0-4508-b5a1-3c2c5a629a53.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--O8UCeKaR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://florimondmanca-personal-website.s3.amazonaws.com/media/markdownx/22e7446d-09a0-4508-b5a1-3c2c5a629a53.png" alt="The first announcement about Bocadillo on Twitter, and Kenneth Reitz's answer. ❣️" width="492" height="500"&gt;&lt;/a&gt;&lt;/p&gt;
The first announcement about Bocadillo on Twitter, and Kenneth Reitz's answer. ❣️



&lt;p&gt;Kenneth's answer and the forthcoming reactions after he retweeted the announcement made me think that &lt;strong&gt;Bocadillo actually had potential&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In just a day, the repo got 20 stars (a personal record already!) and, although it may look trivial, I thought it was really cool.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Psst: if you want to help get Bocadillo known, you can &lt;a href="https://github.com/bocadilloproject/bocadillo"&gt;star it&lt;/a&gt; too and spread the news!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So, after v0.2 was released, I felt motivated to keep working on Bocadillo, and add more features.&lt;/p&gt;

&lt;p&gt;Up until I realized something…&lt;/p&gt;

&lt;h3&gt;
  
  
  Where are the docs? Like, &lt;em&gt;real&lt;/em&gt; docs?
&lt;/h3&gt;

&lt;p&gt;It was clear for me: I wanted Bocadillo to be my first &lt;strong&gt;open source project&lt;/strong&gt;. I wanted to take it &lt;em&gt;seriously&lt;/em&gt; in order to learn as much as I can from the process.&lt;/p&gt;

&lt;p&gt;So, right from the start, I wrote an informative &lt;em&gt;README&lt;/em&gt;, curated a &lt;em&gt;CHANGELOG&lt;/em&gt; (with the help of &lt;a href="https://keepachangelog.com"&gt;keep a changelog&lt;/a&gt;) and added &lt;em&gt;CONTRIBUTING&lt;/em&gt; guidelines. As more and more releases went out between November 6th and November 18th, I updated the changelog and documented new features in the repo's README.&lt;/p&gt;

&lt;p&gt;Quickly though, this became impractical. The README was growing in size and it became hard to navigate, even with a table of contents.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AV9IUxSq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://florimondmanca-personal-website.s3.amazonaws.com/media/markdownx/f8afa87e-0fd0-432c-b11b-1ef77a12e2bb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AV9IUxSq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://florimondmanca-personal-website.s3.amazonaws.com/media/markdownx/f8afa87e-0fd0-432c-b11b-1ef77a12e2bb.png" alt='The table of contents for the repo on November 17th. See that "Usage" section growing to an astronomical size?' width="237" height="500"&gt;&lt;/a&gt;&lt;/p&gt;
The table of contents for the repo on November 17th. See that "Usage" section growing to an astronomical size?



&lt;p&gt;That's when I realized &lt;strong&gt;I needed proper documentation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you think about it, &lt;strong&gt;good documentation is a &lt;em&gt;sine qua non&lt;/em&gt; condition to having people use what you've built&lt;/strong&gt;. And that lengthy README was not good documentation considering the size that Bocadillo was heading at.&lt;/p&gt;

&lt;p&gt;Then, it hit me — a lot of large-scale open source tools, libraries or frameworks I use and love have a &lt;strong&gt;documentation site&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is what led me to release v0.5 on November 18th with a major addition: a brand new &lt;a href="https://bocadilloproject.github.io"&gt;docs site&lt;/a&gt;, which I built with &lt;a href="https://vuepress.vuejs.org"&gt;VuePress&lt;/a&gt; and hosted on &lt;a href="https://pages.github.com"&gt;GitHub Pages&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rND3xJPK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://florimondmanca-personal-website.s3.amazonaws.com/media/markdownx/140491ee-272e-43a7-86a9-c18721022f9f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rND3xJPK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://florimondmanca-personal-website.s3.amazonaws.com/media/markdownx/140491ee-272e-43a7-86a9-c18721022f9f.png" alt="Bocadillo's documentation site home page (end of November, 2018)." width="500" height="247"&gt;&lt;/a&gt;&lt;/p&gt;
Bocadillo's documentation site home page (end of November, 2018).



&lt;p&gt;On the necessity of good documentation — Joe Mancuso, creator of the &lt;a href="https://masoniteframework.gitbooks.io"&gt;Masonite&lt;/a&gt; framework, once shared with me this great piece of advice:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If it's not documented, it doesn't exist.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's why I'm taking documentation very seriously and strive to make it as good as I can — and so should you with &lt;em&gt;every&lt;/em&gt; project you're working on.&lt;/p&gt;

&lt;p&gt;Then, while building the docs site, I made what I now consider as a very important move for &lt;em&gt;any&lt;/em&gt; open source project…&lt;/p&gt;

&lt;h3&gt;
  
  
  Letting Bocadillo stand on its own feet
&lt;/h3&gt;

&lt;p&gt;Before releasing the docs, I moved Bocadillo from a personal repo to its own GitHub organization, namely &lt;a href="https://github.com/bocadilloproject"&gt;BocadilloProject&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The main motivation at the time was that I could use the organization's GitHub Pages domain &lt;code&gt;bocadilloproject.github.io&lt;/code&gt; for the documentation. It is definitely way cleaner and more accessible than &lt;code&gt;florimondmanca.github.io/bocadillo&lt;/code&gt;. 🙃&lt;/p&gt;

&lt;p&gt;However, this had the positive effect of giving Bocadillo &lt;strong&gt;an online space of its own&lt;/strong&gt;. It was not tied to my personal GitHub account anymore — the organisation was the new home for Bocadillo's source code.&lt;/p&gt;

&lt;p&gt;Later, as I realized that Bocadillo announcements was taking over my personal Twitter account, I created a dedicated Twitter account.&lt;/p&gt;

&lt;p&gt;The point is: &lt;strong&gt;it's important that an open source project lives outside of its creators&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now, back to the story — on November 18th, I had a docs site up and running that people could visit. What next?&lt;/p&gt;

&lt;h3&gt;
  
  
  Opening up the development process
&lt;/h3&gt;

&lt;p&gt;Up to November 20th, the way I kept track of the backlog and progress was via a private Trello board.&lt;/p&gt;

&lt;p&gt;This was very practical to me: I use Trello for a variety of things. But I realized that &lt;strong&gt;people visiting the repo had no visibility&lt;/strong&gt; on what was coming next or possible ways they could contribute.&lt;/p&gt;

&lt;p&gt;In fact, from a visitor's perspective, I think the repo looked like just any other personal project — no issues, no PRs, just a ton of commits from a single person — and &lt;em&gt;not&lt;/em&gt; a community-driven effort, i.e. what I'd like Bocadillo to become.&lt;/p&gt;

&lt;p&gt;So, per the advice of a close friend of mine and inspired by &lt;a href="https://medium.freecodecamp.org/how-i-went-from-being-a-contributor-to-an-open-source-project-maintainer-acd8a6b316f5"&gt;this article by Dhanraj Acharya&lt;/a&gt;, I decided to &lt;strong&gt;open up the development process&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I converted all my Trello cards to GitHub issues, and added meaningful labels (see &lt;a href="https://medium.com/@dave_lunny/sane-github-labels-c5d2e6004b63"&gt;Sane GitHub labels&lt;/a&gt; by Dave Lunny) and description to them. I think the repo now provides better visibility on the project and feels more encouraging for newcomers.&lt;/p&gt;

&lt;p&gt;I've learnt from this that &lt;strong&gt;open source is not only about opening the source code&lt;/strong&gt;. You've got to be open about the development process, too.&lt;/p&gt;

&lt;p&gt;I now hoped that, with the repo filled with issues and public PRs, it would attract its first open source contributors.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Spoiler alert: it did!&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Yay! First contributors!
&lt;/h3&gt;

&lt;p&gt;About at the same time, as Bocadillo grew in size, I began to feel the need for external advice. I was afraid that I might be taking bad design decisions or that the code could have been better. In short, &lt;strong&gt;I needed contributors&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Luckily, still on November 20th, I had the great pleasure of welcoming the first contribution to the repo, in the form of Alin Panaitiu commenting on &lt;a href="https://github.com/bocadilloproject/bocadillo/pull/3"&gt;PR #3&lt;/a&gt; for the new "hooks" feature.&lt;/p&gt;

&lt;p&gt;Alin helped me fix a few things about the feature which I was initially unsure about myself, and suggested ways in which it could be made even more useful. He went as far as forking the repo and sending me a diff showing off a fix.&lt;/p&gt;

&lt;p&gt;Later, on November 23rd, Alin got his first PR merged. Bocadillo had officially gained its first contributor! 🎉&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vQOlSCOg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://florimondmanca-personal-website.s3.amazonaws.com/media/markdownx/e338cd3d-ca39-4e06-b176-d5ab87a2fa22.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vQOlSCOg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://florimondmanca-personal-website.s3.amazonaws.com/media/markdownx/e338cd3d-ca39-4e06-b176-d5ab87a2fa22.png" alt="Screenshot of PR #18." width="500" height="257"&gt;&lt;/a&gt;&lt;/p&gt;
Screenshot of PR #18.



&lt;p&gt;As emotional as I can be, I was moved.&lt;/p&gt;

&lt;p&gt;Even more so that Alin actually sticked around. In the v0.7 release, Alin contributed 2 new features (GZip and ASGI middleware), with a code-to-merged time of probably less than a few hours. Thanks Alin, great stuff!&lt;/p&gt;

&lt;h3&gt;
  
  
  Entering maintenance mode
&lt;/h3&gt;

&lt;p&gt;From the end of November and onwards, the pace of the project slowed down a bit. On one hand, I was in a bit of a rush with school projects and exams being on the agenda, but that didn't explain everything. I was experiencing something else.&lt;/p&gt;

&lt;p&gt;You see, in the beginning, committing code to Bocadillo felt very easy. There was barely any legacy so it was exciting and I had tons of ideas. &lt;strong&gt;Everything remained to be done.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But as more features were added in, it started feeling heavier. Releases took longer to get out — now taking a week instead of days. Testing, refactoring and documentation became major parts of the development process. Plus, I now managed Bocadillo's online presence too.&lt;/p&gt;

&lt;p&gt;Don't get me wrong — I'm not complaining. In fact, I like that I got to go past the initial excitation and entered &lt;strong&gt;maintenance mode&lt;/strong&gt;. Besides, it's definitely a normal shift for a project that's trying to gain momentum and reach out to the community.&lt;/p&gt;

&lt;p&gt;I am now enjoying that working on Bocadillo doesn't have to take me nights like it used to in the beginning. Which leads me to the next point…&lt;/p&gt;

&lt;h3&gt;
  
  
  It's not a sprint, it's a marathon
&lt;/h3&gt;

&lt;p&gt;I recently noticed that my attitude towards the project has changed.&lt;/p&gt;

&lt;p&gt;Instead of rushing in to get features out as quickly as possible, and hoping that a burst of users would pop by, send stars en masse, love the framework and beg for more, I now felt more in peace with the idea that the project growth would be slow.&lt;/p&gt;

&lt;p&gt;This stems from the fact that &lt;strong&gt;maintaining an open source project is a marathon, not a sprint&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Put differently, &lt;strong&gt;success should always be a by-product, not a goal&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You'll have noticed that Bocadillo's goal statement doesn't mention fame, nor a threshold number of users. It only states that I hope Bocadillo can help &lt;em&gt;some&lt;/em&gt; people &lt;strong&gt;solve problems&lt;/strong&gt;. If that is the case for at least one person, I'll consider it a win. If it becomes the case for a lot of people, I shall only consider it as a side effect.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EF6-_9jk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.unsplash.com/photo-1455849318743-b2233052fcff%3Fixlib%3Drb-1.2.1%26auto%3Dformat%26fit%3Dcrop%26w%3D1350%26q%3D80" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EF6-_9jk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.unsplash.com/photo-1455849318743-b2233052fcff%3Fixlib%3Drb-1.2.1%26auto%3Dformat%26fit%3Dcrop%26w%3D1350%26q%3D80" alt="Two person standing on gray tile paving. @goian, unsplash.com" width="880" height="587"&gt;&lt;/a&gt;&lt;/p&gt;
Two person standing on gray tile paving. @goian, unsplash.com



&lt;p&gt;This is it for the story behind building Bocadillo! As you may have noted from these discussions, I have been loving the experience so far, and I'm actually now convinced that I made the right decision when I decided to go past the fear of judgement and build my own web framework. Which leads me to the last section of this blog post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tips to start your own open source project
&lt;/h2&gt;

&lt;p&gt;If there is one thing I want you to take away from reading this article, it's got to be this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Open source is an awesome way to learn. You should start your own project today!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;"Sounds great", you think, "but how should I proceed? Do you have any advice?"&lt;/p&gt;

&lt;p&gt;Well, I do have some. 😋&lt;/p&gt;

&lt;p&gt;First and foremost, &lt;strong&gt;learn&lt;/strong&gt; as much as you can and make sure to &lt;strong&gt;enjoy yourself&lt;/strong&gt;. Open source should never become a burden, and if it does, try to find ways in which you can start delegating or relying on the community to drive the project forward.&lt;/p&gt;

&lt;p&gt;For the rest, brace yourselves — categorized bullet points ahead!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: if you're unsure about how to implement any of the following items and/or want to see how I used or configured the tools in practice, feel free to check out &lt;a href="https://github.com/bocadilloproject"&gt;Bocadillo's repo&lt;/a&gt; and just copy the bits you're interested in — it's open source, after all!&lt;/p&gt;

&lt;h3&gt;
  
  
  Project definition
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Decide &lt;strong&gt;what&lt;/strong&gt; you want to build.&lt;/li&gt;
&lt;li&gt;Decide &lt;strong&gt;why&lt;/strong&gt; you want to build it — although it doesn't have to be deep or abstract, having a clear source of motivation helps.&lt;/li&gt;
&lt;li&gt;Think about &lt;strong&gt;scope&lt;/strong&gt; and &lt;strong&gt;design philosophy&lt;/strong&gt;: this will help make informed design decisions, and prevent feature creep.&lt;/li&gt;
&lt;li&gt;Decide &lt;strong&gt;who&lt;/strong&gt; you are targetting: are your users web developers, sys admins, project managers…?&lt;/li&gt;
&lt;li&gt;Explicitly define &lt;strong&gt;user skills expectations&lt;/strong&gt;: what should your users be familiar with, and how much?&lt;/li&gt;
&lt;li&gt;Decide &lt;strong&gt;how&lt;/strong&gt; you will distribute your project (e.g. a PyPI package).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Marketing &amp;amp; Communication
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Build an &lt;strong&gt;identity&lt;/strong&gt;: a name and a tagline at least. Make them short, catchy, and memorable. Showcase them everywhere (repo, PyPI page, docs site…).&lt;/li&gt;
&lt;li&gt;Create a &lt;strong&gt;visual identity&lt;/strong&gt;: this is your logo and graphic charter. It's better to have an okay temporary logo than no logo at all.&lt;/li&gt;
&lt;li&gt;Decide on an &lt;strong&gt;entry point&lt;/strong&gt;, i.e. a natural online location where people can look up your project. It can be the GitHub repo, a docs site, or whatever seems fit, but it should exist. (For Bocadillo, I believe this is the docs site.)&lt;/li&gt;
&lt;li&gt;Give your project &lt;strong&gt;a life of its own&lt;/strong&gt;: it's a good idea to create a separate GitHub organisation or social media account.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;social media&lt;/strong&gt; to communicate news, annoucements and tips, and start gathering people around the project (I use Twitter for this).&lt;/li&gt;
&lt;li&gt;Provide ways for people to see &lt;strong&gt;where you're heading at&lt;/strong&gt; — a roadmap, a list of issues, an "unreleased" section in the CHANGELOG, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Community
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Implement &lt;strong&gt;open source best practices&lt;/strong&gt;: a proper &lt;em&gt;README&lt;/em&gt;, contributing guidelines, a code of conduct, issue/PR templates, etc (use GitHub's checklist!). This will make the repo more welcoming to potential contributors, and show that you care about the community.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Be supportive and kind&lt;/strong&gt; to others. Thank them for their questions. Provide helpful resources.&lt;/li&gt;
&lt;li&gt;It might be a good idea to create a place for informal discussions. I recently decided to experiment with a &lt;a href="https://gitter.im/bocadilloproject/bocadillo"&gt;Gitter&lt;/a&gt; chat room.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Project management
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;GitHub issues&lt;/strong&gt; to list your TODOs. That way, when wondering what you should work on next, just pick up a ticket and work on it!&lt;/li&gt;
&lt;li&gt;Set up meaningful issue labels (see &lt;a href="https://medium.com/@dave_lunny/sane-github-labels-c5d2e6004b63"&gt;Sane GitHub Labels&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;You can also set up a &lt;strong&gt;GitHub project&lt;/strong&gt; to display your issues and PRs in a kanban board.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Code quality
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Set up a &lt;strong&gt;CI/CD pipeline&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Be unforgiving on &lt;strong&gt;tests&lt;/strong&gt; — besides ensuring your software works as it is intended to, they'll help you and everyone catch regressions and be confident when making changes (I use &lt;a href="https://pytest.org"&gt;pytest&lt;/a&gt; as a testing framework).&lt;/li&gt;
&lt;li&gt;Measure &lt;strong&gt;test coverage&lt;/strong&gt; (I use &lt;a href="https://pypi.org/project/pytest-cov/"&gt;pytest-cov&lt;/a&gt; for pytest/coverage.py integration, and &lt;a href="https://codecov.io"&gt;CodeCov&lt;/a&gt; for coverage reports).&lt;/li&gt;
&lt;li&gt;Enforce that &lt;strong&gt;PRs pass tests&lt;/strong&gt; before merging.&lt;/li&gt;
&lt;li&gt;Every PR should contain all 3 items: code, tests and docs.&lt;/li&gt;
&lt;li&gt;Use a &lt;strong&gt;code formatter&lt;/strong&gt; to reduce syntax/code style noise in code reviews (I use the opinionated &lt;a href="https://github.com/ambv/black"&gt;Black&lt;/a&gt; formatter along with a &lt;a href="https://pre-commit.com"&gt;pre-commit&lt;/a&gt; hook).&lt;/li&gt;
&lt;li&gt;If you don't have other reviewers, &lt;strong&gt;make PRs to yourself&lt;/strong&gt;, let them settle and come back later. It's easier to see if the code is a mess after a few days.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Documentation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Write a clear and informed &lt;strong&gt;README&lt;/strong&gt; with at least a project description, install instructions, a quick start example and a link to the docs or somewhere users can learn more.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://keepachangelog.com"&gt;Keep a changelog&lt;/a&gt;&lt;/strong&gt;, you'll thank yourself later.&lt;/li&gt;
&lt;li&gt;Build a &lt;strong&gt;docs site&lt;/strong&gt; if you're building more than a simple library (I use &lt;a href="https://vuepress.vuejs.org"&gt;VuePress&lt;/a&gt; as a static site generator).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structure your docs&lt;/strong&gt;: tutorials, discussions, how-to's, reference (tip: I use &lt;a href="https://github.com/NiklasRosenstein/pydoc-markdown"&gt;PydocMd&lt;/a&gt; to generate Markdown API reference straight from my Python docstrings).&lt;/li&gt;
&lt;li&gt;Remember: &lt;strong&gt;if it's not documented, it doesn't exist&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Add pretty &lt;strong&gt;badges&lt;/strong&gt; to your README (e.g. with &lt;a href="https://shields.io"&gt;shields.io&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Versioning and releasing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;semantic versioning&lt;/strong&gt; (see &lt;a href="https://semver.org"&gt;SemVer&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Automate &lt;strong&gt;version bumping&lt;/strong&gt; with tools such as &lt;a href="https://pypi.org/project/bumpversion/"&gt;bumpversion&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Automate the &lt;strong&gt;release pipeline&lt;/strong&gt;. I use &lt;a href="https://travis-ci.org"&gt;TravisCI&lt;/a&gt; to release tagged commits to PyPI.&lt;/li&gt;
&lt;li&gt;Set up &lt;strong&gt;special release branches&lt;/strong&gt;. For example, I use &lt;code&gt;release/docs&lt;/code&gt; for docs deployment and &lt;code&gt;release/test&lt;/code&gt; for releasing to Test PyPI.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Achievement unlocked?
&lt;/h2&gt;

&lt;p&gt;This blog post is now coming to an end, so let's wrap up!&lt;/p&gt;

&lt;p&gt;Although Bocadillo started as a way for me to learn more about the internals of a web framework, &lt;strong&gt;it has turned into a full-blown open source project&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;With all the effort that already went into building the framework, documenting it, configuring the repo and managing releases, &lt;strong&gt;I now start to think of myself as an open source maintainer&lt;/strong&gt; — which I think is a very enriching experience!&lt;/p&gt;

&lt;p&gt;Even though I do take pride for what I have achieved so far, &lt;strong&gt;all of this is also very humbling&lt;/strong&gt;. I now realize how challenging managing an open source project can be, let alone building a community around it. &lt;strong&gt;It's tough!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That said, I do believe that you should go for it and &lt;strong&gt;start your own open source project&lt;/strong&gt;. It could be a simple tool or library, or an entire application framework — either way, you'll &lt;strong&gt;learn a lot&lt;/strong&gt; in the process.&lt;/p&gt;

&lt;p&gt;Of course, feel free to check out the &lt;a href="https://github.com/bocadilloproject"&gt;Bocadillo repo&lt;/a&gt; if you seek ideas on structuring an open source project or setting up tooling. There are also tons of great resources on &lt;a href="https://opensource.guide"&gt;opensource.guide&lt;/a&gt;, so check this website out, too!&lt;/p&gt;

&lt;p&gt;Thanks for reading through this article! As always, feedback is much appreciated. In particular, I'd love to hear &lt;strong&gt;your own stories on maintaining open source projects&lt;/strong&gt;. Plus, if by any chance this article has inspired you, be sure to drop a comment! ❣️&lt;/p&gt;

&lt;p&gt;Best wishes for this holiday season to you all. ✌️&lt;/p&gt;

&lt;h2&gt;
  
  
  Stay in touch!
&lt;/h2&gt;

&lt;p&gt;If you enjoyed this post, you can &lt;a href="https://twitter.com/FlorimondManca?ref_src=twsrc%5Etfw"&gt;find me on Twitter&lt;/a&gt; for updates, announcements and news. 🐤&lt;/p&gt;

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