<?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: Alessandro Cuppari</title>
    <description>The latest articles on Forem by Alessandro Cuppari (@alessandrojcm).</description>
    <link>https://forem.com/alessandrojcm</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%2Fuser%2Fprofile_image%2F16963%2F01680496-fb32-4fa5-9102-935d84ea8d63.jpg</url>
      <title>Forem: Alessandro Cuppari</title>
      <link>https://forem.com/alessandrojcm</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/alessandrojcm"/>
    <language>en</language>
    <item>
      <title>Getting your user's country and name in a Whatsapp bot using Fastapi and FaunaDB - Part 3</title>
      <dc:creator>Alessandro Cuppari</dc:creator>
      <pubDate>Sat, 23 May 2020 22:22:40 +0000</pubDate>
      <link>https://forem.com/alessandrojcm/getting-your-user-s-country-and-name-in-a-whatsapp-bot-using-fastapi-and-faunadb-part-3-1o6e</link>
      <guid>https://forem.com/alessandrojcm/getting-your-user-s-country-and-name-in-a-whatsapp-bot-using-fastapi-and-faunadb-part-3-1o6e</guid>
      <description>&lt;p&gt;We're back! So a bit recap of the last part: we now have our Fauna database set up, as well as our API with our greeting endpoint and the functionality to search for a user via their phone number. But before we continue, let me tell you I lied to you: I told you we would fetch the user's country from the phone number, but we did not. So let's do that right away!&lt;/p&gt;

&lt;h1&gt;
  
  
  Getting a country from a phone number
&lt;/h1&gt;

&lt;p&gt;For this, we'll use the great &lt;code&gt;phonenumbers&lt;/code&gt; library, which is the Python fork of Google's &lt;code&gt;libphonenumber&lt;/code&gt;. Install it like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;phonenumbers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now, let's go back to our &lt;code&gt;greet.py&lt;/code&gt; file:&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="nn"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;APIRouter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Form&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;phonenumbers&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;phonenumbers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;geocoder&lt;/span&gt; &lt;span class="c1"&gt;# For geographical data
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;src.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;

&lt;span class="n"&gt;user_greeting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;APIRouter&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;phone_to_country&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;parsed_number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;phonenumbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;phonenumbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_possible_number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parsed_number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Invalid phone number"&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;geocoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;country_name_for_number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parsed_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;user_greeting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/greeting"&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;greet_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UserIdentifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;(...)):&lt;/span&gt;
    &lt;span class="s"&gt;"""
    User greet endpoint
    :param: UserIdentifier: user's phone number from Twilio
    """&lt;/span&gt;

    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_by_phone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phone_number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;UserIdentifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Greeting the user since already exists
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"actions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"remember"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"say"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Hi there {name}! Welcome back, how are things in {c}?"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;)},&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;phone_to_country&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UserIdentifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"actions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="s"&gt;"say"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Hello there! Looks like you are writing from {c}"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"redirect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"task://can-have-name"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;See that, besides importing &lt;code&gt;phonenumbers&lt;/code&gt; we also import &lt;code&gt;geocoder&lt;/code&gt;, this adds the ability to obtain the country (among other geographical information) from a phone number; it is a separated import because it adds a large amount of metadata.&lt;/p&gt;

&lt;p&gt;Next,  we write a little helper function to parse a phone number and throw a &lt;code&gt;Bad Request&lt;/code&gt; error in case the number it's not valid. This would only happen if this endpoint it's hit outside of Twilio, but let's add it anyway. Then, it's just a matter of using that helper function in case the user its not in the database.&lt;/p&gt;

&lt;p&gt;Cool, with that out of the way, let's do the rest of the API.&lt;/p&gt;
&lt;h1&gt;
  
  
  Saving users to FaunaDB
&lt;/h1&gt;

&lt;p&gt;Ok, so the first thing we need its a method to store a user in Fauna. Lets quickly add one to our  &lt;code&gt;User&lt;/code&gt; class:&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="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;faunadb&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;faunadb.objects&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Ref&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;src.core&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;_collection_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'users'&lt;/span&gt;
  &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Ref&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&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="n"&gt;phone_number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
  &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;


  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;arbitrary_types_allowed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;data&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;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="s"&gt;"""
    Saves the document in the collection,
    the attributes are serialized utilizing Pydantic's dict method, which
    traverses trough the child class attributes
    :return: An instance of the newly saved object.
    """&lt;/span&gt;
    &lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&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="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_collection_name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;attributes&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="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__class__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"ref"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
 &lt;span class="c1"&gt;# The code from the previous post
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Thanks to Pydantic, this is pretty straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First, I added an extra attribute (&lt;code&gt;_collection_name&lt;/code&gt;) to get the collection's name; it matches the name we set on the Fauna dashboard&lt;/li&gt;
&lt;li&gt;We get the data we want to save as a &lt;code&gt;dict&lt;/code&gt;, we use the &lt;code&gt;BaseModel&lt;/code&gt;'s &lt;code&gt;dict&lt;/code&gt; instance method; which does exactly this.&lt;/li&gt;
&lt;li&gt;Next is the saving process itself:

&lt;ul&gt;
&lt;li&gt;We then use the &lt;code&gt;session&lt;/code&gt; object to create a query&lt;/li&gt;
&lt;li&gt;We pass a &lt;code&gt;collection&lt;/code&gt; object using the collection name attribute&lt;/li&gt;
&lt;li&gt;Finally, we pass the attributes &lt;code&gt;dict&lt;/code&gt; as a second argument. Remember we said that non-standard Fauna objects need to be inside a &lt;code&gt;data&lt;/code&gt; dict? Here we do exactly that.&lt;/li&gt;
&lt;li&gt;Finally, we create a new &lt;code&gt;User&lt;/code&gt; object using the class's &lt;code&gt;__class__&lt;/code&gt; magic method; which it's basically like calling the constructor. The &lt;code&gt;result&lt;/code&gt; object is nothing more than the object returned by Fauna's saving operation. I know that this may not be the best approach (perhaps we should just set the new attributes instead of returning a new instance), but hey that's what I did&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Parsing &lt;code&gt;collect&lt;/code&gt; actions
&lt;/h1&gt;

&lt;p&gt;Now, let's finally go to the reason for this entire series: asking the user for their name.&lt;/p&gt;
&lt;h3&gt;
  
  
  Asking the user if they want to give us their name
&lt;/h3&gt;

&lt;p&gt;Remember the first post, when we defined our schema? We said that our endpoint's route would be &lt;code&gt;can-have-name&lt;/code&gt;, the task's name would be the same, the action's name would be &lt;code&gt;ask-for-name&lt;/code&gt; and the answer from the action would be &lt;code&gt;can_have_name&lt;/code&gt;. Recall that action and tasks are two different things: a task is a series of one or more actions that a bot performs, while the action its the specific thing the bot will do in that task; in this case, asking a question.&lt;/p&gt;

&lt;p&gt;Now, the 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="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;span class="c1"&gt;# The other imports
&lt;/span&gt;
&lt;span class="c1"&gt;# The code we added at the beginning
&lt;/span&gt;
&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;user_greeting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/can-have-name"&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;can_have_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;(...)):&lt;/span&gt;
    &lt;span class="s"&gt;"""
    Asks the user if he/she wants to give us their name
    :param: Memory: JSON Stringified object from Twilio
    """&lt;/span&gt;
    &lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"twilio"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"collected_data"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"ask-for-name"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"answers"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;
        &lt;span class="s"&gt;"can_have_name"&lt;/span&gt;
    &lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"answer"&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;answer&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"Yes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"actions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="s"&gt;"redirect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"task://store-user"&lt;/span&gt;&lt;span class="p"&gt;}]}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"actions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="s"&gt;"say"&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="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Recall that our &lt;code&gt;collect&lt;/code&gt; question had a type of &lt;code&gt;Twilio.YES_NO&lt;/code&gt;, that means that Autopilot's natural language processing engine has done it's magic to convert whatever the user wrote into a &lt;code&gt;Yes&lt;/code&gt; or &lt;code&gt;No&lt;/code&gt; answer (those two are literal). So we just need to parse that response. &lt;/p&gt;

&lt;p&gt;Here, you need to take into account the thing that had me hitting the keyboard with my head a couple of days: Autopilot sends all requests with an encoding of &lt;code&gt;application/x-www- form-urlencoded&lt;/code&gt; and the &lt;code&gt;Memory&lt;/code&gt; object it's in JSON, but, because of the encoding, &lt;strong&gt;this JSON is not parsed&lt;/strong&gt;; it is just a plain string. Because of that, we set the &lt;code&gt;Memory&lt;/code&gt; parameter to be of type &lt;code&gt;str&lt;/code&gt; and we then use Python's standard library &lt;code&gt;json.loads&lt;/code&gt; to parse the &lt;code&gt;Memory&lt;/code&gt; string into a  &lt;code&gt;dict&lt;/code&gt;. Now, what's that intricate dictionary right there? Well, the &lt;code&gt;Memory&lt;/code&gt; object contains other things besides parsed responses, the parsed responses are saved under &lt;code&gt;collected_data&lt;/code&gt;. After that key: the format is &lt;code&gt;action_name.answers.question_name.answer&lt;/code&gt;, as we said above, our action's name is &lt;code&gt;ask-for-name&lt;/code&gt; and the question is &lt;code&gt;can_have_name&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;So after that confusing prop drilling, we have our answer. If the user said yes we just redirect to the task tasked (no pun intended) to get the name; if not well we just can do whatever we want. In this case let's just return an emoji, why? Because we are using Whatsapp and we can!&lt;/p&gt;
&lt;h2&gt;
  
  
  Finally getting the name
&lt;/h2&gt;

&lt;p&gt;For the last endpoint, let's get the user's name. Recall that we &lt;br&gt;
wrote in our schema that the endpoint's route is &lt;code&gt;store-user&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="c1"&gt;# Rest of greet.py up here
&lt;/span&gt;
&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;user_greeting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/store-user"&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;store_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UserIdentifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;(...),&lt;/span&gt; &lt;span class="n"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;(...)):&lt;/span&gt;
    &lt;span class="s"&gt;"""
    Stores a user in the database, fields stored are: country, name, and phone number
    :param: UserIdentifier: Phone number from Twilio
    :param: Memory: JSON Stringified object from Twilio
    """&lt;/span&gt;
    &lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"twilio"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"collected_data"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"collect-name"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"answers"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;
        &lt;span class="s"&gt;"first_name"&lt;/span&gt;
    &lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"answer"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;phone_to_country&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UserIdentifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# This needs error handling right here ;)
&lt;/span&gt;    &lt;span class="n"&gt;new_user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;capitalize&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;phone_number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;UserIdentifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"actions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"remember"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;new_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;new_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="s"&gt;"say"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Hi, {name} from {c}!"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;new_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="s"&gt;"say"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"This is a WIP ok bye"&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Besides the &lt;code&gt;Memory&lt;/code&gt; object, we also need the &lt;code&gt;UserIdentifier&lt;/code&gt;; see how FastAPI allows us to pass as a parameter just the fields we need from the request.&lt;/p&gt;

&lt;p&gt;The next couple of lines are the same as with the last endpoint, the only thing that changes is the keys for the memory &lt;code&gt;dict&lt;/code&gt;: in this case, the name of the action is &lt;code&gt;collect-name&lt;/code&gt; and the name of the question is &lt;code&gt;first_name&lt;/code&gt;. If you check our schema, you'll realize that the type of question is &lt;code&gt;Twilio.FIRST_NAME&lt;/code&gt;; was does this mean? You guessed it! Autopilot's natural language processing engine will try to parse the answer as a first name.&lt;/p&gt;

&lt;p&gt;Now, the saving process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First, we parse the country just like we did in the &lt;code&gt;/greeting&lt;/code&gt; endpoint&lt;/li&gt;
&lt;li&gt;Then, we pass  instantiate a &lt;code&gt;User&lt;/code&gt; object with the parameters we have: name (we sanitize the input a bit via capitalizing the string), country, and the phone; we also call the &lt;code&gt;save&lt;/code&gt; method right away to have a full instance&lt;/li&gt;
&lt;li&gt;Then, like in the first endpoint, we tell the bot to remember the name and the country and return whatever action we want&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Testing all out
&lt;/h1&gt;

&lt;p&gt;So now comes the part we've waited for: testing this bad boy out. For that, we first need to deploy our API to get an endpoint to put in our schema. There are several ways to do this, for example, you could deploy this on Heroku, Digital Ocean, or just write the code directly on Glitch to get a live URL (which is what I did for this series); whatever you choose is up to you! Just remember to set the &lt;code&gt;DEBUG&lt;/code&gt; flag to false and to fill the &lt;code&gt;FAUNA_SERVER_KEY&lt;/code&gt; env variable with a &lt;a href="https://dev-to-uploads.s3.amazonaws.com/i/x5d9qr3jhavu9p0eduz5.png"&gt;server key&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After the API is live, let's go to the schema file and replace all the occurrences of &lt;code&gt;our_super_yet_to_be_endpoint&lt;/code&gt; with the live URL of our API. Now is the time to create the bot, go create a Twilio account if you already haven't; after that, copy the SID and the auth token you'll find in the dashboard.&lt;/p&gt;

&lt;p&gt;There are two ways of creating an Autopilot bot: the first one is through the console and the second one is using the CLI. Since we already have the schema for the bot ready, let's use the CLI; you need either &lt;code&gt;yarn&lt;/code&gt; or &lt;code&gt;npm&lt;/code&gt; for that, I'll use the former:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn global add twilio-cli
twilio plugins:install @dabblelab/plugin-autopilot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;After that's installed, to a &lt;code&gt;twilio login&lt;/code&gt; in your terminal and paste the SID and auth key when prompted.&lt;/p&gt;

&lt;p&gt;Now, set the terminal on the path where the finished schema is stored and do:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;twilio autopilot:create &lt;span class="nt"&gt;-s&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./schema.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That will create a bot named &lt;code&gt;almighty-bot&lt;/code&gt; with the account you used to log in. Now, for the final part, we need to request a phone number from Twilio and enable WhatsApp integration. The process for doing that is out of the scope of this post, so check &lt;a href="%5Bhttps://www.twilio.com/docs/autopilot/channels/whatsapp"&gt;here&lt;/a&gt; for enabling WhatsApp and connecting that number to our bot.&lt;/p&gt;
&lt;h1&gt;
  
  
  Texting the bot
&lt;/h1&gt;

&lt;p&gt;Now, let's add our bot to our contact list a send a message!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--d5OeLhMs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/79evuvp9l05j8o40s2ht.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--d5OeLhMs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/79evuvp9l05j8o40s2ht.jpg" alt="Greeting"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can greet the bot again:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_X41AuJ7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/fbv61cugrvnclfoo0l5z.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_X41AuJ7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/fbv61cugrvnclfoo0l5z.jpg" alt="Greeting again"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, let's go to the Fauna dashboard and erase our user; just to say no and see what happens:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--r2mOpk3G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8bbgiq9ej0q3b8f51ctt.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--r2mOpk3G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8bbgiq9ej0q3b8f51ctt.jpg" alt="Saying no"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Great! We see that our bot reacts correctly to "yes" and "no".&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"But wait a minute"&lt;/em&gt;, I hear you say &lt;em&gt;"Didn't you tell me that a session lasts for 24 hours? Why did the conversation reset the second time I greeted the bot&lt;/em&gt;". Well, that was a half-truth actually: a session lasts for 24 hours &lt;strong&gt;if we include a &lt;code&gt;listen&lt;/code&gt; action at the end or if we redirect to another action&lt;/strong&gt;. If you check the schema and the actions we returned from our endpoints, you'll realize that we did not return that action: we just drove the users to a pre-defined path, so to speak. So, if the returned action neither a redirection nor a &lt;code&gt;listen&lt;/code&gt; action, the session will finish.&lt;/p&gt;
&lt;h1&gt;
  
  
  Are we done?
&lt;/h1&gt;

&lt;p&gt;Of course not! You probably already thought about this: this does not seem too secure, does it? How do I know that the requests really come from Twilio? If you did, you're right, this is completely insecure: everyone could figure our API out and make requests to it. So let's change that.&lt;/p&gt;

&lt;p&gt;Twilio has a package for Python that has, among other things, a class to validate that incoming requests come from Twilio; let's install it:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;twilio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now, how do we implement this for every request? Well, if you are like me and already have worked with Python in the past, you probably are thinking of a decorator. And, if you're like me, you went directly to implement said decorator just to have it fail miserably when deployed live.&lt;/p&gt;

&lt;p&gt;You see, Starlette (and FastAPI, by extension), don't quite work like this. Like I've mentioned a million times before, they are &lt;strong&gt;asynchronous&lt;/strong&gt;; thus, various properties of the request object (like the body or the form) are implemented as asynchronous streams: if you consume them in, say, a decorator, they will complete and will not be available to the next handler in the chain. So, in mortal words: accessing the async parts of the request in any other part that it's not its final destination will make that data unavailable in that final destination; therefore an error will occur.&lt;/p&gt;

&lt;p&gt;So, if not a decorator, how do we implement this? Well, we need to make a custom router. What's this? A router is a class whose methods get called before the request (or the response) hits the handler function. Because of this, we have, in a router, complete access to the request (or the response). Let's write the 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="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Request&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="n"&gt;HTTPException&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;fastapi.routing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;APIRoute&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;starlette.datastructures&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FormData&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;twilio.request_validator&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RequestValidator&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;src.core&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AutopilotRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="s"&gt;"""
    This class serves two purposes. First one, know that Starlette (framework on which Fastapi is built upon)
    is an ASGI framework. That means that parts of the request (like the body) are async. So, if we await those streams in middleware they will be consumed and will not be available to the final route.
    For that, this class consumes the steam (in this case the form) does what it needs to do with the data,
    and creates a new FormData object to pass to the final route.
    """&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;form&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__handle_non_dev_env&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;form&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;__handle_non_dev_env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s"&gt;"""
        In production or staging, validate that the request comes from Twilio
        """&lt;/span&gt;
        &lt;span class="n"&gt;validator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RequestValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TWILIO_AUTH_TOKEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;x_twilio_signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-Twilio-Signature"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"no-header"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;is_valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;validator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;validate&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="nb"&gt;super&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="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x_twilio_signature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;is_valid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;403&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;FormData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&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;AutopilotRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;APIRoute&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="s"&gt;"""
    The custom route to route requests through our AutopilotRequest object
    """&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_route_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;original_route_handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;get_route_handler&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;custom_route_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;new_request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AutopilotRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;original_route_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_request&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;custom_route_handler&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Ok, what's going on here? First, for a custom router, we need two things: the custom router and the custom request object. For this, we subclass &lt;code&gt;ApiRoute&lt;/code&gt; and &lt;code&gt;Request&lt;/code&gt; (from FastApi) respectively.&lt;/p&gt;

&lt;p&gt;Now, for the request:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We are only interested in the &lt;code&gt;form&lt;/code&gt; property since that is where Twilio data is&lt;/li&gt;
&lt;li&gt;We check if we're on debug mode if we are then we just return the original form&lt;/li&gt;
&lt;li&gt;Now, in the &lt;code&gt;__handle_non_dev_env&lt;/code&gt; is where the magic happens

&lt;ul&gt;
&lt;li&gt;We create a &lt;code&gt;RequestValidator&lt;/code&gt; object with the &lt;code&gt;TWILIO_AUTH_TOKEN&lt;/code&gt; (remember that?) property that is already in our configuration object&lt;/li&gt;
&lt;li&gt;We consume the &lt;code&gt;form&lt;/code&gt; stream, see that wee need to &lt;code&gt;await&lt;/code&gt; it. We need to do this because it contains data the validator needs&lt;/li&gt;
&lt;li&gt;Then, we extract the header from the request. The signature is in the &lt;code&gt;X-Twilio-Signature&lt;/code&gt; header&lt;/li&gt;
&lt;li&gt;Now, we use the &lt;code&gt;RequestValidator&lt;/code&gt;'s &lt;code&gt;validate&lt;/code&gt; method to perform the validation&lt;/li&gt;
&lt;li&gt;Next, we need to check if that method returned &lt;code&gt;True&lt;/code&gt;, if not we raise a &lt;code&gt;403 Forbidden&lt;/code&gt; error&lt;/li&gt;
&lt;li&gt;Finally, if the request did indeed come for Twilio, then we need to construct a new form since the old one was consumed; for this we just do: &lt;code&gt;FormData(dict(params.items()))&lt;/code&gt; which will return a new &lt;code&gt;FormData&lt;/code&gt; with the original data from the request&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, the router is simpler: it just needs to convert the incoming request to an &lt;code&gt;AutopilotRequest&lt;/code&gt; and pass it to the original handler. That way, when the handler calls the &lt;code&gt;form&lt;/code&gt; method under the hood, the validation will occur.&lt;/p&gt;

&lt;p&gt;How do we use this? Easily, let's go to our &lt;code&gt;greet.py&lt;/code&gt; file:&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;# routes/greet.py
&lt;/span&gt;
&lt;span class="c1"&gt;# Original imports
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.custom_router&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AutopilotRoute&lt;/span&gt;

&lt;span class="n"&gt;user_greeting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;APIRouter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;user_greeting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AutopilotRoute&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;- here
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's it, now just remember to fetch your Twilio auth token and set the &lt;code&gt;TWILIO_AUTH_TOKEN&lt;/code&gt; with it.&lt;/p&gt;
&lt;h1&gt;
  
  
  Bonus
&lt;/h1&gt;

&lt;p&gt;Now you're thinking: &lt;em&gt;"Wait a minute, do I need to use my phone to debug this? Doesn't this like, cost money?"&lt;/em&gt; Yes! You're also right, texting a number provided by Twilio costs money. For Autopilot, they provide a simulator; which is great for debugging. But, unfortunately, the simulator does not send the &lt;code&gt;UserIdentifier&lt;/code&gt; property; since is not a phone.&lt;/p&gt;

&lt;p&gt;But fear not! I have you covered as well. Remember that &lt;code&gt;FAKE_NUMBER&lt;/code&gt; property? Let's use that now, back our &lt;code&gt;AutopilotRoute&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="c1"&gt;# The imports
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AutopilotRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# The huge docstring
&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;form&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__handle_non_dev_env&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__handle_dev_env&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;__handle_dev_env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s"&gt;"""
        Here we just inject a fake number for testing, this so we can test from
        the Twilio Autopilot Simulator through an SSH tunnel.
        """&lt;/span&gt;
        &lt;span class="n"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;new_form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FormData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="n"&gt;UserIdentifier&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FAKE_NUMBER&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;new_form&lt;/span&gt;

    &lt;span class="c1"&gt;# __handle_non_dev_env goes here
# Our router goes here
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now, we have added a &lt;code&gt;__handle_dev_env&lt;/code&gt; method that gets executed when &lt;code&gt;DEBUG&lt;/code&gt;is set to &lt;code&gt;True&lt;/code&gt;. And whats does it do? Well, it takes whatever the request was and injects the &lt;code&gt;UserIdentifier&lt;/code&gt; property as whatever number we specified in &lt;code&gt;FAKE_NUMBER&lt;/code&gt;. Now behold!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aAHcDq2r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gc1gxiqdj09yajnyyjhh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aAHcDq2r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gc1gxiqdj09yajnyyjhh.png" alt="Simulator"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, for debugging, just set &lt;code&gt;DEBUG&lt;/code&gt; to &lt;code&gt;True&lt;/code&gt; and set &lt;code&gt;FAKE_NUMBER&lt;/code&gt; to a valid phone number. Pro-tip: you can use this with something like Ngrok to debug your API locally.&lt;/p&gt;
&lt;h1&gt;
  
  
  Wrapping up
&lt;/h1&gt;

&lt;p&gt;That's all folks! I hope you liked the series. I certainly did learn a lot while doing this. Before finishing up let me state that this is all very basic, it could be improved in so many ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Needs error handling&lt;/li&gt;
&lt;li&gt;While the Twilio default types do a good job in recognizing common language, the bot still needs to be trained using samples from real conversations&lt;/li&gt;
&lt;li&gt;Needs better automation for deployment&lt;/li&gt;
&lt;li&gt;So much more!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to check out how I solved this things (and also check a slightly more complex bot) you can check out the original &lt;a href="https://github.com/alessandrojcm/covid19-helper-bot"&gt;bot&lt;/a&gt; I was developing before writing this post.&lt;/p&gt;

&lt;p&gt;PS: Oh, also, here's the code for this post, did you think I would forget? ;)&lt;/p&gt;


&lt;div class="glitch-embed-wrap"&gt;
  &lt;iframe src="https://glitch.com/embed/#!/embed/twilio-autopilot-whatsapp?previewSize=0&amp;amp;path=src%2Fapp.py" alt="twilio-autopilot-whatsapp on glitch"&gt;&lt;/iframe&gt;
&lt;/div&gt;



</description>
      <category>python</category>
      <category>showdev</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Getting your user's country and name in a Whatsapp bot using Fastapi and FaunaDB - Part 2</title>
      <dc:creator>Alessandro Cuppari</dc:creator>
      <pubDate>Wed, 20 May 2020 02:55:25 +0000</pubDate>
      <link>https://forem.com/alessandrojcm/getting-your-user-s-country-and-name-in-a-whatsapp-bot-using-fastapi-and-faunadb-part-2-413f</link>
      <guid>https://forem.com/alessandrojcm/getting-your-user-s-country-and-name-in-a-whatsapp-bot-using-fastapi-and-faunadb-part-2-413f</guid>
      <description>&lt;p&gt;Hello there! Here we will continue when we left off in the previous part. So right now we have our shiny static actions all set up; now, in this post, we will scaffold the FastApi project and write the code needed to query FaunaDB.&lt;/p&gt;

&lt;h1&gt;
  
  
  Setting up our project
&lt;/h1&gt;

&lt;p&gt;Ok so first I will assume you have both Python 3.8.2 and &lt;code&gt;virtualenv&lt;/code&gt; in your system; if not, please set them up according to your operating system before continuing.&lt;/p&gt;

&lt;p&gt;Next, let's create a folder and a virtual environment for our project; for the remaining of this article I will assume a UNIX-based system:&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;mkdir &lt;/span&gt;our-awesome-api &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;our-awesome-api
virtualenv venv
&lt;span class="nb"&gt;source &lt;/span&gt;venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we will install the packages we need, and what do we need? Just FastApi and Fauna! Well no, we'll need also a couple more:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;uvicorn fastapi faunadb python-dotenv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok, the two in the middle we recognize, but what are the other two? Well, &lt;code&gt;python-dotenv&lt;/code&gt; it's a package FastApi (or more precisely, Pydantic) uses to parse &lt;code&gt;.env&lt;/code&gt; files; just like Javascript's &lt;code&gt;dotenv&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;On the other hand, &lt;code&gt;uvicorn&lt;/code&gt; it's an ASGI server; remember those? It's the layer that allows FastApi to talk to HTTP servers like Nginx or Apache, it also has a nice dev server built-in along with hot reload when the code changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The file structure
&lt;/h2&gt;

&lt;p&gt;Now, let's create a nice file structure for our project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;└───src
    ├───core
    ├───models
    └───app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What do we have here?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;src&lt;/code&gt; it's the source root of our project&lt;/li&gt;
&lt;li&gt;In &lt;code&gt;core&lt;/code&gt; lives all the code that's critical for the start of our application&lt;/li&gt;
&lt;li&gt;In &lt;code&gt;models&lt;/code&gt; are all the data models (like users, for example)&lt;/li&gt;
&lt;li&gt;In &lt;code&gt;app.py&lt;/code&gt; is where we create our application instance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now that we have our packages installed and our file structure all set up, let's figure out what we need first.&lt;/p&gt;

&lt;h1&gt;
  
  
  Querying FaunaDB
&lt;/h1&gt;

&lt;p&gt;The core of this project is, basically, storing and retrieving users. For that, we need to query our database; now, how do we do that? First, let's see how Fauna stores data: the records fauna stores are called &lt;strong&gt;documents&lt;/strong&gt; (like in MongoDB), and documents are stored into &lt;strong&gt;collections&lt;/strong&gt;. Like I mentioned in the previous article, Fauna does not enforce any type of data schema at the database level, and because of that, we can find inconsistencies between documents in the same collection. Nevertheless, all the documents have two common properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ref&lt;/code&gt;, which is a unique identifier given automatically to all documents (like an old fashioned id)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ts&lt;/code&gt;, which is a timestamp that changes every time an event occurs to this particular document (like creation or update). Fauna automatically stores the events in the lifetime of an object, and you can query it, but that is beyond the scope of this post.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;data&lt;/code&gt;, this is a dictionary containing all the data we store for the object. So, for example, is this was a users collection this would contain information like the username or email of our users.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cool, now that we know how the data is stored, we need to know how to query it. Because no data schema is enforced at the database level, Fauna does not know out of the box &lt;em&gt;how&lt;/em&gt; to query our data (we can't for example, query by username like we would do in a relational database); to tell Fauna how to query our data, we need to create an &lt;strong&gt;index&lt;/strong&gt;. Indexes are a set of instructions, associated with a particular collection, that tells fauna how to query that collection; at the same time, we can tell indexes what data we need to be retrieved. So, for example, maybe we need to list all our users, this list will only show their username and the date they signed up; it would be a waste of bandwidth to fetch all the additional data we won't show in the list. So, for this, we can create an index that does exactly this: paginate through all the users and retrieve only their username and the document creation date.&lt;/p&gt;

&lt;p&gt;Ok, enough theoretical talking, let's recall our flowchart from the last post and see what indexes we need:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qvgj8y5X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/d08pec0eqh76371zmaq2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qvgj8y5X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/d08pec0eqh76371zmaq2.png" alt="Bot's greeting process flowchart"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, we see that we need to check if the user already exists. For that, we'll need an index that queries our collection via a unique index. What property of our users is unique? That's right, their phone number! So, let's go to the Fauna website.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating an index
&lt;/h2&gt;

&lt;p&gt;Well, that title is a bit misleading. We can't create an index for a collection that does not exist and we cannot create a collection for a database that does not exist. So let's go to &lt;a href="https://dashboard.fauna.com/accounts/login"&gt;FaunaDB&lt;/a&gt; and sign up using our GitHub credentials.&lt;/p&gt;

&lt;p&gt;Then, in the dashboard, let's click in "&lt;em&gt;New Database&lt;/em&gt;" and create our shiny new instance:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kcjJTep0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/a5phnx5rxgj17n8qzscj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kcjJTep0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/a5phnx5rxgj17n8qzscj.png" alt="Database creation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, let's create our collection, I named mine users for the sake of originality:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GA6lqY_c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/knasv952e7iq7zzjnnae.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GA6lqY_c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/knasv952e7iq7zzjnnae.png" alt="Collection creation"&gt;&lt;/a&gt;&lt;br&gt;
Now, with the database and collection created, we can finally make our index:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zUIlAu2g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/iiz2tlrt3khc68wefg72.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zUIlAu2g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/iiz2tlrt3khc68wefg72.png" alt="Index creation"&gt;&lt;/a&gt;&lt;br&gt;
Ok, we have several things going on here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First of all, we need to select to which collection this index belongs. We say that this is for querying our users collection.&lt;/li&gt;
&lt;li&gt;Then, the index name. Pretty self-explanatory.&lt;/li&gt;
&lt;li&gt;Now we have this thing called "&lt;em&gt;terms&lt;/em&gt;", what's this? Well, indexes can be used to &lt;strong&gt;paginate&lt;/strong&gt; over a collection (that is, to list a collection) or they can be used to query a collection using a special field (for example, via the phone number, as we have here). Terms are exactly that: the terms that this index will use to query our data against. We write &lt;code&gt;phone_number&lt;/code&gt; here and we see the dashboard formats this to &lt;code&gt;data.phone_number&lt;/code&gt;, that is because all non-standard fields are considered to be inside of the data dict I mentioned before. By standard fields, I mean fields that Fauna generated, like &lt;code&gt;ref&lt;/code&gt; and &lt;code&gt;ts&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;values&lt;/code&gt; are all those fields we want the index to fetch, in this case, we want to fetch the &lt;code&gt;name&lt;/code&gt;, the &lt;code&gt;country&lt;/code&gt;, and the &lt;code&gt;ref&lt;/code&gt;. Notice how the dashboard put &lt;code&gt;ref&lt;/code&gt; and no &lt;code&gt;data.ref&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cool! Now let's click save and leave the dashboard for now.&lt;/p&gt;
&lt;h2&gt;
  
  
  Querying Fauna from Python
&lt;/h2&gt;

&lt;p&gt;Ok, now we need to write code in order to query our database from python. For that, I adopted an OOP approach, kinda like a traditional ORM:&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;# src/models/user_document.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;faunadb&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;faunadb.objects&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Ref&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Ref&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&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="n"&gt;phone_number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
  &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;

  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;arbitrary_types_allowed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

 &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;classmethod&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_by_phone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;phone_number&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="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&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="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"get_by_phone_number"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;phone_number&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&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;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
      &lt;span class="c1"&gt;# The result is a list with the values ordered as the index we defined
&lt;/span&gt;      &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"data"&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;return&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;phone_number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;phone_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, what the heck do we have here? First of all, what is &lt;code&gt;BaseModel&lt;/code&gt; parent class? Well, FastApi uses &lt;a href="https://pydantic-docs.helpmanual.io/"&gt;Pydantic&lt;/a&gt; under the hood. Pydantic it's a validation library that uses the type hints introduced in Python 3.6 for doing its validation. For a class to be validated with Pydantic, we have to do two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inherit from the &lt;code&gt;BaseModel&lt;/code&gt; class&lt;/li&gt;
&lt;li&gt;Defined our class attributes with type hints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And that's what we have done here: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ref&lt;/code&gt; and &lt;code&gt;ts&lt;/code&gt; its Fauna's reference type as discussed above, the Python driver comes with its own type hints for the former, so we use those; for &lt;code&gt;ts&lt;/code&gt;s we use &lt;code&gt;int&lt;/code&gt;, because it is just a UNIX timestamp. You'll see why are they marked as &lt;code&gt;Optional&lt;/code&gt; in a moment.&lt;/li&gt;
&lt;li&gt;We add the same attributes we defined in our index, so the phone number, the country, and the name.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After that, we have just defined a constructor to init the base class.&lt;/p&gt;

&lt;p&gt;Now, we come to the heart of this class, the &lt;code&gt;get_by_phone&lt;/code&gt; static method:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First, we create a query using the FaunaDB client (we will define the &lt;code&gt;session&lt;/code&gt; object in a moment)

&lt;ul&gt;
&lt;li&gt;Inside the &lt;code&gt;query&lt;/code&gt; we pass a &lt;code&gt;paginate&lt;/code&gt; object&lt;/li&gt;
&lt;li&gt;Then we say we want to &lt;code&gt;match&lt;/code&gt; against and &lt;code&gt;index&lt;/code&gt;, in this case, is the &lt;code&gt;"get_by_phone_number"&lt;/code&gt; index we created above&lt;/li&gt;
&lt;li&gt;As a param to that index, we pass the &lt;code&gt;phone_number&lt;/code&gt; parameter passed to the function&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;If the query returns an empty list, we say that we did not find the user, so we return &lt;code&gt;None&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;If not, then we create an instance of &lt;code&gt;User&lt;/code&gt; with the first element of the list the query returned. Notice that this is a static method, because of that we use &lt;code&gt;User&lt;/code&gt; directly instead of &lt;code&gt;self&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cool, now two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The order in which the document's properties are returned &lt;strong&gt;is the same order that we defined in our index&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;The returned data is a list, why is that? Well, remember that &lt;strong&gt;Fauna does not make any assumptions about our data&lt;/strong&gt;. It does not know that the &lt;code&gt;phone_number&lt;/code&gt; must be unique, that is for us to enforce at the application level. So for all Fauna knows, there could be a thousand documents with the same number, what we're doing here is a &lt;em&gt;very educated guess&lt;/em&gt;; so we need to be careful to not allow duplicate phone numbers at the application level.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Populating that &lt;code&gt;core&lt;/code&gt; folder
&lt;/h1&gt;

&lt;p&gt;You noticed above that I imported a magical &lt;code&gt;session&lt;/code&gt; object, you also probably guessed that was the Fauna client initialized somewhere. If you did, you are correct; let's write that initialization code now:&lt;/p&gt;

&lt;p&gt;For this, we need to first write a model to hold the configuration of our app.&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;# src/models/config.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseSettings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AnyHttpUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;validator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HttpUrl&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pydantic.types&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SecretStr&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseSettings&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
  &lt;span class="n"&gt;FAUNA_SERVER_KEY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SecretStr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"your_server_key_here"&lt;/span&gt;
  &lt;span class="n"&gt;FAKE_NUMBER&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"+15555555"&lt;/span&gt;
  &lt;span class="n"&gt;TWILIO_AUTH_TOKEN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&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;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;validator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"TWILIO_AUTH_TOKEN"&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;check_for_twilio_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"DEBUG"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;
        &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"DEBUG"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nb"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{f} must be set"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;field&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;v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, what's this? Well, Pydantic comes with a very &lt;a href="https://pydantic-docs.helpmanual.io/usage/settings/"&gt;handy&lt;/a&gt; class to handle settings (and validation of those settings) for your application. Let's explain what's going on here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First, the base class is &lt;code&gt;BaseSettings&lt;/code&gt; instead of &lt;code&gt;BaseModel&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Then, we defined the configuration variables our application will need:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DEBUG&lt;/code&gt; a handy flag to, well, enable debug mode. Defaults to &lt;code&gt;False&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FAUNA_SERVER_KEY&lt;/code&gt;, the API key four our database. It is of type &lt;code&gt;SecretStr&lt;/code&gt;, which is a helper type to avoid printing the value to &lt;code&gt;stdout&lt;/code&gt;: it will print a bunch of asterisks instead of the real value&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FAKE_NUMBER&lt;/code&gt;, more about that later ;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TWILIO_AUTH_TOKEN&lt;/code&gt; our Twilio API Token, we'll need it later.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;After that, there is a Pydantic &lt;a href="https://pydantic-docs.helpmanual.io/usage/validators/"&gt;decorator&lt;/a&gt;, this function acts as a custom validator for the property that was passed as a parameter to the decorator. In this case, if we are running in debug mode, we don't care if the Twilio API key is not present so we jus return; on the other hand, if &lt;code&gt;DEBUG&lt;/code&gt; is set to false, we need to check that the Twilio API key is present, otherwise the application would raise an exception&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Great, now let's put this model into use:&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;# src/core/config.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;environ&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;src.models.config&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DEBUG"&lt;/span&gt;&lt;span class="p"&gt;,&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;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_env_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;".env"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not much going on here: we are just checking if &lt;code&gt;DEBUG&lt;/code&gt; is set. If it is, we populate our config via a &lt;code&gt;.env&lt;/code&gt; file; if not the config will be taken from environment variables.&lt;/p&gt;

&lt;p&gt;Now, the Pydantic base setting has an order of priority to load variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Higher priority goes to environment variables&lt;/li&gt;
&lt;li&gt;The next higher priority goes to the &lt;code&gt;.env&lt;/code&gt; file, so if the same variable is defined in the file and in the environment, the one in the environment will take precedence.&lt;/li&gt;
&lt;li&gt;Defaults defined in the model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So if an attribute not marked as &lt;code&gt;Optional&lt;/code&gt; which does not have a default and is not defined either in the &lt;code&gt;.env&lt;/code&gt; nor in the environment, will cause Pydantic to throw an error.&lt;/p&gt;

&lt;p&gt;Next, the misterious &lt;code&gt;session&lt;/code&gt; object:&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;# src/core/engine.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;faunadb.client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FaunaClient&lt;/span&gt;

&lt;span class="n"&gt;_session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;session&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="s"&gt;"""
    Helper function to get faunadb session, if there is one already it just returns
    """&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.config&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;

    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;_session&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_session&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;_session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FaunaClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FAUNA_SERVER_KEY&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_secret_value&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;_session&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing crazy here (and sorry for the &lt;code&gt;global&lt;/code&gt;), we are just checking if Fauna client is initialized: if it is, we just return it, if it is not, then we initialize it with the key loaded from our configuration. Check that we are calling the &lt;code&gt;get_secret_value&lt;/code&gt; method, remember that helper class from Pydantic? Well, we have to call this method in order to get the real value, otherwise, we would get a bunch of asterisks.&lt;/p&gt;

&lt;h1&gt;
  
  
  Our first endpoint
&lt;/h1&gt;

&lt;p&gt;Now finally! Let's write our first endpoint. What does this endpoint do? Well, according to our flowchart, it will be responsible to check if our user is in the database and will react accordingly. Let's create a &lt;code&gt;routes&lt;/code&gt; folder inside our source root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir src/routes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside this is of course our &lt;code&gt;__init__.py&lt;/code&gt; and our new &lt;code&gt;greet.py&lt;/code&gt; file which will contain our routes. Next, let's first modify our bot's schema to redirect to our endpoint when the user greets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"uniqueName": "greeting",
"actions": {
  "actions": [
    {
      "redirect": {
        "method": "POST",
        "uri": "our_super_yet_to_be_endpoint/greeting"
      }
    }
  ]
},
// Other stuff down here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we just changed our default action to be a &lt;code&gt;redirect&lt;/code&gt; instead of a &lt;code&gt;say&lt;/code&gt; action. Also, we said the Autopilot should send this as a &lt;code&gt;POST&lt;/code&gt; request.&lt;/p&gt;

&lt;p&gt;Now, in order to write our endpoint, we need to take a look at the Autopilot &lt;a href="https://www.twilio.com/docs/autopilot/actions/autopilot-request"&gt;request reference&lt;/a&gt;. There, we learn that autopilot sends the data using the &lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt; encoding. For FastApi to parse that, we need yet to add another library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install python-multipart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, finally, our 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;# src/routes/greet.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;APIRouter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Form&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;src.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;

&lt;span class="n"&gt;user_greeting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;APIRouter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;user_greeting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/greeting"&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;greet_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UserIdentifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;(...)):&lt;/span&gt;
    &lt;span class="s"&gt;"""
    User greet endpoint
    :param: UserIdentifier: user's phone number from Twilio
    """&lt;/span&gt;

    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_by_phone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phone_number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;UserIdentifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="c1"&gt;# Greeting the user since already exists
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"actions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"remember"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"say"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Hi there {name}! Welcome back, how are things in {c}?"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&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="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"actions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="s"&gt;"say"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Hello there!"&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"redirect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"task://can-have-name"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not much going on here, if you've worked with Python web frameworks this will be pretty familiar to you. Let's see what's going on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First we create a router, this is what FastApi uses to bundle routes together. It helps organize code in self-contained routes.&lt;/li&gt;
&lt;li&gt;Next, we use that object to create a &lt;code&gt;POST&lt;/code&gt; endpoint using it as a decorator, FastApi has decorator for all the HTTP verbs. We pass the endpoint's URI as a parameter.&lt;/li&gt;
&lt;li&gt;Next, the function name, it can be anything&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, what's with the function parameters? Well, FastApi also takes advantage of Python type hints in order to validate our requests. So if, for example, we pass something like: &lt;code&gt;email: str&lt;/code&gt; as a parameter, FastApi will look for a parameter called &lt;code&gt;email&lt;/code&gt; of type &lt;code&gt;str&lt;/code&gt; in the query parameter string of the request. Similarly for all types of request parameters (&lt;code&gt;Body&lt;/code&gt;, path parameters, etc). The name of the function parameter needs to be the &lt;strong&gt;exactly&lt;/strong&gt; the parameter we're expecting; otherwise, FastApi will respond with a 422 Unprocessable Entity error.&lt;/p&gt;

&lt;p&gt;So, in this case, we're expecting a parameter called &lt;code&gt;UserIdentifier&lt;/code&gt; (as per the Twilio docs), which is a string. We also pass &lt;code&gt;Form&lt;/code&gt; as a default value: that way we tell FastApi that we are expecting an &lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt; request. The ellipsis (the three &lt;code&gt;...&lt;/code&gt;) inside the &lt;code&gt;Form&lt;/code&gt; constructor is just telling that this form has no dependencies (dependencies are another FastApi &lt;a href="https://fastapi.tiangolo.com/advanced/advanced-dependencies/"&gt;feature&lt;/a&gt; beyond the scope of this post). But, after all, what's a &lt;code&gt;UserIdentifier&lt;/code&gt;? Well, depends on the channel Autopilot is using to communicate with the user, in Whatsapp and SMS channel it will be the user's phone number.&lt;/p&gt;

&lt;p&gt;After that it's all pretty self-explanatory:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We use the Fauna index we created before to check if the user exists.&lt;/li&gt;
&lt;li&gt;If it exists, we return a personalized greeting&lt;/li&gt;
&lt;li&gt;If not, we redirect to the action that will ask the user for their name; using the task's &lt;code&gt;uniqueName&lt;/code&gt; we created in our previous post&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By default, if we return a Python dict from a FastApi endpoint, it will be encoded as &lt;code&gt;application/json&lt;/code&gt;; which is just what we need. Also, check that we are adhering to Autopilot's Action Schema. We already know about the &lt;code&gt;say&lt;/code&gt; and &lt;code&gt;redirect&lt;/code&gt;, but what is this &lt;code&gt;remember&lt;/code&gt; thingy? Well, Autopilot bots have the concept of "session": sessions are a time frame on which a conversation between the bot and a user is considered active. While the session is active, all data interchanged between the bot and the user will be store and the bot is able to send messages to the user without having to wait for their input; the time the session is considered active varies between channels, for Whatsapp is 24 hours. Now, the bot also has a memory that lasts the whole session; with the &lt;code&gt;remember&lt;/code&gt; action; we are telling the bot to store those values in memory. That way, if we need to refer to the user's name or country, we don't have to hit the database again.&lt;/p&gt;

&lt;h1&gt;
  
  
  Wrapping up
&lt;/h1&gt;

&lt;p&gt;So that's all for this post folks! Initially, this was to be a two-post series, but it overextended a bit. So for the sake of not boring the heck out of you, I decided to make this into a three-post series. In the next post, we'll implement the endpoint to store the name, deploy this thing live and a few more tricks ;)&lt;/p&gt;

</description>
      <category>python</category>
      <category>showdev</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Getting your user's country and name in a Whatsapp bot using Fastapi and FaunaDB</title>
      <dc:creator>Alessandro Cuppari</dc:creator>
      <pubDate>Wed, 06 May 2020 02:45:26 +0000</pubDate>
      <link>https://forem.com/alessandrojcm/getting-your-user-s-country-and-name-in-a-whatsapp-bot-using-fastapi-and-faunadb-46m</link>
      <guid>https://forem.com/alessandrojcm/getting-your-user-s-country-and-name-in-a-whatsapp-bot-using-fastapi-and-faunadb-46m</guid>
      <description>&lt;p&gt;Hi there you all! Yes I know, the title it's a bit weird: I was supposed to document my project &lt;em&gt;during&lt;/em&gt; the Twilio hackathon; but alas I didn't have the time to do it. So, I decided to do a kind of &lt;del&gt;post mortem&lt;/del&gt; describing the part that took me the longest to develop. On top of it, celebrating my &lt;del&gt;second&lt;/del&gt; first post on DEV I wanted to tackle this with a different angle.&lt;/p&gt;

&lt;p&gt;Instead of doing a by-the-book tutorial, Which would make me look super smart, I want to walk through my thought process and document all the gotchas along the way; whilst keeping this as beginner-friendly as possible. So let's start!&lt;/p&gt;

&lt;h2&gt;
  
  
  What are we going to build?
&lt;/h2&gt;

&lt;p&gt;We are going to build a virtual bot using Twilio Autopilot, In the end, this bot will work via a Whatsapp channel, but Autopilot allows you to connect to several other channels without changing your bot's code. This fancy bot will ask the user for its name and will store it in a database along with the user's phone number and country. Use case for this? Well maybe you are building a virtual assistant or you just want to bot to be polite and greet the user via their name; the possibilities are endless!&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;p&gt;You probably guessed by now the building blocks we need: firstly, we need to persist data; so we need a database. Also, we need something to interact with that database and do some processing, like figuring out the user's country. For the former, I used FaunaDB: a multi-cloud decentralized database service and, for the later, the Fastapi framework: an ASGI framework built for the modern Python.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wait, what?
&lt;/h3&gt;

&lt;p&gt;"&lt;em&gt;Wait a minute Alessandro&lt;/em&gt;", I hear you say, "&lt;em&gt;That's a lot of sophisticated technical mumbo-jumbo you have there, care to elaborate&lt;/em&gt;?" Sure, let's see what these technologies are all about and why I choose them.&lt;/p&gt;

&lt;h3&gt;
  
  
  FaunaDB
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YxKZEeU2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/6cnkeuyvaigln4dctdps.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YxKZEeU2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/6cnkeuyvaigln4dctdps.png" alt="FaunaDB"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fauna it's a NoSQL database that's "&lt;em&gt;built for the JAMStack&lt;/em&gt;", that last part means that it's designed to be accessed directly from front-end applications and, because of that, it offers the possibility to set permissions in a very granular way; so you can control with precision which users can access what data. &lt;/p&gt;

&lt;p&gt;The NoSQL part means (you guessed it) that it does not use the Schema Query Language for making queries to the data; but this name can come with a bit of an understatement: it also means that it &lt;strong&gt;makes no assumptions about the shape of your data&lt;/strong&gt; (hold this); so that means that, inside a collection of data (say users for example), we can find inconsistencies in records; for example: maybe a user record has a last name field but other user doesn't. &lt;/p&gt;

&lt;p&gt;The decentralized part means that your database it's replicated across multiple instances, that's a fancy way of saying that there are a bunch of copies of your database scattered across several different servers. Why is this good? Because this offers &lt;strong&gt;data redundancy&lt;/strong&gt;; sure, maybe you've heard that data redundancy it's the root of all evil and it must be eradicated from the face of the earth, but that depends on the context. In this case, it's good because it means that, if one of your servers fails, the other replicas are there to ensure that your service does not interrupt and, more importantly, that you don't lose any data; it also means that data will be server from the replica geographically closet to the user, resulting in faster response times. This replication it's harder to achieve when you have to set it up by yourself, but Fauna abstracts away all this hassle for us.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why I choose it
&lt;/h4&gt;

&lt;p&gt;Well all this explanation sound well and good, but the reasons I chose it are far less romantic:&lt;/p&gt;

&lt;p&gt;1) Cost, as in money: as devs, when we embark on a side project that needs to be hosted live, we try to minimize costs as much as possible. And database hosting is one of those things that can be costly, especially if you don't pay much attention to the pricing model: you can end up with a bill that's higher than you expected. Fauna has a great free plan that's more than enough.&lt;br&gt;
2) The nature of the schema: as you have noticed, the data schema it's pretty simple (name, phone number, and country); so spinning up a whole relational database with SQLAlchemy, migrations, and such was a bit overkill.&lt;br&gt;
3) Sheer curiosity: Being a database built for serverless, I was curious to see if Fauna could adapt to a more traditional application, and since they have a Python driver that was a no brainer (Spoiler alert: it does, kinda).&lt;/p&gt;
&lt;h3&gt;
  
  
  FastAPI
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YdQd3SYp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YdQd3SYp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="Fastapi"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;FastaAPI is an Asynchronous Standard Gateway Interface (ASGI) framework built for the modern Python. It's built upon two great projects: Starlette (another ASGI framework) and Pydantic (a validation library with zero dependencies). So, what's ASGI anyways? Well, it's the natural evolution of Web Server Gateway Interface (WSGI).&lt;/p&gt;

&lt;p&gt;You see, if you have worked with a WSGI framework (like Flask or Django) you know that they have built-in servers; so you can just test your application in real-time when development, usually with nice logging and live reload built-in. Thing is that, when the project gets deployed, that dev server won't cut it: you'll need a real server like Apache's HTTP Webserver, Nginx or IIS. But the detail it's that those servers are usually written in C/C++ and they handle requests via sockets and all that low-level stuff, they don't speak Python! So you need a "glue" for the servers to be able to call your Python application, that's were WSGI came in.&lt;/p&gt;

&lt;p&gt;The problem with WSGI it's that it is synchronous: it cannot take advantage of asynchronous programming, thus it's often "slower" than pure asynchronous applications like those written with the Express framework or the Go programming language.&lt;/p&gt;

&lt;p&gt;For that, ASGI was created: it brings a new interface that supports asynchronous code on the server-side with Python's async/await syntax and also brings new features like HTTP/2 support and Websockets. &lt;/p&gt;

&lt;p&gt;I won't be diving into more details about this, that would be a post in its own. But you can find plenty of material on the &lt;a href="https://www.encode.io/articles/hello-asgi"&gt;web&lt;/a&gt; or here in &lt;a href="https://dev.to/florimondmanca/introduction-to-asgi-emergence-of-an-async-python-web-ecosystem-38oi"&gt;DEV&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Why I choose it
&lt;/h4&gt;

&lt;p&gt;1) Because of Python!&lt;br&gt;
2) I had my eye on FastAPI for a while and was just looking for an excuse to use it&lt;/p&gt;

&lt;p&gt;Yeah, a couple of pretty objective well-researched reasons right there; thank you very much.&lt;/p&gt;
&lt;h1&gt;
  
  
  The bot
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8dBR08HO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/sbdrsd7xam63n6zp1x8x.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8dBR08HO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/sbdrsd7xam63n6zp1x8x.jpg" alt="Toy robot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So now, let's get into the bot's design. First, let's head to Autopilot's &lt;a href="https://www.twilio.com/docs/autopilot"&gt;documentation&lt;/a&gt; and we'll that bots are composed as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The unique name of the bot&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;defaults&lt;/code&gt; section, which defines (you guessed it) default behaviors for your bot: the default task, default task to run on errors and the default task to run in case a &lt;code&gt;collect&lt;/code&gt; action fails (well's see what this is later on).&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;styleSheet&lt;/code&gt; defines the "personality", here we define the default messages to say in case of errors or success, the default behavior of &lt;code&gt;collect&lt;/code&gt; actions, and the voice for the bot (this in case we use the bot for automatic phone calls).&lt;/li&gt;
&lt;li&gt;Tasks: the tasks (duh) they need to perform; each one with a name

&lt;ul&gt;
&lt;li&gt;Actions: the individual actions that these tasks trigger.&lt;/li&gt;
&lt;li&gt;Samples: the part of the conversation that triggers this task.&lt;/li&gt;
&lt;li&gt;Fields: pre-defined fields that hold special value and will appear in your samples (we'll also see what's this about later on)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I will only be explained the tasks needed for this bot, so, for the complete list check the &lt;a href="https://www.twilio.com/docs/autopilot/actions"&gt;docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We can also see that task are a simple JSON schema and that there are two types of actions:&lt;/p&gt;

&lt;p&gt;1) Static: those defined in the bot's JSON schema.&lt;br&gt;
2) Dynamic: those that are returned from an API, must comply with the JSON schema.&lt;/p&gt;

&lt;p&gt;As you probably have guessed by now, static actions cannot perform any kind of logic: they only trigger, perform the actions described in the schema, and end. If we are to do any kind of logic, we must use dynamic actions.&lt;/p&gt;

&lt;p&gt;So, with this in mind, let's see which tasks we need. For the sake of making this article less boring, let's throw a flowchart right here:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qvgj8y5X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/d08pec0eqh76371zmaq2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qvgj8y5X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/d08pec0eqh76371zmaq2.png" alt="Bot's greeting process flowchart"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the flowchart's decision and process blocks, we can easily determine, not only the number of actions needed but also which actions are dynamic and which are static. &lt;/p&gt;

&lt;p&gt;The static actions would be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ask the user if they want to give us their name; this question needs to be broken down into two, we'll see why in a moment.&lt;/li&gt;
&lt;li&gt;The action that takes place after the &lt;em&gt;user response&lt;/em&gt; decision block.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The only dynamic action will be the greet action since we need to check if the user is already in the database to build the appropriate greeting message.&lt;/p&gt;
&lt;h2&gt;
  
  
  Creating the initial schema
&lt;/h2&gt;

&lt;p&gt;Ok, now that we determined how many actions we need; let's create the actual schema that will describe our bot. In this post, we'll be writing only the static actions; while the dynamic will be written in the next post. First of all, you need to know that there are two ways of developing an Autopilot bot: first one it's through the Twilio Dashboard, and the second one it's by editing the bot's JSON schema directly. In this little project, we'll be using the latter.&lt;/p&gt;
&lt;h3&gt;
  
  
  Setting up the schema
&lt;/h3&gt;

&lt;p&gt;There are a bunch of &lt;a href="https://github.com/twilio/autopilot-templates/tree/master/Assistants"&gt;templates&lt;/a&gt; ready for us to use; we'll be using the &lt;em&gt;HelloWorld&lt;/em&gt; template since the other ones don't quite fit our use case. So go to that link and download the &lt;code&gt;schema.json&lt;/code&gt; file inside &lt;code&gt;Assistants/HelloWorld&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's open up the file with our favorite editor, first of all: we'll change our bot's name. Something like &lt;code&gt;almighty-bot&lt;/code&gt; will do, so let's go and change the &lt;code&gt;uniqueName&lt;/code&gt; and &lt;code&gt;friendlyName&lt;/code&gt; keys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"friendlyName"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The almighty bot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"logQueries"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;This&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;activates&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;logging&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;messages&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Twilio&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Dashboard&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"uniqueName"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"almighty-bot"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;other&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;stuff&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we'll see that the schema has only four tasks; two of which are fallback actions in case of error. Let's write the static actions we defined above:&lt;/p&gt;

&lt;h3&gt;
  
  
  Asking for the name
&lt;/h3&gt;

&lt;p&gt;For asking information, we use the &lt;a href="https://www.twilio.com/docs/autopilot/actions/collect"&gt;&lt;code&gt;collect&lt;/code&gt;&lt;/a&gt; action. This action takes a name, an array of questions that will be made in the order they are defined, and an &lt;code&gt;on_success&lt;/code&gt; callback; which can be an HTTP endpoint or another task.&lt;/p&gt;

&lt;p&gt;Remember I said that this action needs to be broken down into two? Well, imagine that our bot asks the users for their name and the user responds with a negative answer, what do we do? As stated above, we cannot perform any logic in static actions; so we need to call an endpoint with our user's response (that is if they want to give us their name or not) and proceed accordingly; just like the flowchart above.&lt;/p&gt;

&lt;p&gt;So, let'a add our very first task; inside the schema's &lt;code&gt;actions&lt;/code&gt; array add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"uniqueName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"can-have-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"actions"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"actions"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"collect"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="nl"&gt;"on_complete"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"redirect"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="nl"&gt;"uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"our_super_yet_to_be_endpoint/can-have-name"&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"ask-for-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="nl"&gt;"questions"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Twilio.YES_NO"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="nl"&gt;"question"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Do you want to tell me your name?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"can_have_name"&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"fields"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"uniqueName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"can_have_name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"fieldType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Twilio.YES_NO"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"samples"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"en-US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"taggedText"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"{can_have_name}"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok, so, what kind of wizardry did we just do? First of all, the task needs a unique (machine-friendly) name. After that, we write the actions the task will perform. In this case, we are doing a &lt;code&gt;collect&lt;/code&gt;. Our &lt;code&gt;collect&lt;/code&gt; task has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A name: &lt;code&gt;ask-for-name&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;An &lt;code&gt;on_complete&lt;/code&gt; key, the action that'll be performed when the &lt;code&gt;collect&lt;/code&gt; ends. In this case, we are instructing the bot to do an HTTP call.&lt;/li&gt;
&lt;li&gt;An array of questions, a question must have:

&lt;ul&gt;
&lt;li&gt;A &lt;a href="https://www.twilio.com/docs/autopilot/built-in-field-types"&gt;type&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The actual question's text.&lt;/li&gt;
&lt;li&gt;A name, this is the key we'll use to parse the answers in our API; so put a programming language friendly name.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice that the type is &lt;code&gt;Twilio.YES_NO&lt;/code&gt;; Autopilot already has a bunch of pre-defined types for the most common questions you'll need. These types are already trained to do natural language recognition; so we'll use it whenever we can.&lt;/p&gt;

&lt;p&gt;Finally, notice the &lt;code&gt;fields&lt;/code&gt; and &lt;code&gt;samples&lt;/code&gt; keys. Like I stated above, &lt;code&gt;fields&lt;/code&gt; are pre-defined, well, fields, that act like variable names and samples are the inputs that will trigger this particular task. All collect actions must have at least one sample (Twilio recommends at least 6) but, since we're expecting only a yes or no, we'll only add one sample, which sample? Well, since we're just expecting a yes or no, let's create a &lt;code&gt;field&lt;/code&gt; with that type and add it to the &lt;code&gt;samples&lt;/code&gt; enclosing the field's name with curly braces. Yes, I know this part with the samples and fields it's not clear at the moment; but bear with me, it'll be clear with the next task.&lt;/p&gt;

&lt;p&gt;Now, let's add the actual question for asking the name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"uniqueName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"store-user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"actions"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"actions"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"collect"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="nl"&gt;"on_complete"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"redirect"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="nl"&gt;"uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"our_super_yet_to_be_endpoint/store-user"&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"collect-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="nl"&gt;"questions"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Twilio.FIRST_NAME"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="nl"&gt;"question"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Great! What's your name?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"first_name"&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"fields"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"uniqueName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"first_name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"fieldType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Twilio.FIRST_NAME"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"samples"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"en-US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"taggedText"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"I'm {first_name}"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"en-US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"taggedText"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"{first_name} it's what I'm called"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"en-US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"taggedText"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"{first_name} is my name"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"en-US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"taggedText"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"people call me {first_name}"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"en-US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"taggedText"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"people know me as {first_name}"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"en-US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"taggedText"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"{first_name}"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"en-US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"taggedText"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"my name is {first_name}"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"en-US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"taggedText"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"i'm {first_name}"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, what's going on here it's pretty much the same we did on the previous task: the only changes are the type of the question (now we tell Autopilot to try to recognize first names), the fields and samples and the URI of the endpoint. Remember that about fields being like variable names? So, imagine that the answers you're waiting for it's in the middle of a broader sentence, like: "&lt;em&gt;I'd like to make an appointment at 3 o'clock&lt;/em&gt;" or "&lt;em&gt;My name is John&lt;/em&gt;"; that's where the fields come in: by defining a field, we can write samples and tell Autopilot on which part of the sample we expect our answer to be. If we take a look at the samples above, we'll see the field name (&lt;code&gt;{first_name}&lt;/code&gt;) we defined in the part of the sentence we expect our answer to be; the more samples the better. So now, If our user texts something like "&lt;em&gt;My name is John&lt;/em&gt;"; that will trigger the &lt;em&gt;"my name is {first_name}"&lt;/em&gt; sample and Autopilot will parse it as the answer of the current active question.&lt;/p&gt;

&lt;h3&gt;
  
  
  The final action
&lt;/h3&gt;

&lt;p&gt;This is the action that'll take place after the user has (or not) give us their name. For the sake of simplicity we'll thow a placeholder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
   "uniqueName":"placeholder",
   "actions":{
      "actions":[
         {
            "say":"Almighty bot under construction"
         }
      ]
   },
   "fields":[],
   "samples":[]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we're introducing the &lt;code&gt;say&lt;/code&gt; it (you guessed it again) simply sends a text to the user.&lt;/p&gt;

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

&lt;p&gt;That's all for this post! Not a lot of writing code I know, we'll be writing the API that wires all this up in the next post; so stay tuned😉&lt;/p&gt;

&lt;p&gt;PS: I know, I need to work on post-titling skills&lt;/p&gt;

</description>
      <category>python</category>
      <category>twiliohackathon</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>COVID-19 Helper Whatsapp Bot - A Twilio Hackathon Submission</title>
      <dc:creator>Alessandro Cuppari</dc:creator>
      <pubDate>Wed, 29 Apr 2020 05:23:06 +0000</pubDate>
      <link>https://forem.com/alessandrojcm/covid-19-helper-whatsapp-bot-a-twilio-hackathon-submission-5fje</link>
      <guid>https://forem.com/alessandrojcm/covid-19-helper-whatsapp-bot-a-twilio-hackathon-submission-5fje</guid>
      <description>&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Category: Interesting Integrations
&lt;/h3&gt;

&lt;p&gt;I set up to build a Whatsapp Bot using Twilio Autopilot which would help the user with several tasks in his daily quarantined life; several ones stayed just as ideas (hows that for feature-creep huh?) and my bot got the following function:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Statistical data of the COVID19 pandemic by country:

&lt;ul&gt;
&lt;li&gt;"How are they doing in Spain?"&lt;/li&gt;
&lt;li&gt;"I'd like information on the USA"&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Historical data of the COVID19 pandemic by country:

&lt;ul&gt;
&lt;li&gt;"How was Ireland doing on march 15th"&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;A self-screening process (with a little help from AI):

&lt;ul&gt;
&lt;li&gt;Here, the bot will ask the users if they are feeling the most common symptoms of COVID-19&lt;/li&gt;
&lt;li&gt;Then, along with other variables (do they live in a country with confirmed cases? have they had contact with someone that looks ill?), would advise the users if they should seek medical attention&lt;/li&gt;
&lt;li&gt;Of course, this self checker is merely informative, by no means replaces the diagnosis of a health professional&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  But first, &lt;strong&gt;show me dah&lt;/strong&gt; GIFS!
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TWvD9NE9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/fc9qjztqbop63bj5fkej.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TWvD9NE9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/fc9qjztqbop63bj5fkej.gif" alt="greeting"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--__0H80vL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/oq6bc5be0vuzx1hy081x.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--__0H80vL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/oq6bc5be0vuzx1hy081x.gif" alt="latest data query"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mPoIqVdY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/m11w23xz5asr8yitz4hs.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mPoIqVdY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/m11w23xz5asr8yitz4hs.gif" alt="historical query"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uVybfgKh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/v68nidwtka2jjoi8pet4.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uVybfgKh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/v68nidwtka2jjoi8pet4.gif" alt="screening query"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Link to Code
&lt;/h2&gt;

&lt;p&gt;You can join the sandbox by sending a Whatsapp message to: &lt;code&gt;+1 (415) 523-8886&lt;/code&gt;, first text &lt;code&gt;join using-similar&lt;/code&gt; and then say hi!&lt;/p&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--i3JOwpme--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/alessandrojcm"&gt;
        alessandrojcm
      &lt;/a&gt; / &lt;a href="https://github.com/alessandrojcm/covid19-helper-bot"&gt;
        covid19-helper-bot
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A helper Whatsapp Bot for COVID-19 related information
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
COVID19 Helper Bot&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://app.buddy.works/alessandrojcuppari/twilio-dev-hackathon/pipelines/pipeline/249599" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/08de0b90b7b1c723f583edfb263e34f504c2c5e485f1b8674e73106a4c4e6951/68747470733a2f2f6170702e62756464792e776f726b732f616c657373616e64726f6a637570706172692f7477696c696f2d6465762d6861636b6174686f6e2f706970656c696e65732f706970656c696e652f3234393539392f62616467652e7376673f746f6b656e3d61396531663463373666363038616333316162333463336235323838626634333761643338656530323963386236306232303430366566626530343834333335" alt="buddy pipeline" title="buddy pipeline"&gt;&lt;/a&gt;
&lt;a href="https://deepsource.io/gh/alessandrojcm/twilio-dev-hackathon/?ref=repository-badge" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/434e93b877c79c590cdefc44446594600a959d9f425b2e9ead69648052c7523b/68747470733a2f2f7374617469632e64656570736f757263652e696f2f64656570736f757263652d62616467652d6461726b2d6d696e692e737667" alt="DeepSource"&gt;&lt;/a&gt;
&lt;a href="https://app.buddy.works/alessandrojcuppari/twilio-dev-hackathon/pipelines/pipeline/249599" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/08de0b90b7b1c723f583edfb263e34f504c2c5e485f1b8674e73106a4c4e6951/68747470733a2f2f6170702e62756464792e776f726b732f616c657373616e64726f6a637570706172692f7477696c696f2d6465762d6861636b6174686f6e2f706970656c696e65732f706970656c696e652f3234393539392f62616467652e7376673f746f6b656e3d61396531663463373666363038616333316162333463336235323838626634333761643338656530323963386236306232303430366566626530343834333335" alt="buddy pipeline" title="buddy pipeline"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A Whatsapp Bot to help get information about COVID19, aiming to participate
on the &lt;a href="https://dev.to/devteam/announcing-the-twilio-hackathon-on-dev-2lh8" rel="nofollow"&gt;Twilio &amp;amp; Dev 2020 Hackathon&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Using Twilio Autopilot and Python 3.8&lt;/p&gt;
&lt;h2&gt;
Stack&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Python3.8.2&lt;/li&gt;
&lt;li&gt;Poetry&lt;/li&gt;
&lt;li&gt;Fastapi
&lt;ul&gt;
&lt;li&gt;Pydantic&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Fauna DB&lt;/li&gt;
&lt;li&gt;Hosted on Heroku&lt;/li&gt;
&lt;li&gt;Pytest&lt;/li&gt;
&lt;li&gt;Loguru&lt;/li&gt;
&lt;li&gt;Requests&lt;/li&gt;
&lt;li&gt;Sentry&lt;/li&gt;
&lt;li&gt;Click&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Kickstarted with: &lt;a href="https://github.com/Dectinc/cookiecutter-fastapi"&gt;https://github.com/Dectinc/cookiecutter-fastapi&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
Project structure&lt;/h2&gt;
&lt;p&gt;Files related to application are in the &lt;code&gt;app&lt;/code&gt; or &lt;code&gt;tests&lt;/code&gt; directories
Application parts are:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
├── app
│   ├── api - Api routes
│   │   └── routes
│   │       └── autopilot - Twilio Autopilot Dynamic actions
│   ├── core - Critical configuration (sessions, logging, etc)
│   ├── custom_routers - FastAPI custom routes
│   ├── error_handlers - Custom error handlers
│   ├── middlewares - FastAPI/Starlette middleware configuration
│   ├── models - Pydatic models
│   ├── scripts - Helper scripts (mainly cli stuff)
│   ├── services - Utilites for interaction with external apis and logic too heavy for the routes
│   └── utils&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/alessandrojcm/covid19-helper-bot"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  The project
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The one million dollar ideas
&lt;/h3&gt;

&lt;p&gt;So first of all, when I saw the hackathon announcement I knew I had to participate. Right away I also knew that I wanted to make a Whastapp Bot and I wanted to do it with Python. But alas, what should the bot do? I'd started brainstorming ideas like crazy, here are some of then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Query a specific statistic (ie: how many recoveries in Spain)&lt;/li&gt;
&lt;li&gt;Query by more specific location (ie: how many cases in San Francisco)&lt;/li&gt;
&lt;li&gt;Take the user's location and show open grocery stores near then&lt;/li&gt;
&lt;li&gt;Send the user the latest news for their country daily&lt;/li&gt;
&lt;li&gt;And there are a couple more left out &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, pretty feature creep huh? Some of those features were left out because of lack of time and others for technical reasons (for example, the Whatsapp API does not let you take the user's location; at least not yet). But I knew that I had to implement the self-screening process.&lt;/p&gt;

&lt;h3&gt;
  
  
  The stack, oh the stack
&lt;/h3&gt;

&lt;p&gt;well, I knew more or less what I wanted to do, now with the stack. I wanted to it in Python and I wanted to do it with &lt;a href="https://fastapi.tiangolo.com/"&gt;Fastapi&lt;/a&gt;, since Twilio has a helper library for Python that was a no brainer. &lt;/p&gt;

&lt;p&gt;Now, the fun part was my database of choice: &lt;a href="https://faunadb.com"&gt;FaunaDB&lt;/a&gt;. Why did I choose it? Well, first of all, it has a generous free tier, and second, they advertise heavily for being a solution for the JAM stack; so I was curious to see how it fitted with a more traditional use case (spoiler: it can, but with some gotchas).&lt;/p&gt;

&lt;p&gt;So I set sail with my shiny stack, and boy it was a ride. First, because it was my first time with an ASGI framework; and thus I had to wrap my head around Python coroutines and async routes. Things that you would normally do in WSGI frameworks are different here (middlewares, for example); but, overall, I had a blast working with Fastapi: I learn a lot in the process and was nice to work with a framework designed for the modern Python.&lt;/p&gt;

&lt;p&gt;But Fauna, well that was another beast. I come mainly from a traditional relational database background, so jumping to NoSQL was a challenge of its own; there were a lot of gotchas in the process: where are my primary keys? If this value is unique why does my query return an array? Where are my migrations? How can this record be repeated if all its fields are the same? And all kinds of surprises.&lt;/p&gt;

&lt;h3&gt;
  
  
  Last but not least, the bot
&lt;/h3&gt;

&lt;p&gt;At this point, I was sure developing the bot would be the bigger challenge. At first, it seemed like so: I couldn't get the Autopilot requests to hit my API; it turned out it was a case of me skipping a little of pretty important documentation (specifically how to parse &lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt; requests with FastAPI). But, once I had that sorted out, the bot itself turned out to be a breeze to develop. &lt;/p&gt;

&lt;h2&gt;
  
  
  Additional Resources/Info
&lt;/h2&gt;

&lt;p&gt;The APIS I used are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://corona.lmao.ninja/"&gt;Novelcovid API&lt;/a&gt;, an API that aggregates data from several sources. I choose the one from the John Hopkins Universite&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.endlessmedical.com"&gt;Endless Medical API&lt;/a&gt;, AI-powered trained with real records and diagnosis that suggests outcomes based on a patient's symptoms. Thanks to Lukasz Kiljanek for kindly answering my inquiries!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Overall, this was a great project and not only got me to learn several things in the process; at first, I was a little disappointed that I couldn't squeeze more features in, but seeing all the new concepts I managed to grasp in only a month made it a worthy ride.&lt;/p&gt;

&lt;p&gt;It also got my first post on DEV published! So that alone was worth it (I may be following up with more in-depth articles about the code 😉). So that's all, happy coding and keep safe!&lt;/p&gt;

</description>
      <category>twiliohackathon</category>
    </item>
  </channel>
</rss>
