<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"><channel><title>Full Stack Python</title><link>https://www.fullstackpython.com/</link><description></description><lastBuildDate>Mon, 13 Sep 2021 00:00:00 -0400</lastBuildDate><item><title>Application Performance Monitoring AWS Lambda Functions with Sentry</title><link>https://www.fullstackpython.com/blog/application-performance-monitoring-aws-lambda-functions-sentry.html</link><description>&lt;p&gt;&lt;a href="/aws-lambda.html"&gt;Amazon Web Services (AWS) Lambda&lt;/a&gt; is a usage-based
computing infrastructure service that can execute 
&lt;a href="/why-use-python.html"&gt;Python 3&lt;/a&gt; code. One of the challenges of this 
environment is ensuring efficient performance of your Lambda Functions. 
Application performance monitoring (APM) is particularly useful in these
situations because you are billed based on how long you use the
resources.&lt;/p&gt;
&lt;p&gt;In this post we will install and configure 
&lt;a href="https://sentry.io/for/performance/"&gt;Sentry's APM&lt;/a&gt; that works via a
&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/invocation-layers.html"&gt;Lambda layer&lt;/a&gt;.
Note that if you are looking for error monitoring rather than performance
monitoring, take a look at 
&lt;a href="/blog/monitor-python-functions-aws-lambda-sentry.html"&gt;How to Monitor Python Functions on AWS Lambda with Sentry&lt;/a&gt;
rather than following this post.&lt;/p&gt;
&lt;h2&gt;First steps with AWS Lambda&lt;/h2&gt;
&lt;p&gt;A local &lt;a href="/development-environments.html"&gt;development environment&lt;/a&gt; is not
required to follow this tutorial because all of the coding and configuration
can happen in a web browser through the 
&lt;a href="https://console.aws.amazon.com/console/"&gt;AWS Console&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://aws.amazon.com/console"&gt;Sign into your existing AWS account&lt;/a&gt;
or sign up for a &lt;a href="https://aws.amazon.com/"&gt;new account&lt;/a&gt;. Lambda
gives you the first 1 million requests for free so that you can execute 
basic applications without no or low cost.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/aws-lambda-landing.jpg" width="100%" class="shot rnd outl" alt="The AWS Lambda landing page."&gt;&lt;/p&gt;
&lt;p&gt;When you log into your account, use the search box to enter
"lambda" and select "Lambda" when it appears to get to the right
page.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/lambda-search-bar.png" width="100%" class="shot rnd outl" alt="Use the search bar to find AWS Lambda."&gt;&lt;/p&gt;
&lt;p&gt;If you have already used Lambda before, you will see your existing Lambda 
functions in a searchable table. We're going to create a new function so
click the "Create function" button.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/create-function.png" width="100%" class="shot rnd outl" alt="Click the create function button."&gt;&lt;/p&gt;
&lt;p&gt;The create function page will give you several options for building a
Lambda function.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/create-function-detail.png" width="100%" class="shot rnd outl" alt="The create function details page."&gt;&lt;/p&gt;
&lt;p&gt;Click the "Browse Serverless App Repository" selection box, then choose
the "hello-world-python3" starter app from within the 
"Public applications" section.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/create-function-detail.png" width="100%" class="shot rnd outl" alt="The create function details page."&gt;&lt;/p&gt;
&lt;p&gt;The hello-world-python3 starter app details page should look something
like the following screen:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/hello-world-python3.png" width="100%" class="shot rnd outl" alt="Hello world Python3 example app and Lambda function."&gt;&lt;/p&gt;
&lt;p&gt;Fill in some example text such as "test" under &lt;code&gt;IdentityNameParameter&lt;/code&gt;
and click the "Deploy" button:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/deploy-starter-app.png" width="100%" class="shot rnd outl" alt="Click the deploy button to use the starter app."&gt;&lt;/p&gt;
&lt;p&gt;The function will now be deployed. As soon as it is ready we can
customize it and test it out before adding Sentry to capture any errors
that occur during execution.&lt;/p&gt;
&lt;p&gt;Go back to the Lambda functions main page and select your new deployed
starter app from the list.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/functions-list.jpg" width="100%" class="shot rnd outl" alt="List of AWS Lambda functions you have created."&gt;&lt;/p&gt;
&lt;p&gt;Find the orange "Test" button with a down arrow next to it like you
see in the image below, and then click the down arrow. Select
"Configure Test Event".&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/configure-test.jpg" width="100%" class="shot rnd outl" alt="Configure the test event."&gt;&lt;/p&gt;
&lt;p&gt;Fill in the Event name as "FirstTest" or something similar, then
press the "Create" button at the bottom of the modal window.&lt;/p&gt;
&lt;p&gt;Click the "Test" button and it will run the Lambda function with
the parameters from that new test event. You should see something
like the following output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;value1&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;Function&lt;/span&gt; &lt;span class="n"&gt;Logs&lt;/span&gt;
&lt;span class="n"&gt;START&lt;/span&gt; &lt;span class="n"&gt;RequestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;62&lt;/span&gt;&lt;span class="n"&gt;fa2f25&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;669&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;47&lt;/span&gt;&lt;span class="n"&gt;b7&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;b4e7&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;47353&lt;/span&gt;&lt;span class="n"&gt;b0bd914&lt;/span&gt; &lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;LATEST&lt;/span&gt;
&lt;span class="n"&gt;value1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value1&lt;/span&gt;
&lt;span class="n"&gt;value2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value2&lt;/span&gt;
&lt;span class="n"&gt;value3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value3&lt;/span&gt;
&lt;span class="n"&gt;END&lt;/span&gt; &lt;span class="n"&gt;RequestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;62&lt;/span&gt;&lt;span class="n"&gt;fa2f25&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;669&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;47&lt;/span&gt;&lt;span class="n"&gt;b7&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;b4e7&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;47353&lt;/span&gt;&lt;span class="n"&gt;b0bd914&lt;/span&gt;
&lt;span class="n"&gt;REPORT&lt;/span&gt; &lt;span class="n"&gt;RequestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;62&lt;/span&gt;&lt;span class="n"&gt;fa2f25&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;669&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;47&lt;/span&gt;&lt;span class="n"&gt;b7&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;b4e7&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;47353&lt;/span&gt;&lt;span class="n"&gt;b0bd914&lt;/span&gt;  &lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.30&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt;   &lt;span class="n"&gt;Billed&lt;/span&gt; &lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt;   &lt;span class="n"&gt;Memory&lt;/span&gt; &lt;span class="n"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt; &lt;span class="n"&gt;MB&lt;/span&gt; &lt;span class="n"&gt;Max&lt;/span&gt; &lt;span class="n"&gt;Memory&lt;/span&gt; &lt;span class="n"&gt;Used&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;43&lt;/span&gt; &lt;span class="n"&gt;MB&lt;/span&gt;  &lt;span class="n"&gt;Init&lt;/span&gt; &lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.34&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt;

&lt;span class="n"&gt;Request&lt;/span&gt; &lt;span class="n"&gt;ID&lt;/span&gt;
&lt;span class="mi"&gt;62&lt;/span&gt;&lt;span class="n"&gt;fa2f25&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;669&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;47&lt;/span&gt;&lt;span class="n"&gt;b7&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;b4e7&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;47353&lt;/span&gt;&lt;span class="n"&gt;b0bd914&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The code was successfully executed, so let's add Sentry's performance
monitoring and test some code that uses it.&lt;/p&gt;
&lt;h2&gt;Performance monitoring with Sentry&lt;/h2&gt;
&lt;p&gt;Go to &lt;a href="https://sentry.io"&gt;Sentry.io's homepage&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/sentry-homepage.jpg" width="100%" class="shot rnd outl" alt="Sentry.io homepage where you can sign up for a free account."&gt;&lt;/p&gt;
&lt;p&gt;Sign into your account or sign up for a new free account. You will be at
the main account dashboard after logging in or completing the Sentry sign
up process.&lt;/p&gt;
&lt;p&gt;Select "Performance" on the left navigation bar, it will take you to the
performance monitoring page.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210823-sentry-apm-lambda/performance.jpg" width="100%" class="shot rnd outl" alt="Click the 'performance' button on the left side nav."&gt;&lt;/p&gt;
&lt;p&gt;Click "Start Setup" then go back over to AWS Lambda to complete the
steps for adding Sentry's Python layer to your Lambda function.&lt;/p&gt;
&lt;p&gt;The easiest way to add Sentry to Lambda for this application
is to configure an 
&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html"&gt;AWS Lambda Layer&lt;/a&gt;
with the necessary dependency for Sentry. Sentry has concise
&lt;a href="https://docs.sentry.io/platforms/python/guides/aws-lambda/layer/"&gt;documentation on adding via Lambda Layers&lt;/a&gt;
so we will walk through that way to configure it and test it
out.&lt;/p&gt;
&lt;p&gt;Scroll down to the "Layers" section while in your Lambda
function configuration. Click the "Add a layer" button":&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/add-lambda-layer.png" width="100%" class="shot rnd outl" alt="Add Lambda layer."&gt;&lt;/p&gt;
&lt;p&gt;In the "Add layer" screen, select the "Specify an ARN" option.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/add-layer-specify-arn.jpg" width="100%" class="shot rnd outl" alt="Select Specify ARN in the Add Layer screen."&gt;&lt;/p&gt;
&lt;p&gt;Now to specify the Amazon Resource Name (ARN), we need to use
the Sentry documentation to get the right configuration string.&lt;/p&gt;
&lt;p&gt;US-East-1 is the oldest and most commonly-used region so I'll
use that here in this tutorial but you should check which one
you are in if you are not certain.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210823-sentry-apm-lambda/arn-region.png" width="100%" class="shot rnd outl" alt="Select the AWS for the ARN string."&gt;&lt;/p&gt;
&lt;p&gt;Copy that value into the Lambda Layer configuration, like this:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210823-sentry-apm-lambda/layer-with-arn.png" width="100%" class="shot rnd outl" alt="Select the AWS for the ARN string."&gt;&lt;/p&gt;
&lt;p&gt;Then press the "Add" button. You now have the Sentry dependency
in your environment so code that relies upon that library can be 
used in the Lambda function.&lt;/p&gt;
&lt;h2&gt;Testing performance monitoring&lt;/h2&gt;
&lt;p&gt;Let's change our Python code in the Lambda function and test out
the APM agent.&lt;/p&gt;
&lt;p&gt;Make sure you are signed into your Sentry account and go to 
&lt;a href="https://docs.sentry.io/platforms/python/guides/aws-lambda/"&gt;this specific AWS Lambda set up guide&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You will see a "DSN string" that we need to set as an environment
variable on AWS Lambda to finish our setup. Copy the string that
matches your project as shown on that page in the highlighted green
section:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210823-sentry-apm-lambda//sentry-dsn-string.png" width="100%" class="shot rnd outl" alt="Copy the Sentry DSN string so we can export it as an environment variable."&gt;&lt;/p&gt;
&lt;p&gt;We will
&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html"&gt;use environment variables on AWS Lambda&lt;/a&gt; 
to store and access values like this Sentry DSN key.&lt;/p&gt;
&lt;p&gt;Go into the Lambda console to create a new environment variable. To do 
that, click the "Configuration" tab within Lambda like you see here:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/aws-lambda-configuration.jpg" width="100%" class="shot rnd outl" alt="Click the Lambda Configuration tab."&gt;&lt;/p&gt;
&lt;p&gt;Then click "Edit" and add a new environment variable with the key of &lt;code&gt;SENTRY_DSN&lt;/code&gt;
and the value of the DSN string that you copied from the Sentry screen. &lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/add-env-var.jpg" width="100%" class="shot rnd outl" alt="Add the environment variable in AWS Lambda."&gt;&lt;/p&gt;
&lt;p&gt;Click the "Save" button and go back to your Lambda function's code editor.&lt;/p&gt;
&lt;p&gt;Replace the code in your Lambda function with the following code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sentry_sdk&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sentry_sdk.integrations.aws_lambda&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AwsLambdaIntegration&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sentry_sdk&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;start_transaction&lt;/span&gt;

&lt;span class="n"&gt;SENTRY_DSN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SENTRY_DSN&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sentry_sdk&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;dsn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SENTRY_DSN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;traces_sample_rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;integrations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;AwsLambdaIntegration&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Loading function&amp;#39;&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;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;calc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;

    &lt;span class="c1"&gt;# this is custom instrumentation, see docs: https://bit.ly/2WjT3AY&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;start_transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;task&amp;quot;&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="s2"&gt;&amp;quot;big calculation&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;calc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;

    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calc&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;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;key1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# Echo back the first key value&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The above code imports the Sentry dependencies, and then runs both
&lt;a href="https://docs.sentry.io/platforms/python/guides/aws-lambda/performance/instrumentation/automatic-instrumentation/"&gt;automatic instrumentation&lt;/a&gt; 
and &lt;a href="https://bit.ly/2WjT3AY"&gt;custom instrumentation&lt;/a&gt; on the
code. Click the "Deploy" button and then "Test". The code will 
successfully execute and when we go back to our Sentry performance
monitoring dashboard we will see some initial results, like this
following screenshot.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210823-sentry-apm-lambda/performance-results.jpg" width="100%" class="shot rnd outl" alt="APM results shown in the Sentry dashboard."&gt;&lt;/p&gt;
&lt;p&gt;Looks good, you have both the default and the specified transaction 
performance recordings in the dashboard, and you can toggle between
them (or other transactions you record) through the user interface.&lt;/p&gt;
&lt;h2&gt;What's Next?&lt;/h2&gt;
&lt;p&gt;We just wrote and executed a Python 3 function on AWS Lambda that
used the basics of Sentry APM to get some initial performance
monitoring data.&lt;/p&gt;
&lt;p&gt;Check out the &lt;a href="/aws-lambda.html"&gt;AWS Lambda section&lt;/a&gt; for 
more tutorials by other developers.&lt;/p&gt;
&lt;p&gt;Further questions? Contact me on Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;. I am also on GitHub with
the username &lt;a href="https://github.com/mattmakai"&gt;mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Something wrong with this post? Fork 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/210823-performance-monitoring-aws-lambda-sentry.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Thu, 26 Aug 2021 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2021-08-23:blog/application-performance-monitoring-aws-lambda-functions-sentry.html</guid></item><item><title>How to Monitor Python Functions on AWS Lambda with Sentry</title><link>https://www.fullstackpython.com/blog/monitor-python-functions-aws-lambda-sentry.html</link><description>&lt;p&gt;&lt;a href="/aws-lambda.html"&gt;Amazon Web Services (AWS) Lambda&lt;/a&gt; is a usage-based
compute service that can run &lt;a href="/why-use-python.html"&gt;Python 3&lt;/a&gt; code. Errors 
can happen in any environment you are running your application in, so 
it is necessary to have reliable &lt;a href="/monitoring.html"&gt;monitoring&lt;/a&gt; in place 
to have visibility when a problem occurs.&lt;/p&gt;
&lt;p&gt;In this post we will install and configure 
&lt;a href="https://sentry.io/welcome/"&gt;Sentry&lt;/a&gt;'s application monitoring 
service that works specifically for code running on AWS Lambda.&lt;/p&gt;
&lt;h2&gt;Application Dependencies&lt;/h2&gt;
&lt;p&gt;A local &lt;a href="/development-environments.html"&gt;development environment&lt;/a&gt; is not
required to follow this tutorial because all of the coding and configuration
can happen in a web browser through the 
&lt;a href="https://console.aws.amazon.com/console/"&gt;AWS Console&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The example code can be copy and pasted from this blog post or you
can access it on GitHub under the
&lt;a href="https://github.com/fullstackpython/blog-code-examples"&gt;Full Stack Python blog-post-examples&lt;/a&gt;
repository within the 
&lt;a href="https://github.com/fullstackpython/blog-code-examples/tree/master/monitor-python-aws-lambda-sentry"&gt;monitor-python-aws-lambda-sentry directory&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Accessing the AWS Lambda Service&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://aws.amazon.com/console"&gt;Sign into your existing AWS account&lt;/a&gt;
or sign up for a &lt;a href="https://aws.amazon.com/"&gt;new account&lt;/a&gt;. Lambda
gives you the first 1 million requests for free so that you can execute 
basic applications without no or low cost.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/aws-lambda-landing.jpg" width="100%" class="shot rnd outl" alt="The AWS Lambda landing page."&gt;&lt;/p&gt;
&lt;p&gt;When you log into your account, use the search box to enter
"lambda" and select "Lambda" when it appears to get to the right
page.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/lambda-search-bar.png" width="100%" class="shot rnd outl" alt="Use the search bar to find AWS Lambda."&gt;&lt;/p&gt;
&lt;p&gt;If you have already used Lambda before, you will see your existing Lambda 
functions in a searchable table. We're going to create a new function so
click the "Create function" button.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/create-function.png" width="100%" class="shot rnd outl" alt="Click the create function button."&gt;&lt;/p&gt;
&lt;p&gt;The create function page will give you several options for starting a new
Lambda function.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/create-function-detail.png" width="100%" class="shot rnd outl" alt="The create function details page."&gt;&lt;/p&gt;
&lt;p&gt;Click the "Browse Serverless App Repository" selection box, then choose
the "hello-world-python3" starter app from within the 
"Public applications" section.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/create-function-detail.png" width="100%" class="shot rnd outl" alt="The create function details page."&gt;&lt;/p&gt;
&lt;p&gt;The hello-world-python3 starter app details page should look something
like the following screen:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/hello-world-python3.png" width="100%" class="shot rnd outl" alt="Hello world Python3 example app and Lambda function."&gt;&lt;/p&gt;
&lt;p&gt;Fill in some example text such as "test" under &lt;code&gt;IdentityNameParameter&lt;/code&gt;
and click the "Deploy" button:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/deploy-starter-app.png" width="100%" class="shot rnd outl" alt="Click the deploy button to use the starter app."&gt;&lt;/p&gt;
&lt;p&gt;The function will now be deployed. As soon as it is ready we can
customize it and test it out before adding Sentry to capture any errors
that occur during execution.&lt;/p&gt;
&lt;h2&gt;Testing the starter Python app&lt;/h2&gt;
&lt;p&gt;Go back to the Lambda functions main page and select your new deployed
starter app from the list.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/functions-list.jpg" width="100%" class="shot rnd outl" alt="List of AWS Lambda functions you have created."&gt;&lt;/p&gt;
&lt;p&gt;Find the orange "Test" button with a down arrow next to it like you
see in the image below, and then click the down arrow. Select
"Configure Test Event".&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/configure-test.jpg" width="100%" class="shot rnd outl" alt="Configure the test event."&gt;&lt;/p&gt;
&lt;p&gt;Fill in the Event name as "FirstTest" or something similar, then
press the "Create" button at the bottom of the modal window.&lt;/p&gt;
&lt;p&gt;Click the "Test" button and it will run the Lambda function with
the parameters from that new test event. You should see something
like the following output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;value1&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;Function&lt;/span&gt; &lt;span class="n"&gt;Logs&lt;/span&gt;
&lt;span class="n"&gt;START&lt;/span&gt; &lt;span class="n"&gt;RequestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;62&lt;/span&gt;&lt;span class="n"&gt;fa2f25&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;669&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;47&lt;/span&gt;&lt;span class="n"&gt;b7&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;b4e7&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;47353&lt;/span&gt;&lt;span class="n"&gt;b0bd914&lt;/span&gt; &lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;LATEST&lt;/span&gt;
&lt;span class="n"&gt;value1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value1&lt;/span&gt;
&lt;span class="n"&gt;value2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value2&lt;/span&gt;
&lt;span class="n"&gt;value3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value3&lt;/span&gt;
&lt;span class="n"&gt;END&lt;/span&gt; &lt;span class="n"&gt;RequestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;62&lt;/span&gt;&lt;span class="n"&gt;fa2f25&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;669&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;47&lt;/span&gt;&lt;span class="n"&gt;b7&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;b4e7&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;47353&lt;/span&gt;&lt;span class="n"&gt;b0bd914&lt;/span&gt;
&lt;span class="n"&gt;REPORT&lt;/span&gt; &lt;span class="n"&gt;RequestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;62&lt;/span&gt;&lt;span class="n"&gt;fa2f25&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;669&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;47&lt;/span&gt;&lt;span class="n"&gt;b7&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;b4e7&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;47353&lt;/span&gt;&lt;span class="n"&gt;b0bd914&lt;/span&gt;  &lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.30&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt;   &lt;span class="n"&gt;Billed&lt;/span&gt; &lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt;   &lt;span class="n"&gt;Memory&lt;/span&gt; &lt;span class="n"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt; &lt;span class="n"&gt;MB&lt;/span&gt; &lt;span class="n"&gt;Max&lt;/span&gt; &lt;span class="n"&gt;Memory&lt;/span&gt; &lt;span class="n"&gt;Used&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;43&lt;/span&gt; &lt;span class="n"&gt;MB&lt;/span&gt;  &lt;span class="n"&gt;Init&lt;/span&gt; &lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.34&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt;

&lt;span class="n"&gt;Request&lt;/span&gt; &lt;span class="n"&gt;ID&lt;/span&gt;
&lt;span class="mi"&gt;62&lt;/span&gt;&lt;span class="n"&gt;fa2f25&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;669&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;47&lt;/span&gt;&lt;span class="n"&gt;b7&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;b4e7&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;47353&lt;/span&gt;&lt;span class="n"&gt;b0bd914&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;That means the test case was successful, but what happens even if there
is a straightforward mistake in the code, such as trying to access an
undeclared variable?&lt;/p&gt;
&lt;p&gt;Go into the code editor and you should see the starter code like this:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/lambda-code-editor.jpg" width="100%" class="shot rnd outl" alt="Code editor within AWS Lambda."&gt;&lt;/p&gt;
&lt;p&gt;Update the code with the new highlighted line, which tries to access
a fourth variable, which does not exist in the test configuration
we try to run it with.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Loading function&amp;#39;&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;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;#print(&amp;quot;Received event: &amp;quot; + json.dumps(event, indent=2))&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;value1 = &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;key1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;value2 = &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;key2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;value3 = &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;key3&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;value4 = &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;key4&amp;#39;&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;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;key1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# Echo back the first key value&lt;/span&gt;
    &lt;span class="c1"&gt;#raise Exception(&amp;#39;Something went wrong&amp;#39;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;After adding that one new line of code, hit the "Deploy" button,
then the "Test" button. You should see some error output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;&amp;quot;errorMessage&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;#39;key4&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;&amp;quot;errorType&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;KeyError&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;&amp;quot;stackTrace&amp;quot;&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="s2"&gt;&amp;quot;/var/task/lambda_function.py&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;&amp;quot;lambda_handler&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;&amp;quot;print(&lt;/span&gt;&lt;span class="se"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="s2"&gt;value4 = &lt;/span&gt;&lt;span class="se"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="s2"&gt; + event[&amp;#39;key4&amp;#39;])&amp;quot;&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;Function&lt;/span&gt; &lt;span class="n"&gt;Logs&lt;/span&gt;
&lt;span class="n"&gt;START&lt;/span&gt; &lt;span class="n"&gt;RequestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;a4e956bd&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;cce4&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;b5e7&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;e95bc3ffa2cb&lt;/span&gt; &lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;LATEST&lt;/span&gt;
&lt;span class="n"&gt;value1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value1&lt;/span&gt;
&lt;span class="n"&gt;value2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value2&lt;/span&gt;
&lt;span class="n"&gt;value3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value3&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;key4&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;KeyError&lt;/span&gt;
&lt;span class="n"&gt;Traceback&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;most&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/var/task/lambda_function.py&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;lambda_handler&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;value4 = &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;key4&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;KeyError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;key4&amp;#39;&lt;/span&gt;

&lt;span class="n"&gt;END&lt;/span&gt; &lt;span class="n"&gt;RequestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;a4e956bd&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;cce4&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;b5e7&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;e95bc3ffa2cb&lt;/span&gt;
&lt;span class="n"&gt;REPORT&lt;/span&gt; &lt;span class="n"&gt;RequestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;a4e956bd&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;cce4&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;b5e7&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;e95bc3ffa2cb&lt;/span&gt;  &lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.81&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt;   &lt;span class="n"&gt;Billed&lt;/span&gt; &lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt;   &lt;span class="n"&gt;Memory&lt;/span&gt; &lt;span class="n"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt; &lt;span class="n"&gt;MB&lt;/span&gt; &lt;span class="n"&gt;Max&lt;/span&gt; &lt;span class="n"&gt;Memory&lt;/span&gt; &lt;span class="n"&gt;Used&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;43&lt;/span&gt; &lt;span class="n"&gt;MB&lt;/span&gt;  &lt;span class="n"&gt;Init&lt;/span&gt; &lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.61&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt;

&lt;span class="n"&gt;Request&lt;/span&gt; &lt;span class="n"&gt;ID&lt;/span&gt;
&lt;span class="n"&gt;a4e956bd&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;cce4&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;b5e7&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;e95bc3ffa2cb&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;It is obvious when we are working in the Console that an error just
occurred. However, in most cases an error will happen sporadically 
which is why we need a monitoring system in place to catch and report
on those exceptions.&lt;/p&gt;
&lt;h2&gt;AWS Lambda function monitoring with Sentry&lt;/h2&gt;
&lt;p&gt;The easiest way to add Sentry to Lambda for this application
is to configure an 
&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html"&gt;AWS Lambda Layer&lt;/a&gt;
with the necessary dependency for Sentry. Sentry has concise
&lt;a href="https://docs.sentry.io/platforms/python/guides/aws-lambda/layer/"&gt;documentation on addin gvia Lambda Layers&lt;/a&gt;
so we will walk through that way to configure it and test it
out.&lt;/p&gt;
&lt;p&gt;First, scroll down to the "Layers" section while in your Lambda
function configuration. Click the "Add a layer" button":&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/add-lambda-layer.png" width="100%" class="shot rnd outl" alt="Add Lambda layer."&gt;&lt;/p&gt;
&lt;p&gt;In the "Add layer" screen, select the "Specify an ARN" option.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/add-layer-specify-arn.jpg" width="100%" class="shot rnd outl" alt="Select Specify ARN in the Add Layer screen."&gt;&lt;/p&gt;
&lt;p&gt;Now to specify the Amazon Resource Name (ARN), we need to use
the Sentry documentation to get the right configuration string.&lt;/p&gt;
&lt;p&gt;US-East-1 is the oldest and most commonly-used region so I'll
use that here in this tutorial but you should check which one
you are in if you are not certain.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/arn-region.png" width="100%" class="shot rnd outl" alt="Select the AWS for the ARN string."&gt;&lt;/p&gt;
&lt;p&gt;Copy that value into the Lambda Layer configuration, like this:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/layer-with-arn.png" width="100%" class="shot rnd outl" alt="Select the AWS for the ARN string."&gt;&lt;/p&gt;
&lt;p&gt;Then press the "Add" button. Now you have the Sentry dependency
in your environment so code that relies upon that library can be 
used in the Lambda function.&lt;/p&gt;
&lt;p&gt;Next we need to go into the Sentry dashboard to create a project,
get our unique identifer, and connect it to our Lambda function.&lt;/p&gt;
&lt;p&gt;Sentry can be &lt;a href="https://github.com/getsentry/onpremise"&gt;self-hosted&lt;/a&gt; or
used as a cloud service through &lt;a href="https://sentry.io"&gt;Sentry.io&lt;/a&gt;. We will
use the cloud hosted version because it is quicker than
setting up your own server as well as free for smaller projects.&lt;/p&gt;
&lt;p&gt;Go to &lt;a href="https://sentry.io"&gt;Sentry.io's homepage&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/sentry-homepage.jpg" width="100%" class="shot rnd outl" alt="Sentry.io homepage where you can sign up for a free account."&gt;&lt;/p&gt;
&lt;p&gt;Sign into your account or sign up for a new free account. You will be at
the main account dashboard after logging in or completing the Sentry sign
up process.&lt;/p&gt;
&lt;p&gt;There are no errors logged on our account dashboard yet, which is as
expected because we have not yet connected our account to our Lambda
function.&lt;/p&gt;
&lt;p&gt;Click "Projects" on the left navigation bar, then "Create Project"
in the top right corner.&lt;/p&gt;
&lt;p&gt;Under "Choose a Platform", select "Serverless" and then "AWS Lambda (Python)"
as shown below:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/aws-lambda-python.jpg" width="100%" class="shot rnd outl" alt="Choose AWS Lambda (Python) under the platform options."&gt;&lt;/p&gt;
&lt;p&gt;Decide under what criteria it should send error information out of
Lambda. For this tutorial, we will have it send every exception.
Then click the "Create Project." button.&lt;/p&gt;
&lt;p&gt;You can have Sentry handle the instrumentation automatically but
we will handle it manually for our function. On the next screen, Sentry 
will provide you with your unique DSN string, which we will need for 
our function.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/sentry-dsn-string.jpg" width="100%" class="shot rnd outl" alt="Copy the Sentry DSN string so we can export it as an environment variable."&gt;&lt;/p&gt;
&lt;p&gt;Typically you will want to 
&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html"&gt;use environment variables on AWS Lambda&lt;/a&gt; 
to store and access values like your Sentry key.&lt;/p&gt;
&lt;p&gt;Copy the contents of the Sentry DSN string, and go into the Lambda console
to create a new environment variable. To do that, click the "Configuration"
tab within Lambda like you see here:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/aws-lambda-configuration.jpg" width="100%" class="shot rnd outl" alt="Click the Lambda Configuration tab."&gt;&lt;/p&gt;
&lt;p&gt;Then click "Edit" and add a new environment variable with the key of &lt;code&gt;SENTRY_DSN&lt;/code&gt;
and the value of the DSN string that you copied from the Sentry screen. &lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/add-env-var.jpg" width="100%" class="shot rnd outl" alt="Add the environment variable in AWS Lambda."&gt;&lt;/p&gt;
&lt;p&gt;Click the "Save" button and go back to your Lambda function code.&lt;/p&gt;
&lt;p&gt;Update your Lambda function with the following highlighted new lines of code
to send errors to Sentry.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sentry_sdk&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sentry_sdk.integrations.aws_lambda&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AwsLambdaIntegration&lt;/span&gt;

&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="n"&gt;SENTRY_DSN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SENTRY_DSN&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="n"&gt;sentry_sdk&lt;/span&gt;&lt;span class="o"&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;dsn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SENTRY_DSN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="n"&gt;integrations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;AwsLambdaIntegration&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Loading function&amp;#39;&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;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;#print(&amp;quot;Received event: &amp;quot; + json.dumps(event, indent=2))&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;value1 = &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;key1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;value2 = &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;key2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;value3 = &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;key3&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;value4 = &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;key4&amp;#39;&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;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;key1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# Echo back the first key value&lt;/span&gt;
    &lt;span class="c1"&gt;#raise Exception(&amp;#39;Something went wrong&amp;#39;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Click the "Deploy" button and then "Test". The code will throw
an error and when we go back to our Sentry dashboard we will
see it captured and viewable for further inspection.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210406-python-sentry-aws-lambda/sentry-error-dashboard.jpg" width="100%" class="shot rnd outl" alt="AWS Lambda exception in the Sentry dashboard."&gt;&lt;/p&gt;
&lt;p&gt;It works! Next you will likely want to tune your exception reporting
criteria to make sure you get alerted to the right number of exceptions
if you do not want to see all of them.&lt;/p&gt;
&lt;h2&gt;What's Next?&lt;/h2&gt;
&lt;p&gt;We just wrote and executed a Python 3 function on AWS Lambda then
captured the exception message into the Sentry logs. You can
now continue building out your Python code knowing that when something
goes wrong you will have full visibility on what happened.&lt;/p&gt;
&lt;p&gt;Check out the &lt;a href="/aws-lambda.html"&gt;AWS Lambda section&lt;/a&gt; for 
more tutorials by other developers.&lt;/p&gt;
&lt;p&gt;Further questions? Contact me on Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;. I am also on GitHub with
the username &lt;a href="https://github.com/mattmakai"&gt;mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Something wrong with this post? Fork 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/210422-monitor-python-aws-lambda-sentry.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Fri, 23 Apr 2021 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2021-04-22:blog/monitor-python-functions-aws-lambda-sentry.html</guid></item><item><title>Using Django &amp; AssemblyAI for More Accurate Twilio Call Transcriptions</title><link>https://www.fullstackpython.com/blog/django-accurate-twilio-voice-transcriptions.html</link><description>&lt;p&gt;&lt;a href="https://www.twilio.com/docs/voice/tutorials/how-to-record-phone-calls-python"&gt;Recording phone calls&lt;/a&gt;
with one or more participants is easy with 
&lt;a href="https://www.twilio.com/docs/voice/quickstart/python"&gt;Twilio's Programmable Voice API&lt;/a&gt;,
but the speech-to-text accuracy can be poor, especially for transcription 
of words from niche domains such as healthcare and engineering.
&lt;a href="https://www.assemblyai.com/"&gt;AssemblyAI's API for transcription&lt;/a&gt;
provides much higher accuracy by default and through optional keyword lists.
accuracy for &lt;a href="https://www.twilio.com/docs/voice/api/recording"&gt;recordings&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;In this tutorial, we'll record an outbound Twilio call recording to AssemblyAI's
API to get significantly more accurate speech-to-text output.&lt;/p&gt;
&lt;h2&gt;Tutorial Prerequisites&lt;/h2&gt;
&lt;p&gt;Ensure you have Python 3 installed, because Python 2 reached its
end-of-life at the beginning of 2020 and is no longer supported.
Preferrably, you should have
&lt;a href="https://www.python.org/downloads/"&gt;Python 3.7 or greater installed&lt;/a&gt;
in your &lt;a href="/development-environments.html"&gt;development environment&lt;/a&gt;.
This tutorial will also use:&lt;/p&gt;
&lt;p&gt;We will use the following dependencies to complete this
tutorial:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/django.html"&gt;Django&lt;/a&gt; version 3.1.x, where &lt;em&gt;x&lt;/em&gt; is the latest security 
  release&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://www.twilio.com/referral/w9pugq"&gt;Twilio account&lt;/a&gt; and the 
  &lt;a href="https://pypi.org/project/twilio/"&gt;Python Twilio helper library&lt;/a&gt;
  version 6.45.2 or newer&lt;/li&gt;
&lt;li&gt;&lt;a href="https://requests.readthedocs.io/"&gt;requests&lt;/a&gt;
  &lt;a href="https://pypi.org/project/requests/"&gt;version 2.24.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;An &lt;a href="https://www.assemblyai.com/"&gt;AssemblyAI&lt;/a&gt; account, which you can sign up for a &lt;a href="https://app.assemblyai.com/login/"&gt;free key API access key here&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All code in this blog post is available open source under the MIT license
on GitHub under the
&lt;a href="https://github.com/fullstackpython/blog-code-examples"&gt;django-accurate-twilio-voice-transcriptions directory of the blog-code-examples repository&lt;/a&gt;.
Use the source code as you desire for your own projects.&lt;/p&gt;
&lt;h2&gt;Configuring our development environment&lt;/h2&gt;
&lt;p&gt;Change into the directory where you keep your Python
&lt;a href="/virtual-environments-virtualenvs-venvs.html"&gt;virtual environments&lt;/a&gt;.
Create a new virtualenv for this project using the following
command.&lt;/p&gt;
&lt;p&gt;Start the Django project by creating a new
&lt;a href="/virtual-environments-virtualenvs-venvs.html"&gt;virtual environment&lt;/a&gt;
using the following command. I recommend using a separate directory
such as &lt;code&gt;~/venvs/&lt;/code&gt; (the tilde is a shortcut for your user's &lt;code&gt;home&lt;/code&gt;
directory) so that you always know where all your virtualenvs are
located.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python3 -m venv ~/venvs/djtranscribe
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Activate the virtualenv with the &lt;code&gt;activate&lt;/code&gt; shell script:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt; ~/venvs/djtranscribe/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;After the above command is executed, the command prompt will
change so that the name of the virtualenv is prepended to the
original command prompt format, so if your prompt is just
&lt;code&gt;$&lt;/code&gt;, it will now look like the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;djtranscribe&lt;span class="o"&gt;)&lt;/span&gt; $
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Remember, you have to activate your virtualenv in every new terminal
window where you want to use dependencies in the virtualenv.&lt;/p&gt;
&lt;p&gt;We can now install the &lt;a href="https://pypi.org/project/Django/"&gt;Django&lt;/a&gt;
package into the activated but otherwise empty virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install django==3.1.3 requests==2.24.0 twilio==6.45.2
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Look for output similar to the following to confirm the appropriate
packages were installed correctly from PyPI.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;djtranscribe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt; &lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mf"&gt;3.1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mf"&gt;2.24&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;twilio&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;6.45&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt; &lt;span class="n"&gt;twilio&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;
  &lt;span class="n"&gt;Downloading&lt;/span&gt; &lt;span class="n"&gt;Django&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;7.8&lt;/span&gt; &lt;span class="n"&gt;MB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;████████████████████████████████&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mf"&gt;7.8&lt;/span&gt; &lt;span class="n"&gt;MB&lt;/span&gt; &lt;span class="mf"&gt;2.6&lt;/span&gt; &lt;span class="n"&gt;MB&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; 
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.24&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;61&lt;/span&gt; &lt;span class="n"&gt;kB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;twilio&lt;/span&gt;
  &lt;span class="n"&gt;Downloading&lt;/span&gt; &lt;span class="n"&gt;twilio&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;6.47&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;0.&lt;/span&gt;&lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gz&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;460&lt;/span&gt; &lt;span class="n"&gt;kB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;████████████████████████████████&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;460&lt;/span&gt; &lt;span class="n"&gt;kB&lt;/span&gt; &lt;span class="mf"&gt;19.6&lt;/span&gt; &lt;span class="n"&gt;MB&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; 
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;sqlparse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="n"&gt;Downloading&lt;/span&gt; &lt;span class="n"&gt;sqlparse&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt; &lt;span class="n"&gt;kB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;████████████████████████████████&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt; &lt;span class="n"&gt;kB&lt;/span&gt; &lt;span class="mf"&gt;4.8&lt;/span&gt; &lt;span class="n"&gt;MB&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; 
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;pytz&lt;/span&gt;
  &lt;span class="n"&gt;Downloading&lt;/span&gt; &lt;span class="n"&gt;pytz&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2020.4&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;509&lt;/span&gt; &lt;span class="n"&gt;kB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;████████████████████████████████&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;509&lt;/span&gt; &lt;span class="n"&gt;kB&lt;/span&gt; &lt;span class="mf"&gt;31.0&lt;/span&gt; &lt;span class="n"&gt;MB&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; 
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;asgiref&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;3.2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;
  &lt;span class="n"&gt;Downloading&lt;/span&gt; &lt;span class="n"&gt;asgiref&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt; &lt;span class="n"&gt;kB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;chardet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;chardet&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;133&lt;/span&gt; &lt;span class="n"&gt;kB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;idna&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;2.5&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;idna&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.10&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;58&lt;/span&gt; &lt;span class="n"&gt;kB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;certifi&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;2017.4&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;certifi&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2020.6&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;156&lt;/span&gt; &lt;span class="n"&gt;kB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;urllib3&lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="mf"&gt;1.25&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="o"&gt;!=&lt;/span&gt;&lt;span class="mf"&gt;1.25&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mf"&gt;1.26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.21&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="n"&gt;Downloading&lt;/span&gt; &lt;span class="n"&gt;urllib3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.25&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;127&lt;/span&gt; &lt;span class="n"&gt;kB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;████████████████████████████████&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;127&lt;/span&gt; &lt;span class="n"&gt;kB&lt;/span&gt; &lt;span class="mf"&gt;24.5&lt;/span&gt; &lt;span class="n"&gt;MB&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; 
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;six&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;six&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.15&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="n"&gt;kB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;PyJWT&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.4&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;PyJWT&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.7&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt; &lt;span class="n"&gt;kB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;legacy&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;setup.py install&amp;#39;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;twilio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;since&lt;/span&gt; &lt;span class="n"&gt;package&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;wheel&amp;#39;&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;installed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;Installing&lt;/span&gt; &lt;span class="n"&gt;collected&lt;/span&gt; &lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sqlparse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pytz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;asgiref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chardet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idna&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;certifi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urllib3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;six&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PyJWT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;twilio&lt;/span&gt;
    &lt;span class="n"&gt;Running&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;twilio&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt;
&lt;span class="n"&gt;Successfully&lt;/span&gt; &lt;span class="n"&gt;installed&lt;/span&gt; &lt;span class="n"&gt;PyJWT&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.7&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;asgiref&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;certifi&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2020.6&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="n"&gt;chardet&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="n"&gt;idna&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.10&lt;/span&gt; &lt;span class="n"&gt;pytz&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2020.4&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.24&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;six&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.15&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;sqlparse&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;twilio&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;6.47&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;urllib3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.25&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We can get started coding the application now that we have all of our
required dependencies installed.&lt;/p&gt;
&lt;h2&gt;Starting our Django project&lt;/h2&gt;
&lt;p&gt;Let's begin coding our application.&lt;/p&gt;
&lt;p&gt;We can use the &lt;a href="/django.html"&gt;Django&lt;/a&gt; &lt;code&gt;django-admin&lt;/code&gt; tool to create
the boilerplate code structure to get our project started.
Change into the directory where you develop your applications. For
example, I typically use &lt;code&gt;/Users/matt/devel/py/&lt;/code&gt; for all of my
Python projects. Then run the following command to start a Django
project named &lt;code&gt;djtranscribe&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;django-admin.py startproject djtranscribe
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Note that in this tutorial we are using the same name for both the
virtualenv and the Django project directory, but they can be
different names if you prefer that for organizing your own projects.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;django-admin&lt;/code&gt; command creates a directory named &lt;code&gt;djtranscribe&lt;/code&gt;
along with several subdirectories that you should be familiar with
if you have previously worked with Django.&lt;/p&gt;
&lt;p&gt;Change directories into the new project.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;cd djtranscribe
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Create a new Django app within &lt;code&gt;djtranscribe&lt;/code&gt; named &lt;code&gt;caller&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python manage.py startapp caller
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Django will generate a new folder named &lt;code&gt;caller&lt;/code&gt; in the project.
We should update the URLs so the app is accessible before we write
our &lt;code&gt;views.py&lt;/code&gt; code.&lt;/p&gt;
&lt;p&gt;Open &lt;code&gt;djtranscribe/djtranscribe/urls.py&lt;/code&gt;. Add the highlighted
lines so that URL resolver will check the &lt;code&gt;caller&lt;/code&gt; app
for additional routes to match with URLs that are requested of
this Django application.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# djtranscribe/djtranscribe/urls.py&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;include&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;


&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;caller.urls&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;admin/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Save &lt;code&gt;djtranscribe/djtranscribe/urls.py&lt;/code&gt; and open
&lt;code&gt;djtranscribe/djtranscribe/settings.py&lt;/code&gt;.
Add the &lt;code&gt;caller&lt;/code&gt; app to &lt;code&gt;settings.py&lt;/code&gt; by inserting
the highlighted line:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# djtranscribe/djtranscribe/settings.py&lt;/span&gt;
&lt;span class="c1"&gt;# Application definition&lt;/span&gt;

&lt;span class="n"&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.admin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.auth&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.contenttypes&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.sessions&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.messages&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.staticfiles&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="s1"&gt;&amp;#39;caller&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Make sure you change the default &lt;code&gt;DEBUG&lt;/code&gt; and &lt;code&gt;SECRET_KEY&lt;/code&gt;
values in &lt;code&gt;settings.py&lt;/code&gt; before you deploy any code to production. Secure
your app properly with the information from the Django
&lt;a href="https://docs.djangoproject.com/en/stable/howto/deployment/checklist/"&gt;production deployment checklist&lt;/a&gt;
so that you do not add your project to the list of hacked applications
on the web.&lt;/p&gt;
&lt;p&gt;Save and close &lt;code&gt;settings.py&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next change into the &lt;code&gt;djtranscribe/caller&lt;/code&gt; directory. Create
a new file named &lt;code&gt;urls.py&lt;/code&gt; to contain routes for the &lt;code&gt;caller&lt;/code&gt; app.&lt;/p&gt;
&lt;p&gt;Add all of these lines to the empty &lt;code&gt;djtranscribe/caller/urls.py&lt;/code&gt;
file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# djtranscribe/caller/urls.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&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="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&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="s2"&gt;&amp;quot;index&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Save &lt;code&gt;djtranscribe/caller/urls.py&lt;/code&gt;. Open
&lt;code&gt;djtranscribe/caller/views.py&lt;/code&gt; to add the
following two highlighted lines. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# djtranscribe/caller/views.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.http&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;


&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&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;~~&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Hello, world!&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We can test out that this simple boilerplate response is
correct before we start adding the meat of the functionality to
the project. Change into the base directory of your Django project
where the &lt;code&gt;manage.py&lt;/code&gt; file is located. Execute the development
server with the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python manage.py runserver
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The Django development server should start up with no issues other than
an unapplied migrations warning.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;Watching&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="n"&gt;changes&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;StatReloader&lt;/span&gt;
&lt;span class="n"&gt;Performing&lt;/span&gt; &lt;span class="n"&gt;system&lt;/span&gt; &lt;span class="n"&gt;checks&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;System&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="n"&gt;identified&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;issues&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;silenced&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;

&lt;span class="n"&gt;November&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2020&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;07&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;03&lt;/span&gt;
&lt;span class="n"&gt;Django&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="mf"&gt;3.1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;using&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;djtranscribe.settings&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;Starting&lt;/span&gt; &lt;span class="n"&gt;development&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="mf"&gt;127.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="n"&gt;Quit&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;CONTROL&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Open a web browser and go to &lt;code&gt;localhost:8000&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/visuals/first-step.jpg" width="100%" class="shot rnd outl" alt="Web browser rendering simple text 'Hello, world!'."&gt;&lt;/p&gt;
&lt;p&gt;You should see 'Hello, world!' rendered in the browser.
That means everything is working properly so far and we can
now add the dialing, recording and transcribing capabilities to 
our Django project.&lt;/p&gt;
&lt;h2&gt;Adding Twilio to the Django project&lt;/h2&gt;
&lt;p&gt;Time to add Twilio's Voice API into the mix so we can dial
a phone call from our Django project and make a recording
out of it.&lt;/p&gt;
&lt;p&gt;Start by opening up &lt;code&gt;djtranscribe/djtranscribe/settings.py&lt;/code&gt;
and modifying it with the following highlighted &lt;code&gt;import os&lt;/code&gt;
line:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# djtranscribe/djtranscribe/settings.py&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;


&lt;span class="c1"&gt;# Build paths inside the project like this: BASE_DIR / &amp;#39;subdir&amp;#39;.&lt;/span&gt;
&lt;span class="n"&gt;BASE_DIR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Then at the bottom of the &lt;code&gt;settings.py&lt;/code&gt; file, add the
following highlighted lines, which will be settings that are pulled from
environment variables we will configure later.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Static files (CSS, JavaScript, Images)&lt;/span&gt;
&lt;span class="c1"&gt;# https://docs.djangoproject.com/en/3.1/howto/static-files/&lt;/span&gt;

&lt;span class="n"&gt;STATIC_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/static/&amp;#39;&lt;/span&gt;


&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="n"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;BASE_URL&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="n"&gt;TWIML_INSTRUCTIONS_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;/record/&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="n"&gt;TWILIO_PHONE_NUMBER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;TWILIO_PHONE_NUMBER&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Save &lt;code&gt;settings.py&lt;/code&gt; and change into the &lt;code&gt;caller&lt;/code&gt; Django app directory.&lt;/p&gt;
&lt;p&gt;Update &lt;code&gt;djtranscribe/caller/urls.py&lt;/code&gt; with the the following new
code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# djtranscribe/caller/urls.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; 
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; 
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;dial/(\d+)/$&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dial&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="s2"&gt;&amp;quot;dial&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;record/$&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;record_twiml&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="s2"&gt;&amp;quot;record-twiml&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;get-recording-url/([A-Za-z0-9]+)/$&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_recording_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;recording-url&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Next, open &lt;code&gt;djtranscribe/views.py&lt;/code&gt; and update it with the following
code, replacing what already exists within the file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# djtranscribe/caller/views.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.http&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.views.decorators.csrf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;csrf_exempt&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;twilio.rest&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;twilio.twiml.voice_response&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;VoiceResponse&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dial&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;phone_number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Dials an outbound phone call to the number in the URL. Just&lt;/span&gt;
&lt;span class="sd"&gt;    as a heads up you will never want to leave a URL like this exposed&lt;/span&gt;
&lt;span class="sd"&gt;    without authentication and further phone number format verification.&lt;/span&gt;
&lt;span class="sd"&gt;    phone_number should be just the digits with the country code first,&lt;/span&gt;
&lt;span class="sd"&gt;    for example 14155559812.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="c1"&gt;# pulls credentials from environment variables&lt;/span&gt;
    &lt;span class="n"&gt;twilio_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;twilio_client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calls&lt;/span&gt;&lt;span class="o"&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;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;+&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&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="n"&gt;from_&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TWILIO_PHONE_NUMBER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TWIML_INSTRUCTIONS_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;   
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sid&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;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dialing +&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;. call SID is: &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&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="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sid&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="nd"&gt;@csrf_exempt&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;record_twiml&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="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Returns TwiML which prompts the caller to record a message&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="c1"&gt;# Start our TwiML response&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VoiceResponse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Use &amp;lt;Say&amp;gt; to give the caller some instructions&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Ahoy! Call recording starts now.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Use &amp;lt;Record&amp;gt; to record the caller&amp;#39;s message&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# End the call with &amp;lt;Hangup&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hangup&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;HttpResponse&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;response&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;application/xml&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_recording_url&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;call_sid&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Returns an HttpResponse with plain text of the link to one or more&lt;/span&gt;
&lt;span class="sd"&gt;    recordings from the specified Call SID.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="c1"&gt;# pulls credentials from environment variables&lt;/span&gt;
    &lt;span class="n"&gt;twilio_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;recording_urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;twilio_client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call_sid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recordings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;recording_urls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;recording_urls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://api.twilio.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uri&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;HttpResponse&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;recording_urls&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Each of the above view functions performs one of the steps needed to 
create a call recording of a phone call dialed by Twilio, and then
retrieve it as a file. &lt;code&gt;dial&lt;/code&gt; programmatically initiates the outbound
call, &lt;code&gt;record_twiml&lt;/code&gt; returns instructions to play a message that the
call is being recorded, records it, and then hangs up when the call
is done. &lt;code&gt;get_recording_url&lt;/code&gt; only returns the URL location of the
recorded phone call so that in the next step we can send the file over
to AssemblyAI.&lt;/p&gt;
&lt;p&gt;Our Django project modifications are done. Next, we need to use
two services, Twilio and Ngrok, to enable some of the machine
to happen of phone calling and running the application from our
local machine.&lt;/p&gt;
&lt;h2&gt;Twilio credentials and environment variables&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.twilio.com/referral/w9pugq"&gt;Sign up for Twilio&lt;/a&gt; or 
&lt;a href="https://www.twilio.com/console"&gt;log into your existing account&lt;/a&gt;. 
Once you get to the &lt;a href="https://www.twilio.com/console"&gt;Twilio Console&lt;/a&gt;, 
you can obtain your &lt;code&gt;TWILIO_ACCOUNT_SID&lt;/code&gt; and &lt;code&gt;TWILIO_AUTH_TOKEN&lt;/code&gt; on the 
right side of the page:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210105-django-assemblyai/twilio-console.png" width="100%" alt="Twilio Console." class="shot rnd outl"&gt;&lt;/p&gt;
&lt;p&gt;When you sign up you should have a phone number assigned to your account.
You can use that or 
&lt;a href="https://www.twilio.com/console/phone-numbers/search"&gt;purchase a new phone number&lt;/a&gt;
to use.&lt;/p&gt;
&lt;p&gt;Set three environment variables with the names &lt;code&gt;TWILIO_ACCOUNT_SID&lt;/code&gt;,
&lt;code&gt;TWILIO_AUTH_TOKEN&lt;/code&gt;, and &lt;code&gt;TWILIO_PHONE_NUMBER&lt;/code&gt; using the &lt;code&gt;export&lt;/code&gt; command
in your terminal. Make sure to replace the values with your own Account SID,
Auth Token and Twilio phone number.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;TWILIO_ACCOUNT_SID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;xxxxxxxxxxxxx    &lt;span class="c1"&gt;# found in twilio.com/console&lt;/span&gt;
&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;TWILIO_AUTH_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;yyyyyyyyyyyyyy    &lt;span class="c1"&gt;# found in twilio.com/console&lt;/span&gt;
&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;TWILIO_PHONE_NUMBER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;+17166382453    &lt;span class="c1"&gt;# replace with your Twilio number&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Note that you must use the &lt;code&gt;export&lt;/code&gt; command in every command line window
that you want this key to be accessible. The scripts we are writing will
not be able to access the Twilio APIs if you do not have the tokens exported
in the environment where you are running the script.&lt;/p&gt;
&lt;p&gt;There is one more environment variable to set before we can run &lt;code&gt;app.py&lt;/code&gt;.
We need to use Ngrok as a localhost tunnel so that Twilio's webhook can
send an HTTP POST request to our Django application running on
our local development environment.&lt;/p&gt;
&lt;p&gt;Run Ngrok in a new terminal window, because you will need to keep it
running while we run our other Python code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;./ngrok http &lt;span class="m"&gt;8000&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img src="/img/210105-django-assemblyai/ngrok.jpg" width="100%" alt="Ngrok creating a localhost tunnel." class="shot rnd outl"&gt;&lt;/p&gt;
&lt;p&gt;Copy the HTTPS version of the "Forwarding" URL and set the &lt;code&gt;BASE_URL&lt;/code&gt; 
environment variable value to it. For example, in this screenshot you
would set &lt;code&gt;BASE_URL&lt;/code&gt; to &lt;code&gt;https://7764c1810ad3.ngrok.io&lt;/code&gt; using the
following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://7764c1810ad3.ngrok.io    &lt;span class="c1"&gt;# use your ngrok URL, or domain. no trailing slash&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We also need to update &lt;code&gt;djtranscribe/djtranscribe/settings.py&lt;/code&gt;'s 
&lt;code&gt;ALLOWED_HOSTS&lt;/code&gt; list to include the Ngrok Forwarding URL otherwise
the &lt;a href="/webhooks.html"&gt;webhook&lt;/a&gt; from Twilio asking for instructions
on how to handle the phone call will fail. Open the &lt;code&gt;settings.py&lt;/code&gt;
file and update the &lt;code&gt;ALLOWED_HOSTS&lt;/code&gt; with your Ngrok Forwarding
hostname list the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# &lt;span class="nv"&gt;SECURITY&lt;/span&gt; &lt;span class="nv"&gt;WARNING&lt;/span&gt;: &lt;span class="nv"&gt;keep&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;secret&lt;/span&gt; &lt;span class="nv"&gt;key&lt;/span&gt; &lt;span class="nv"&gt;used&lt;/span&gt; &lt;span class="nv"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;production&lt;/span&gt; &lt;span class="nv"&gt;secret&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
&lt;span class="nv"&gt;SECRET_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;os&lt;/span&gt;.&lt;span class="k"&gt;getenv&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;SECRET_KEY&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;development key&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;

# &lt;span class="nv"&gt;SECURITY&lt;/span&gt; &lt;span class="nv"&gt;WARNING&lt;/span&gt;: &lt;span class="nv"&gt;don&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;t run with debug turned on in production!&lt;/span&gt;
&lt;span class="nv"&gt;DEBUG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;True&lt;/span&gt;

&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="nv"&gt;ALLOWED_HOSTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; [&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;7764c1810ad3.ngrok.io&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;,&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;,&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;]


# &lt;span class="nv"&gt;Application&lt;/span&gt; &lt;span class="nv"&gt;definition&lt;/span&gt;

&lt;span class="nv"&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; [
    &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;django.contrib.admin&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;,
    &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;django.contrib.auth&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;,
    &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;django.contrib.contenttypes&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;,
    &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;django.contrib.sessions&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;,
    &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;django.contrib.messages&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;,
    &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;django.contrib.staticfiles&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;,
    &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;caller&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;,
]
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Okay, we can finally re-run our Django web app. Ensure Ngrok is still
running in a different window, your virtualenv is active and that in this 
terminal you have your four environment variables set, then run the 
&lt;code&gt;runserver&lt;/code&gt; command in the root project directory where &lt;code&gt;manage.py&lt;/code&gt;
is located:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python manage.py runserver
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Let's make our phone ring by testing the application.&lt;/p&gt;
&lt;h2&gt;Testing Twilio Programmable Voice Recording&lt;/h2&gt;
&lt;p&gt;We can test our application by going to localhost on port 8000.
Go to this URL in your web browser, replacing the "14155551234"
with the phone number you want to call, where the person on the
line will be recorded: http://localhost:8000/dial/14155551234.&lt;/p&gt;
&lt;p&gt;That number should now receive a phone call from your Twilio
number. Pick up, record a message that you want to use to test
the transcription, and then hang up.&lt;/p&gt;
&lt;p&gt;If you get an error, make sure all of your environment variables
are set. You can check the values by using the echo command like
this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$BASE_URL&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;When the call is over, copy the call SID show on the web page
so that we can use it to look up where the recording audio
file is stored.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210105-django-assemblyai/dial-call-sid.png" width="100%" alt="Twilio call SID served through the Django web app." class="shot rnd outl"&gt;&lt;/p&gt;
&lt;p&gt;Go to "localhost:8000/get-recording-url/" with the call SID
at the end. For example, 
"localhost:8000/get-recording-url/CAda3f2f49ff4e8ef2be6b726edb998c92".&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210105-django-assemblyai/call-recording-url.png" width="100%" alt="Twilio call recording URL." class="shot rnd outl"&gt;&lt;/p&gt;
&lt;p&gt;Copy the entire output except for the ".json" at the end, then paste
it into the web browser's URL bar, prepended with "api.twilio.com".
For example, 
"https://api.twilio.com/2010-04-01/Accounts/ACe3737affa0d2e17561ad44c9d190e70c/Recordings/RE3b42cf470bef829c3680ded961a09300".
This will bring up the recording. Copy the entire URL and we will use it
as input into the AssemblyAI service.&lt;/p&gt;
&lt;h2&gt;Transcribing with the AssemblyAI API&lt;/h2&gt;
&lt;p&gt;We can now use the AssemblyAI API for speech-to-text transcription on
the call recording that was just made.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://app.assemblyai.com/login/"&gt;Sign up for an AssemblyAI account&lt;/a&gt; 
and log in to the 
&lt;a href="https://app.assemblyai.com/dashboard/"&gt;AssemblyAI dashboard&lt;/a&gt;, then
copy "Your API token" as shown in this screenshot:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/210105-django-assemblyai/assemblyai-dashboard.png" width="100%" alt="AssemblyAI dashboard." class="shot rnd outl"&gt;&lt;/p&gt;
&lt;p&gt;We need to export our AssemblyAI API key as an environment variable
so that our Python application can use it to authenticate with their
API. We also need to pass the publicly-accessible URL for the recording,
so we'll set that as an environment variable as well.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# make sure to replace this URL with the one for your recording&lt;/span&gt;
&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;ASSEMBLYAI_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-api-key-here
&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;RECORDING_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://api.twilio.com/2010-04-01/Accounts/ACe3737affa0d2e17561ad44c9d190e70c/Recordings/RE3b42cf470bef829c3680ded961a09300
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Create a new file named &lt;code&gt;transcribe.py&lt;/code&gt; and write the following code in it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://api.assemblyai.com/v2/transcript&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;&amp;quot;audio_url&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;RECORDING_URL&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;authorization&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ASSEMBLYAI_KEY&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;content-type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;application/json&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&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;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The above code calls the AssemblyAI transcription service using
the secret key and passes it the URL with the file recording.
The script prints out the JSON response from the service,
which will contain a transcription ID that we'll use to access
the results after they finish processing.&lt;/p&gt;
&lt;p&gt;Run the script using the &lt;code&gt;python&lt;/code&gt; command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python transcribe.py
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;You will get back some JSON as output, similar what you see here:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;audio_end_at&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;acoustic_model&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;assemblyai_default&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;audio_url&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;https://api.twilio.com/2010-04-01/Accounts/ACe3737affa0d2e17561ad44c9d190e70c/Recordings/RE3b42cf470bef829c3680ded961a09300&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;speed_boost&amp;#39;&lt;/span&gt;: False, &lt;span class="s1"&gt;&amp;#39;language_model&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;assemblyai_default&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;redact_pii&amp;#39;&lt;/span&gt;: False, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;webhook_status_code&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;zibe9vwmx-82ce-476c-85a7-e82c09c67daf&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;status&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;queued&amp;#39;&lt;/span&gt;,
&lt;span class="s1"&gt;&amp;#39;boost_param&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;words&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;format_text&amp;#39;&lt;/span&gt;: True, &lt;span class="s1"&gt;&amp;#39;webhook_url&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;punctuate&amp;#39;&lt;/span&gt;: True, &lt;span class="s1"&gt;&amp;#39;utterances&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;audio_duration&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;auto_highlights&amp;#39;&lt;/span&gt;: False, &lt;span class="s1"&gt;&amp;#39;word_boost&amp;#39;&lt;/span&gt;: &lt;span class="o"&gt;[]&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;dual_channel&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;audio_start_from&amp;#39;&lt;/span&gt;: None&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Find the value contained with the &lt;code&gt;id&lt;/code&gt; field of the JSON response. We need
that value to look up the final result of our transcription. Copy the 
transcription ID and set it as an environment variable to use as input by 
the final script:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# replace with what&amp;#39;s found within `id` from the JSON response&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="n"&gt;TRANSCRIPTION_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;aksd19vwmx&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;82ce&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;476c&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;85a7&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;e82c09c67daf&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We just need a little more Python that looks up the result and we'll be all
done.&lt;/p&gt;
&lt;h2&gt;Retrieve the AssemblyAI Transcription&lt;/h2&gt;
&lt;p&gt;AssemblyAI will be busy transcribing the recording. Depending on the size of
the file it can take anywhere from a few seconds to a few minutes for the
job to complete. We can use the following code to see if the job is still
in progress or it has completed. If the transcription is done it will print
the results to the terminal.&lt;/p&gt;
&lt;p&gt;Create a new file named &lt;code&gt;print_transcription.py&lt;/code&gt; with the following code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://api.assemblyai.com/v2/transcript/&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;TRANSCRIPTION_ID&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;authorization&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ASSEMBLYAI_KEY&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="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="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The code above in &lt;code&gt;print_transcription.py&lt;/code&gt; is very similar to the code
in the previous &lt;code&gt;transcribe.py&lt;/code&gt; source file. imports &lt;code&gt;os&lt;/code&gt; (operating system)
from the Python standard library, as we did in the previous two files,
to obtain the &lt;code&gt;TRANSCRIPTION_ID&lt;/code&gt; and &lt;code&gt;ASSEMBLYAI_KEY&lt;/code&gt; environment variable
values.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;endpoint&lt;/code&gt; is the AssemblyAI API endpoint for retrieving
transcriptions. We set the appropriate &lt;code&gt;authorization&lt;/code&gt; header and
make the API call using the &lt;code&gt;requests.get&lt;/code&gt; function. We then print
out the JSON response as well as just the text that was transcribed.&lt;/p&gt;
&lt;p&gt;Time to test out this third file. Execute the following command in
the terminal:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python print_transcription.py
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Your output will be different based on your recording but you should see a 
result in the terminal similar to the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;audio_end_at&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;acoustic_model&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;assemblyai_default&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;auto_highlights_result&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;An object relational mapper is a code library that automates the transfer of data stored in a relational database tables into objects that are more commonly used in application. Code or MS provide a high level abstraction upon a relational database that allows the developer to write Python code. Instead of sequel to create read update and delete data and schemas in their database developers can use the programming language that they are comfortable with comfortable to work with the database instead of writing sequel statements or short procedures.&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;audio_url&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;https://api.twilio.com/2010-04-01/Accounts/ACe3737affa0d2e17561ad44c9d190e70c/Recordings/RE3b42cf470bef829c3680ded961a09300&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;speed_boost&amp;#39;&lt;/span&gt;: False, &lt;span class="s1"&gt;&amp;#39;language_model&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;assemblyai_default&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;zibe9vwmx-82ce-476c-85a7-e82c09c67daf&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.931797752808989, &lt;span class="s1"&gt;&amp;#39;webhook_status_code&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;status&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;completed&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;boost_param&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;redact_pii&amp;#39;&lt;/span&gt;: False, &lt;span class="s1"&gt;&amp;#39;words&amp;#39;&lt;/span&gt;: &lt;span class="o"&gt;[{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;An&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1&lt;/span&gt;.0, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;90&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;object&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.94, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;570&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;210&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;relational&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.89, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1080&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;510&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;mapper&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.97, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1380&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1020&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;is&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.88, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1560&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1350&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.99, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1620&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1500&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;code&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.93, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1920&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1620&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;library&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.94, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;2250&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1860&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;that&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.99, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;2490&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;2220&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;automates&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.93, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;2940&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;2430&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;the&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.95, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;3150&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;2910&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;transfer&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.98, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;3510&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;3090&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;of&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;:
&lt;span class="m"&gt;0&lt;/span&gt;.99, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;3660&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;3480&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;data&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.84, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;3960&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;3630&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;stored&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.89, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;4350&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;3900&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;in&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.98, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;4500&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;4290&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.85, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;4560&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;4440&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;relational&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.87, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;5580&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;4500&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;database&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.92, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;:
&lt;span class="m"&gt;6030&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;5520&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;tables&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.93, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;6330&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;5970&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;into&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.92, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;7130&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;6560&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;objects&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.96, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;7490&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;7100&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;that&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.97, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;7700&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;7430&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;are&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.9, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;7850&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;7640&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;more&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.97, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;8030&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;7790&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;commonly&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.92, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;8480&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;7970&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;used&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.86, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;8750&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;8420&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;in&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.94, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;9050&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;8840&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;application.&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.98, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;9860&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;9110&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;Code&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.93, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;10040&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;9830&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;or&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1&lt;/span&gt;.0, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;11210&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;10220&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;MS&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.83, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;11480&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;11180&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;provide&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.94, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;11870&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;11510&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1&lt;/span&gt;.0, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;11960&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;11840&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;high&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1&lt;/span&gt;.0, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;12200&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;11930&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;level&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.94, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;12440&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;12170&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;abstraction&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.95, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;12980&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;12410&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;:
&lt;span class="s1"&gt;&amp;#39;upon&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.94, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;13220&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;12950&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1&lt;/span&gt;.0, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;13280&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;13160&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;relational&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.94, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;13820&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;13280&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;database&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.95, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;14210&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;13790&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;that&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.96, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;14420&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;14150&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;allows&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.99, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;14720&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;14360&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;:
&lt;span class="s1"&gt;&amp;#39;the&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.56, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;14870&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;14690&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;developer&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.98, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;15290&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;14810&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;to&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.94, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;15410&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;15230&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;write&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.96, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;15680&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;15380&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;Python&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.94, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;16070&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;15620&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;code.&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.98, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;16310&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;16070&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;Instead&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.97, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;17160&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;16500&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;of&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.93, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;17340&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;17130&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;sequel&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.86, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;17820&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;17280&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;to&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.91, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;18090&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;17880&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;create&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.89, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;18450&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;18090&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;read&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.88, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;18840&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;18480&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;update&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.92, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;19290&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;18870&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;and&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.94, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;19590&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;19230&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;delete&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.89, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;19920&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;19530&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;data&amp;#39;&lt;/span&gt;,
&lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.95, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;20190&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;19890&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;and&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.92, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;20490&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;20250&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;schemas&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.86, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;21000&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;20430&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;in&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.94, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;21210&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;21000&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;their&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.98, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;21510&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;21150&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;database&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.97, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;21900&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;21450&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;developers&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.83, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;23200&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;22420&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;can&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.95, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;23440&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;23200&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;use&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.97, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;23650&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;23410&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;the&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.99, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;23890&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;23590&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;programming&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.97, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;24370&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;23830&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;language&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1&lt;/span&gt;.0, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;24700&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;24310&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;that&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1&lt;/span&gt;.0, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;24880&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;24640&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;they&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.99, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;25060&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;24820&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;are&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.85, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;25210&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;25000&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;comfortable&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.92, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;25780&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;25180&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;with&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1&lt;/span&gt;.0, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;25960&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;25720&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;comfortable&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.94, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;29090&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;28090&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;to&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.84, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;29840&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;29180&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;work&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.95, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;30050&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;29780&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;with&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.98, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;30290&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;30020&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;the&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.69, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;30440&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;30230&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;database&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.98, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;30860&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;30380&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;instead&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1&lt;/span&gt;.0, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;32780&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;31780&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;of&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.98, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;32900&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;32720&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;writing&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.87, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;33320&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;32870&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;sequel&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.88, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;33860&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;33290&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;statements&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.95, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;34310&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;33800&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;or&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.9, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;34460&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;34250&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;short&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.9, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;34790&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;34430&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;procedures.&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.98, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;35270&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;34760&lt;/span&gt;&lt;span class="o"&gt;}]&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;format_text&amp;#39;&lt;/span&gt;: True, &lt;span class="s1"&gt;&amp;#39;webhook_url&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;punctuate&amp;#39;&lt;/span&gt;: True, &lt;span class="s1"&gt;&amp;#39;utterances&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;audio_duration&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;36&lt;/span&gt;.288, &lt;span class="s1"&gt;&amp;#39;auto_highlights&amp;#39;&lt;/span&gt;: False, &lt;span class="s1"&gt;&amp;#39;word_boost&amp;#39;&lt;/span&gt;: &lt;span class="o"&gt;[]&lt;/span&gt;,
&lt;span class="s1"&gt;&amp;#39;dual_channel&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;audio_start_from&amp;#39;&lt;/span&gt;: None&lt;span class="o"&gt;}&lt;/span&gt;


An object relational mapper is a code library that automates the transfer of data stored &lt;span class="k"&gt;in&lt;/span&gt; a relational database tables into objects that are more commonly used &lt;span class="k"&gt;in&lt;/span&gt; application. Code or MS provide a high level abstraction upon a relational database that allows the developer to write Python code. Instead of sequel to create &lt;span class="nb"&gt;read&lt;/span&gt; update and delete data and schemas &lt;span class="k"&gt;in&lt;/span&gt; their database developers can use the programming language that they are comfortable with comfortable to work with the database instead of writing sequel statements or short procedures.
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;That's a lot of output. The first part contains the results of the 
transcription and the confidence in the accuracy of each word transcribed.
The second part is just the plain text output from the transcription.&lt;/p&gt;
&lt;p&gt;You can take this now take this base code and add it to any application
that needs high quality text-to-speech transcription. If the results
aren't quite good enough for you yet, check out this tutorial on
&lt;a href="https://docs.assemblyai.com/guides/boosting-accuracy-for-keywords-or-phrases"&gt;boosting accuracy for keywords or phrases&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Additional resources&lt;/h2&gt;
&lt;p&gt;We just finished building a highly accurate transcription application for recordings.&lt;/p&gt;
&lt;p&gt;Next, try out some of these other related &lt;a href="/django.html"&gt;Django&lt;/a&gt; tutorials:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/blog/sentry-handle-exceptions-django-projects.html"&gt;Using Sentry to Handle Python Exceptions in Django Projects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/blog/track-daily-user-data-django-user-visit.html"&gt;Tracking Daily User Data in Django with django-user-visit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/blog/bootstrap-4-django-template.html"&gt;How to Quickly Use Bootstrap 4 in a Django Template with a CDN&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Questions? Let me know via
&lt;a href="https://github.com/mattmakai/fullstackpython.com/issues"&gt;a GitHub issue ticket on the Full Stack Python repository&lt;/a&gt;,
on Twitter
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;.
See something wrong with this post? Fork
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/210105-django-accurate-twilio-voice-transcriptions.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Mon, 13 Sep 2021 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2021-01-05:blog/django-accurate-twilio-voice-transcriptions.html</guid></item><item><title>Higher Accuracy Twilio Voice Transcriptions with Python and Flask</title><link>https://www.fullstackpython.com/blog/accurate-twilio-voice-call-recording-transcriptions-assemblyai.html</link><description>&lt;p&gt;&lt;a href="https://www.twilio.com/docs/voice"&gt;Twilio's Programmable Voice API&lt;/a&gt; 
is commonly used to initiate and receive phone calls, but the transcription 
accuracy for &lt;a href="https://www.twilio.com/docs/voice/api/recording"&gt;recordings&lt;/a&gt; 
often leaves a lot to be desired. In this tutorial, we'll see how to connect an
outbound phone call powered by the Twilio Voice API with 
&lt;a href="https://docs.assemblyai.com/overview/getting-started"&gt;AssemblyAI's deep learning transcription API&lt;/a&gt;
to get significantly more accurate speech-to-text output.&lt;/p&gt;
&lt;h2&gt;Required Tools for this Application&lt;/h2&gt;
&lt;p&gt;Ensure you have Python 3 installed, because Python 2 reached its
end-of-life at the beginning of 2020 and is no longer supported.
Preferrably, you should have
&lt;a href="https://www.python.org/downloads/"&gt;Python 3.6 or newer installed&lt;/a&gt;
in your &lt;a href="/development-environments.html"&gt;development environment&lt;/a&gt;.
This tutorial will also use:&lt;/p&gt;
&lt;p&gt;We will use the following dependencies to complete this
tutorial:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://requests.readthedocs.io/"&gt;requests&lt;/a&gt;, version
  &lt;a href="https://pypi.org/project/requests/"&gt;2.24.0&lt;/a&gt;, for accessing the
  &lt;a href="https://docs.assemblyai.com/overview/getting-started"&gt;AssemblyAI transcription API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flask.palletsprojects.com/en/1.1.x/"&gt;Flask&lt;/a&gt;, version
  &lt;a href="https://pypi.org/project/Flask/1.1.2/"&gt;1.1.2&lt;/a&gt;, to respond to Twilio's 
  webhooks&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://www.twilio.com/referral/w9pugq"&gt;Twilio account&lt;/a&gt;, of which a
  free trial version is good enough to test this tutorial&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pypi.org/project/twilio/"&gt;Twilio Python helper library&lt;/a&gt;,
  version &lt;a href="https://pypi.org/project/twilio/6.45.4/"&gt;6.45.4&lt;/a&gt; or newer,
  for interacting with the &lt;a href="https://www.twilio.com/docs/usage/api"&gt;REST API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;An &lt;a href="https://www.assemblyai.com/"&gt;AssemblyAI&lt;/a&gt; account, which you can sign 
  up for a &lt;a href="https://app.assemblyai.com/login/"&gt;free key API access key here&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ngrok.com/"&gt;Ngrok&lt;/a&gt; if you need a localhost tunnel to expose
  a public URL that webhooks can send a POST request to&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All code in this blog post is available open source under the MIT license
on GitHub under the
&lt;a href="https://github.com/fullstackpython/blog-code-examples"&gt;accurate-twilio-voice-call-recording-transcriptions-assemblyai directory of the blog-code-examples repository&lt;/a&gt;.
Use the source code as you desire for your own projects.&lt;/p&gt;
&lt;h2&gt;Configuring our development environment&lt;/h2&gt;
&lt;p&gt;Change into the directory where you keep your Python
&lt;a href="/virtual-environments-virtualenvs-venvs.html"&gt;virtual environments&lt;/a&gt;.
Create a new virtualenv for this project using the following
command.&lt;/p&gt;
&lt;p&gt;Start this Python project by creating a new
&lt;a href="/virtual-environments-virtualenvs-venvs.html"&gt;virtual environment&lt;/a&gt;
using the following command. I recommend using a separate directory
such as &lt;code&gt;~/venvs/&lt;/code&gt; (the tilde is a shortcut for your user's &lt;code&gt;home&lt;/code&gt;
directory) so that you always know where all your virtualenvs are
located.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python3 -m venv ~/venvs/record-transcribe
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Activate the virtualenv with the &lt;code&gt;activate&lt;/code&gt; shell script:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt; ~/venvs/record-transcribe/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;After the above command is executed, the command prompt will
change so that the name of the virtualenv is prepended to the
original command prompt format, so if your prompt is simply
&lt;code&gt;$&lt;/code&gt;, it will now look like the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;record-transcribe&lt;span class="o"&gt;)&lt;/span&gt; $
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Remember, you have to activate your virtualenv in every new terminal
window where you want to use dependencies in the virtualenv.&lt;/p&gt;
&lt;p&gt;We can now install the required packages
package into the activated but otherwise empty virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install Flask==1.1.2 requests==2.24.0 twilio==6.45.4
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Look for output similar to the following to confirm the appropriate
packages were installed correctly from PyPI.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;recordt&lt;/span&gt;&lt;span class="n"&gt;ranscribe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mf"&gt;1.1.2&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mf"&gt;2.24.0&lt;/span&gt; &lt;span class="n"&gt;twilio&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;6.45.4&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="o"&gt;://&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;f2&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;a03252dfb9ebf377f40fba6a7841b47083260bf8bd8e737b0c6952df83f&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.1.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="o"&gt;://&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;c169c6a5381e241ba7404532c16a21d86ab872c9bed8bdcd4c423954103&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.24.0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;twilio&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="o"&gt;://&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;d0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="n"&gt;c377eb1a1d57f011dc1bee2fee77cf1e9a08407b8d44ea25a187a30c78d&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;twilio&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;6.45.4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gz&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;Werkzeug&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.15&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="o"&gt;://&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;cc&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;94&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;5f&lt;/span&gt;&lt;span class="mi"&gt;7079&lt;/span&gt;&lt;span class="n"&gt;a0e00bd6863ef8f1da638721e9da21e5bacee597595b318f71d62e&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Werkzeug&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.0.1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;itsdangerous&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.24&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="o"&gt;://&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;76&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ae&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;44&lt;/span&gt;&lt;span class="n"&gt;b03b253d6fade317f32c24d100b3b35c2239807046a4c953c7b89fa49e&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;itsdangerous&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.1.0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;5.1&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="o"&gt;://&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;d2&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;fa76db83bf75c4f8d338c2fd15c8d33fdd7ad23a9b5e57eb6c5de26b430e&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;7.1.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;Jinja2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;2.10.1&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="o"&gt;://&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;f663a2aa66a09d838042ae1a2c5659828bb9b41ea3a6efa20a20fd92b121&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Jinja2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.11.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;urllib3&lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="mf"&gt;1.25.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="mf"&gt;1.25.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mf"&gt;1.26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.21.1&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="o"&gt;://&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;9f&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;f0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;a391d1463ebb1b233795cabfc0ef38d3db4442339de68f847026199e69d7&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;urllib3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.25.10&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;idna&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;2.5&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="o"&gt;://&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;a2&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;38&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;928&lt;/span&gt;&lt;span class="n"&gt;ddce2273eaa564f6f50de919327bf3a00f091b5baba8dfa9460f3a8a8&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;idna&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.10&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;certifi&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;2017.4.17&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="o"&gt;://&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;c4&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="n"&gt;c4fe722df5343c33226f0b4e0bb042e4dc13483228b4718baf286f86d87&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;certifi&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2020.6.20&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;chardet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;3.0.2&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="o"&gt;://&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bc&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;a9&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01ff&lt;/span&gt;&lt;span class="n"&gt;ebfb562e4274b6487b4bb1ddec7ca55ec7510b22e4c51f14098443b8&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;chardet&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.0.4&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;six&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;twilio&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="o"&gt;://&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ee&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ff&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="n"&gt;bde5c0f013094d729fe4b0316ba2a24774b3ff1c52d924a8a4cb04078a&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;six&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.15.0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;pytz&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;twilio&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="o"&gt;://&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;4f&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;a4&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;879454&lt;/span&gt;&lt;span class="n"&gt;d49688e2fad93e59d7d4efda580b783c745fd2ec2a3adf87b0808d&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pytz&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2020.1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;PyJWT&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.4.2&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;twilio&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="o"&gt;://&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;87&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="n"&gt;a9f14b5f781697e51259d81657e6048fd31a113229cf346880bb7545565&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;PyJWT&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.7.1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;MarkupSafe&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.23&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;Jinja2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;2.10.1&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="o"&gt;://&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;37f&lt;/span&gt;&lt;span class="mi"&gt;68957526&lt;/span&gt;&lt;span class="n"&gt;d1ec0883b521934b4e1b8ff3dd8e4fab858a5bf3e487bcee9&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;MarkupSafe&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.1.1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;cp38&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;cp38&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;macosx_10_9_x86_64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;span class="n"&gt;Installing&lt;/span&gt; &lt;span class="n"&gt;collected&lt;/span&gt; &lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Werkzeug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;itsdangerous&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MarkupSafe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Jinja2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urllib3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idna&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;certifi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chardet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;six&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pytz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PyJWT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;twilio&lt;/span&gt;
  &lt;span class="n"&gt;Running&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="n"&gt;twilio&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt;
&lt;span class="n"&gt;Successfully&lt;/span&gt; &lt;span class="n"&gt;installed&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.1.2&lt;/span&gt; &lt;span class="n"&gt;Jinja2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.11.2&lt;/span&gt; &lt;span class="n"&gt;MarkupSafe&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.1.1&lt;/span&gt; &lt;span class="n"&gt;PyJWT&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.7.1&lt;/span&gt; &lt;span class="n"&gt;Werkzeug&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.0.1&lt;/span&gt; &lt;span class="n"&gt;certifi&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2020.6.20&lt;/span&gt; &lt;span class="n"&gt;chardet&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.0.4&lt;/span&gt; &lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;7.1.2&lt;/span&gt; &lt;span class="n"&gt;idna&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.10&lt;/span&gt; &lt;span class="n"&gt;itsdangerous&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.1.0&lt;/span&gt; &lt;span class="n"&gt;pytz&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2020.1&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.24.0&lt;/span&gt; &lt;span class="n"&gt;six&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.15.0&lt;/span&gt; &lt;span class="n"&gt;twilio&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;6.45.4&lt;/span&gt; &lt;span class="n"&gt;urllib3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.25.10&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We can get started coding the application now that we have all of our
required dependencies installed.&lt;/p&gt;
&lt;h2&gt;Building our application&lt;/h2&gt;
&lt;p&gt;Time to dig into the code! We're going to write three source files in 
this application:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;app.py&lt;/code&gt;: a Flask app that will handle the phone call and recording&lt;/li&gt;
&lt;li&gt;&lt;code&gt;transcribe.py&lt;/code&gt;: a short Python script to invoke AssemblyAI with the
  recording and start the transcription process&lt;/li&gt;
&lt;li&gt;&lt;code&gt;print_transcription.py&lt;/code&gt;: a script to print the output of the 
  transcription to the terminal&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Remember that you can get access to all three of the completed files in the
&lt;code&gt;accurate-twilio-voice-call-recording-transcriptions-assemblyai&lt;/code&gt; directory
of the 
&lt;a href="https://github.com/fullstackpython/blog-code-examples"&gt;blog-code-examples&lt;/a&gt;
Git repository if you do not want to type or copy from the blog post
itself.&lt;/p&gt;
&lt;p&gt;Create a new directory named &lt;code&gt;record-transcribe&lt;/code&gt; to store your source files
and change into the new directory.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;mkdir record-transcribe
cd record-transcribe
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Create a new file named &lt;code&gt;app.py&lt;/code&gt; with the following code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;                                                                                                                                                                                                                                     
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;twilio.twiml.voice_response&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;VoiceResponse&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;twilio.rest&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;


&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# pulls credentials from environment variables&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;BASE_URL&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;twiml_instructions_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;/record&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;recording_callback_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;/callback&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;twilio_phone_number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;TWILIO_PHONE_NUMBER&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/record&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GET&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;POST&amp;quot;&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;record&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Returns TwiML which prompts the caller to record a message&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="c1"&gt;# Start our TwiML response&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VoiceResponse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Use &amp;lt;Say&amp;gt; to give the caller some instructions&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Ahoy! Call recording starts now.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Use &amp;lt;Record&amp;gt; to record the caller&amp;#39;s message&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# End the call with &amp;lt;Hangup&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hangup&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;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;There are a couple more functions we'll need to add to &lt;code&gt;app.py&lt;/code&gt; but first
let's take a look at what the above code does. &lt;/p&gt;
&lt;p&gt;We imported parts of both the Flask and Twilio helper libraries, which will
enable us to programmatically create and control phone calls that Twilio 
records. Note that when we instantiate the Twilio helper library with the
empty &lt;code&gt;Client()&lt;/code&gt; constructor, it automatically looks to read two environment
variables, &lt;code&gt;TWILIO_ACCOUNT_SID&lt;/code&gt; and &lt;code&gt;TWILIO_AUTH_TOKEN&lt;/code&gt; to gain appropriate
permissions to your Twilio account. If those two environment variables
are not set with those exact names then you will need to explicitly pass
the Account SID and Auth Token for your account into the constructor.&lt;/p&gt;
&lt;p&gt;After the import are the Flask and Twilio library instantiations.
Then we configure the &lt;code&gt;BASE_URL&lt;/code&gt; by reading from an environment variable.
In this tutorial the &lt;code&gt;BASE_URL&lt;/code&gt; will be from Ngrok, but it can also
be your domain where your application is deployed, such as 
"https://www.twilio.com". We have not yet set these environment variables, 
but we will shortly after we finish writing &lt;code&gt;app.py&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;After setting &lt;code&gt;BASE_URL&lt;/code&gt;, and the three other variables set by environment
variables, we have the &lt;code&gt;record&lt;/code&gt; function. This function is a 
&lt;a href="https://hackersandslackers.com/flask-routes/"&gt;Flask route&lt;/a&gt; that
generates the &lt;a href="https://www.twilio.com/docs/voice/twiml"&gt;TwiML&lt;/a&gt;
that tells Twilio how to handle a phone call. First, an automated voice
alerts the person who picks up that the phone call is being recorded. Then
the recording starts. Whatever the person on the call says will be recorded
and stored by Twilio.&lt;/p&gt;
&lt;p&gt;Finish &lt;code&gt;app.py&lt;/code&gt; by adding these two following functions after the
&lt;code&gt;record&lt;/code&gt; function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/dial/&amp;lt;int:phone_number&amp;gt;&amp;quot;&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;dial&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="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Dials an outbound phone call to the number in the URL. Just&lt;/span&gt;
&lt;span class="sd"&gt;    as a heads up you will never want to leave a URL like this exposed&lt;/span&gt;
&lt;span class="sd"&gt;    without authentication and further phone number format verification.&lt;/span&gt;
&lt;span class="sd"&gt;    phone_number should be just the digits with the country code first,&lt;/span&gt;
&lt;span class="sd"&gt;    for example 14155559812.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calls&lt;/span&gt;&lt;span class="o"&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;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;+&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&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="n"&gt;from_&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;twilio_phone_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;twiml_instructions_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;dialing +&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;. call SID is: &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&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="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/get-recording-url/&amp;lt;call_sid&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_recording_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call_sid&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;recording_urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call_sid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recordings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;recording_urls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;recording_urls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uri&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;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recording_urls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;dial&lt;/code&gt; function creates a Flask route that takes a phone number
input as part of the second level path. Note that in a production
application you &lt;em&gt;must&lt;/em&gt; have better phone number validation or you
will have a security issue with unsanitized inputs. We are doing
this here to easily grab a phone number as input rather than having
to build a whole user interface with an HTML form just to grab a
phone number. &lt;code&gt;dial&lt;/code&gt; calls the 
&lt;a href="https://www.twilio.com/docs/voice"&gt;Twilio Voice API&lt;/a&gt; using our
Twilio account credentials so that we can dial an outbound phone
call to the number sent in through the URL. The &lt;code&gt;twiml_instructions_url&lt;/code&gt;
should be set to the &lt;code&gt;record&lt;/code&gt; function URL so that it can give the
proper dialing and recording TwiML instructions for how Twilio's
service should handle dialing the phone call.&lt;/p&gt;
&lt;p&gt;Once we dial the outbound phone call, the 
&lt;a href="https://support.twilio.com/hc/en-us/articles/223180488-What-is-a-Call-SID-"&gt;call SID&lt;/a&gt;
is printed to the terminal. We'll need that call SID to get the
recording after the call is finished.&lt;/p&gt;
&lt;p&gt;Our &lt;code&gt;app.py&lt;/code&gt; file is all done. We just need to export our environment
variables for our Twilio credentials. &lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.twilio.com/referral/w9pugq"&gt;Sign up for Twilio&lt;/a&gt; or 
&lt;a href="https://www.twilio.com/console"&gt;log into your existing account&lt;/a&gt;. 
Once you get to the &lt;a href="https://www.twilio.com/console"&gt;Twilio Console&lt;/a&gt;, 
you can obtain your &lt;code&gt;TWILIO_ACCOUNT_SID&lt;/code&gt; and &lt;code&gt;TWILIO_AUTH_TOKEN&lt;/code&gt; on the 
right side of the page:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/201009-twilio-flask-assemblyai/twilio-console.png" width="100%" alt="Twilio Console." class="shot rnd outl"&gt;&lt;/p&gt;
&lt;p&gt;When you sign up you should have a phone number assigned to your account.
You can use that or 
&lt;a href="https://www.twilio.com/console/phone-numbers/search"&gt;purchase a new phone number&lt;/a&gt;
to use.&lt;/p&gt;
&lt;p&gt;Set three environment variables with the names &lt;code&gt;TWILIO_ACCOUNT_SID&lt;/code&gt;,
&lt;code&gt;TWILIO_AUTH_TOKEN&lt;/code&gt;, and &lt;code&gt;TWILIO_PHONE_NUMBER&lt;/code&gt; using the &lt;code&gt;export&lt;/code&gt; command
in your terminal. Make sure to replace the values with your own Account SID,
Auth Token and Twilio phone number.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;TWILIO_ACCOUNT_SID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;xxxxxxxxxxxxx    &lt;span class="c1"&gt;# found in twilio.com/console&lt;/span&gt;
&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;TWILIO_AUTH_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;yyyyyyyyyyyyyy    &lt;span class="c1"&gt;# found in twilio.com/console&lt;/span&gt;
&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;TWILIO_PHONE_NUMBER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;+17166382453    &lt;span class="c1"&gt;# replace with your Twilio number&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Note that you must use the &lt;code&gt;export&lt;/code&gt; command in every command line window
that you want this key to be accessible. The scripts we are writing will
not be able to access the Twilio APIs if you do not have the tokens exported
in the environment where you are running the script.&lt;/p&gt;
&lt;p&gt;There is one more environment variable to set before we can run &lt;code&gt;app.py&lt;/code&gt;.
We need to use Ngrok as a localhost tunnel so that Twilio's webhook can
send an HTTP POST request to our &lt;code&gt;app.py&lt;/code&gt; Flask application running on
our local development environment.&lt;/p&gt;
&lt;p&gt;Run Ngrok in a new terminal window, because you will need to keep it
running while we run our other Python code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;./ngrok http &lt;span class="m"&gt;5000&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img src="/img/201009-twilio-flask-assemblyai/ngrok.jpg" width="100%" alt="Ngrok running with a localhost tunnel." class="shot rnd outl"&gt;&lt;/p&gt;
&lt;p&gt;Copy the HTTPS version of the "Forwarding" URL and set the &lt;code&gt;BASE_URL&lt;/code&gt; 
environment variable value to it. For example, in this screenshot you
would set &lt;code&gt;BASE_URL&lt;/code&gt; to &lt;code&gt;https://7f9139eaf445.ngrok.io&lt;/code&gt; using the
following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://7f9139eaf445.ngrok.io    &lt;span class="c1"&gt;# use your ngrok URL, or domain. no trailing slash&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Okay, we can finally run &lt;code&gt;app.py&lt;/code&gt;. Make sure you are still running Ngrok
in a different window, your virtualenv is active and that in this terminal
you have your four environment variables set, then run the &lt;code&gt;flask run&lt;/code&gt;
command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;flask run
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;You should see Flask output something like the following text:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt; * Environment: production
   WARNING: This is a development server. Do not use it &lt;span class="k"&gt;in&lt;/span&gt; a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ &lt;span class="o"&gt;(&lt;/span&gt;Press CTRL+C to quit&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;That is a legitimate warning: only use this command for
development purposes and when you want to &lt;a href="/deployment.html"&gt;deploy&lt;/a&gt;
to production you need to use a real &lt;a href="/wsgi-servers.html"&gt;WSGI server&lt;/a&gt;
like &lt;a href="/green-unicorn-gunicorn.html"&gt;Gunicorn&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Time to test out our application.&lt;/p&gt;
&lt;h2&gt;Testing Twilio Programmable Voice Recording&lt;/h2&gt;
&lt;p&gt;We can test our application by going to localhost on port 5000.
Go to this URL in your web browser, replacing the "14155551234"
with the phone number you want to call, where the person on the
line will be recorded: http://localhost:5000/dial/14155551234.&lt;/p&gt;
&lt;p&gt;That number should now receive a phone call from your Twilio
number. Pick up, record a message that you want to use to test
the transcription, and then hang up.&lt;/p&gt;
&lt;p&gt;If you get an error, make sure all of your environment variables
are set. You can check the values by using the echo command like
this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$BASE_URL&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;When the call is over, copy the call SID show on the web page
so that we can use it to look up where the recording audio
file is stored.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/201009-twilio-flask-assemblyai/dial-call-sid.png" width="100%" alt="Twilio call SID." class="shot rnd outl"&gt;&lt;/p&gt;
&lt;p&gt;Go to "localhost:5000/get-recording-url/" with the call SID
at the end. For example, 
"localhost:5000/get-recording-url/CAda3f2f49ff4e8ef2be6b726edb998c92".&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/201009-twilio-flask-assemblyai/call-recording-url.png" width="100%" alt="Twilio call recording URL." class="shot rnd outl"&gt;&lt;/p&gt;
&lt;p&gt;Copy the entire output except for the ".json" at the end, then paste
it into the web browser's URL bar, prepended with "api.twilio.com".
For example, 
"https://api.twilio.com/2010-04-01/Accounts/ACe3737affa0d2e17561ad44c9d190e70c/Recordings/RE3b42cf470bef829c3680ded961a09300".
This will bring up the recording. Copy the entire URL and we will use it
as input into the AssemblyAI service.&lt;/p&gt;
&lt;h2&gt;Transcribing with the AssemblyAI API&lt;/h2&gt;
&lt;p&gt;We can now use the AssemblyAI API for speech-to-text transcription on
the call recording that was just made.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://app.assemblyai.com/login/"&gt;Sign up for an AssemblyAI account&lt;/a&gt; 
and log in to the 
&lt;a href="https://app.assemblyai.com/dashboard/"&gt;AssemblyAI dashboard&lt;/a&gt;, then
copy "Your API token" as shown in this screenshot:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200809-transcription-assemblyai/assemblyai-dashboard.png" width="100%" alt="AssemblyAI dashboard." class="shot rnd outl"&gt;&lt;/p&gt;
&lt;p&gt;We need to export our AssemblyAI API key as an environment variable
so that our Python application can use it to authenticate with their
API. We also need to pass the publicly-accessible URL for the recording,
so we'll set that as an environment variable as well.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# make sure to replace this URL with the one for your recording&lt;/span&gt;
&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;ASSEMBLYAI_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-api-key-here
&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;RECORDING_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://api.twilio.com/2010-04-01/Accounts/ACe3737affa0d2e17561ad44c9d190e70c/Recordings/RE3b42cf470bef829c3680ded961a09300
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Create a new file named &lt;code&gt;transcribe.py&lt;/code&gt; and write the following code in it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://api.assemblyai.com/v2/transcript&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;&amp;quot;audio_url&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;RECORDING_URL&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;authorization&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ASSEMBLYAI_KEY&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;content-type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;application/json&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&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;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The above code calls the AssemblyAI transcription service using
the secret key and passes it the URL with the file recording.
The script prints out the JSON response from the service,
which will contain a transcription ID that we'll use to access
the results after they finish processing.&lt;/p&gt;
&lt;p&gt;Run the script using the &lt;code&gt;python&lt;/code&gt; command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python transcribe.py
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;You will get back some JSON as output, similar what you see here:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;audio_end_at&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;acoustic_model&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;assemblyai_default&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;audio_url&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;https://api.twilio.com/2010-04-01/Accounts/ACe3737affa0d2e17561ad44c9d190e70c/Recordings/RE3b42cf470bef829c3680ded961a09300&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;speed_boost&amp;#39;&lt;/span&gt;: False, &lt;span class="s1"&gt;&amp;#39;language_model&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;assemblyai_default&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;redact_pii&amp;#39;&lt;/span&gt;: False, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;webhook_status_code&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;zibe9vwmx-82ce-476c-85a7-e82c09c67daf&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;status&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;queued&amp;#39;&lt;/span&gt;,
&lt;span class="s1"&gt;&amp;#39;boost_param&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;words&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;format_text&amp;#39;&lt;/span&gt;: True, &lt;span class="s1"&gt;&amp;#39;webhook_url&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;punctuate&amp;#39;&lt;/span&gt;: True, &lt;span class="s1"&gt;&amp;#39;utterances&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;audio_duration&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;auto_highlights&amp;#39;&lt;/span&gt;: False, &lt;span class="s1"&gt;&amp;#39;word_boost&amp;#39;&lt;/span&gt;: &lt;span class="o"&gt;[]&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;dual_channel&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;audio_start_from&amp;#39;&lt;/span&gt;: None&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Find the value contained with the &lt;code&gt;id&lt;/code&gt; field of the JSON response. We need
that value to look up the final result of our transcription. Copy the 
transcription ID and set it as an environment variable to use as input by 
the final script:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# replace with what&amp;#39;s found within `id` from the JSON response&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="n"&gt;TRANSCRIPTION_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;aksd19vwmx&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;82ce&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;476c&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;85a7&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;e82c09c67daf&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We just need a little more Python that looks up the result and we'll be all
done.&lt;/p&gt;
&lt;h2&gt;Retrieve the AssemblyAI Transcription&lt;/h2&gt;
&lt;p&gt;AssemblyAI will be busy transcribing the recording. Depending on the size of
the file it can take anywhere from a few seconds to a few minutes for the
job to complete. We can use the following code to see if the job is still
in progress or it has completed. If the transcription is done it will print
the results to the terminal.&lt;/p&gt;
&lt;p&gt;Create a new file named &lt;code&gt;print_transcription.py&lt;/code&gt; with the following code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://api.assemblyai.com/v2/transcript/&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;TRANSCRIPTION_ID&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;authorization&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ASSEMBLYAI_KEY&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="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="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The code above in &lt;code&gt;print_transcription.py&lt;/code&gt; is very similar to the code
in the previous &lt;code&gt;transcribe.py&lt;/code&gt; source file. imports &lt;code&gt;os&lt;/code&gt; (operating system)
from the Python standard library, as we did in the previous two files,
to obtain the &lt;code&gt;TRANSCRIPTION_ID&lt;/code&gt; and &lt;code&gt;ASSEMBLYAI_KEY&lt;/code&gt; environment variable
values.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;endpoint&lt;/code&gt; is simply the AssemblyAI API endpoint for retrieving
transcriptions. We set the appropriate &lt;code&gt;authorization&lt;/code&gt; header and
make the API call using the &lt;code&gt;requests.get&lt;/code&gt; function. We then print
out the JSON response as well as just the text that was transcribed.&lt;/p&gt;
&lt;p&gt;Time to test out this third file. Execute the following command in
the terminal:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python print_transcription.py
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Your output will be different based on your recording but you should see a 
result in the terminal similar to the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;audio_end_at&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;acoustic_model&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;assemblyai_default&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;auto_highlights_result&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;An object relational mapper is a code library that automates the transfer of data stored in a relational database tables into objects that are more commonly used in application. Code or MS provide a high level abstraction upon a relational database that allows the developer to write Python code. Instead of sequel to create read update and delete data and schemas in their database developers can use the programming language that they are comfortable with comfortable to work with the database instead of writing sequel statements or short procedures.&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;audio_url&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;https://api.twilio.com/2010-04-01/Accounts/ACe3737affa0d2e17561ad44c9d190e70c/Recordings/RE3b42cf470bef829c3680ded961a09300&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;speed_boost&amp;#39;&lt;/span&gt;: False, &lt;span class="s1"&gt;&amp;#39;language_model&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;assemblyai_default&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;zibe9vwmx-82ce-476c-85a7-e82c09c67daf&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.931797752808989, &lt;span class="s1"&gt;&amp;#39;webhook_status_code&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;status&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;completed&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;boost_param&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;redact_pii&amp;#39;&lt;/span&gt;: False, &lt;span class="s1"&gt;&amp;#39;words&amp;#39;&lt;/span&gt;: &lt;span class="o"&gt;[{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;An&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1&lt;/span&gt;.0, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;90&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;object&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.94, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;570&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;210&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;relational&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.89, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1080&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;510&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;mapper&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.97, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1380&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1020&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;is&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.88, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1560&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1350&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.99, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1620&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1500&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;code&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.93, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1920&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1620&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;library&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.94, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;2250&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1860&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;that&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.99, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;2490&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;2220&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;automates&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.93, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;2940&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;2430&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;the&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.95, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;3150&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;2910&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;transfer&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.98, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;3510&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;3090&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;of&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;:
&lt;span class="m"&gt;0&lt;/span&gt;.99, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;3660&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;3480&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;data&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.84, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;3960&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;3630&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;stored&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.89, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;4350&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;3900&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;in&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.98, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;4500&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;4290&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.85, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;4560&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;4440&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;relational&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.87, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;5580&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;4500&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;database&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.92, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;:
&lt;span class="m"&gt;6030&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;5520&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;tables&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.93, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;6330&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;5970&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;into&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.92, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;7130&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;6560&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;objects&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.96, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;7490&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;7100&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;that&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.97, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;7700&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;7430&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;are&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.9, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;7850&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;7640&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;more&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.97, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;8030&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;7790&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;commonly&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.92, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;8480&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;7970&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;used&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.86, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;8750&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;8420&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;in&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.94, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;9050&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;8840&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;application.&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.98, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;9860&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;9110&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;Code&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.93, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;10040&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;9830&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;or&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1&lt;/span&gt;.0, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;11210&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;10220&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;MS&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.83, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;11480&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;11180&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;provide&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.94, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;11870&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;11510&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1&lt;/span&gt;.0, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;11960&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;11840&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;high&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1&lt;/span&gt;.0, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;12200&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;11930&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;level&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.94, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;12440&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;12170&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;abstraction&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.95, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;12980&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;12410&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;:
&lt;span class="s1"&gt;&amp;#39;upon&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.94, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;13220&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;12950&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1&lt;/span&gt;.0, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;13280&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;13160&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;relational&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.94, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;13820&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;13280&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;database&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.95, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;14210&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;13790&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;that&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.96, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;14420&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;14150&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;allows&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.99, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;14720&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;14360&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;:
&lt;span class="s1"&gt;&amp;#39;the&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.56, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;14870&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;14690&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;developer&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.98, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;15290&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;14810&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;to&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.94, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;15410&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;15230&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;write&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.96, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;15680&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;15380&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;Python&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.94, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;16070&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;15620&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;code.&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.98, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;16310&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;16070&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;Instead&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.97, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;17160&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;16500&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;of&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.93, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;17340&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;17130&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;sequel&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.86, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;17820&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;17280&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;to&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.91, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;18090&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;17880&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;create&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.89, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;18450&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;18090&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;read&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.88, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;18840&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;18480&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;update&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.92, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;19290&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;18870&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;and&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.94, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;19590&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;19230&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;delete&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.89, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;19920&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;19530&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;data&amp;#39;&lt;/span&gt;,
&lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.95, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;20190&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;19890&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;and&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.92, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;20490&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;20250&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;schemas&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.86, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;21000&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;20430&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;in&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.94, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;21210&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;21000&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;their&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.98, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;21510&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;21150&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;database&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.97, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;21900&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;21450&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;developers&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.83, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;23200&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;22420&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;can&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.95, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;23440&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;23200&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;use&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.97, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;23650&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;23410&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;the&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.99, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;23890&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;23590&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;programming&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.97, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;24370&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;23830&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;language&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1&lt;/span&gt;.0, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;24700&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;24310&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;that&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1&lt;/span&gt;.0, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;24880&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;24640&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;they&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.99, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;25060&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;24820&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;are&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.85, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;25210&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;25000&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;comfortable&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.92, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;25780&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;25180&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;with&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1&lt;/span&gt;.0, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;25960&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;25720&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;comfortable&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.94, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;29090&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;28090&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;to&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.84, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;29840&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;29180&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;work&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.95, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;30050&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;29780&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;with&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.98, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;30290&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;30020&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;the&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.69, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;30440&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;30230&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;database&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.98, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;30860&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;30380&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;instead&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;1&lt;/span&gt;.0, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;32780&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;31780&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;of&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.98, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;32900&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;32720&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;writing&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.87, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;33320&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;32870&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;sequel&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.88, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;33860&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;33290&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;statements&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.95, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;34310&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;33800&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;or&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.9, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;34460&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;34250&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;short&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.9, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;34790&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;34430&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;procedures.&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;0&lt;/span&gt;.98, &lt;span class="s1"&gt;&amp;#39;end&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;35270&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;34760&lt;/span&gt;&lt;span class="o"&gt;}]&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;format_text&amp;#39;&lt;/span&gt;: True, &lt;span class="s1"&gt;&amp;#39;webhook_url&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;punctuate&amp;#39;&lt;/span&gt;: True, &lt;span class="s1"&gt;&amp;#39;utterances&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;audio_duration&amp;#39;&lt;/span&gt;: &lt;span class="m"&gt;36&lt;/span&gt;.288, &lt;span class="s1"&gt;&amp;#39;auto_highlights&amp;#39;&lt;/span&gt;: False, &lt;span class="s1"&gt;&amp;#39;word_boost&amp;#39;&lt;/span&gt;: &lt;span class="o"&gt;[]&lt;/span&gt;,
&lt;span class="s1"&gt;&amp;#39;dual_channel&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;audio_start_from&amp;#39;&lt;/span&gt;: None&lt;span class="o"&gt;}&lt;/span&gt;


An object relational mapper is a code library that automates the transfer of data stored &lt;span class="k"&gt;in&lt;/span&gt; a relational database tables into objects that are more commonly used &lt;span class="k"&gt;in&lt;/span&gt; application. Code or MS provide a high level abstraction upon a relational database that allows the developer to write Python code. Instead of sequel to create &lt;span class="nb"&gt;read&lt;/span&gt; update and delete data and schemas &lt;span class="k"&gt;in&lt;/span&gt; their database developers can use the programming language that they are comfortable with comfortable to work with the database instead of writing sequel statements or short procedures.
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;That's a lot of output. The first part contains the results of the 
transcription and the confidence in the accuracy of each word transcribed.
The second part is just the plain text output from the transcription.&lt;/p&gt;
&lt;p&gt;You can take this now take this base code and add it to any application
that needs high quality text-to-speech transcription. If the results
aren't quite good enough for you, check out this tutorial on
&lt;a href="https://docs.assemblyai.com/guides/boosting-accuracy-for-keywords-or-phrases"&gt;boosting accuracy for keywords or phrases&lt;/a&gt;
as well as
&lt;a href="https://docs.assemblyai.com/guides/iab-categorization"&gt;better matching your data with topic detection&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;What now?&lt;/h2&gt;
&lt;p&gt;We just finished building a highly accurate transcription application for recordings.&lt;/p&gt;
&lt;p&gt;Next, try out some of these other related Python tutorials:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/blog/transcribe-recordings-speech-text-assemblyai.html"&gt;How to Transcribe Speech Recordings into Text with Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/blog/report-exceptions-python-scripts-sentry.html"&gt;Reporting Exceptions in Python Scripts with Sentry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/blog/python-basic-data-types-strings.html"&gt;Basic Data Types in Python: Strings&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Questions? Let me know via
&lt;a href="https://github.com/mattmakai/fullstackpython.com/issues"&gt;a GitHub issue ticket on the Full Stack Python repository&lt;/a&gt;,
on Twitter
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;.
If you see an issue or error in this tutorial, please
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/201009-accurate-twilio-voice-call-recording-transcriptions-assemblyai.markdown"&gt;fork the source repository on GitHub&lt;/a&gt;
and submit a pull request with the fix.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Sat, 10 Oct 2020 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2020-10-10:blog/accurate-twilio-voice-call-recording-transcriptions-assemblyai.html</guid></item><item><title>Using Sentry to Handle Python Exceptions in Django Projects</title><link>https://www.fullstackpython.com/blog/sentry-handle-exceptions-django-projects.html</link><description>&lt;p&gt;Web applications built in &lt;a href="/django.html"&gt;Django&lt;/a&gt; can become sprawlingly complex over time, which is one reason why centralized error handling is important. This tutorial will guide you through adding a free, basic Sentry configuration to a new Django project.&lt;/p&gt;
&lt;p&gt;When we're done, you will be able to view centralized error reports in the Sentry dashboard like you see in this screenshot:&lt;/p&gt;
&lt;p&gt;&lt;img src='/img/200823-django-sentry/sentry-dashboard.jpg' width='100%' alt='Sentry dashboard with caught Django exceptions.' class='shot rnd outl'&gt;&lt;/p&gt;
&lt;h2&gt;Tutorial Requirements&lt;/h2&gt;
&lt;p&gt;Throughout this tutorial we are going to use the following dependencies,
which we will install in just a moment. Make sure you also have Python 3,
&lt;a href="https://www.python.org/downloads/"&gt;preferrably 3.7 or newer installed&lt;/a&gt;,
in your environment:&lt;/p&gt;
&lt;p&gt;We will use the following dependencies to complete this
tutorial:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/django.html"&gt;Django&lt;/a&gt; &lt;a href="/web-frameworks.html"&gt;web framework&lt;/a&gt;,
  &lt;a href="https://www.djangoproject.com/download/"&gt;version 3.1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sentry.io/for/python/"&gt;sentry-sdk&lt;/a&gt;,
  &lt;a href="https://pypi.org/project/sentry-sdk/"&gt;version 0.16.5&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All code in this blog post is available open source under the MIT license
on GitHub under the
&lt;a href="https://github.com/fullstackpython/blog-code-examples"&gt;sentry-handle-exceptions-django-projects directory of the blog-code-examples repository&lt;/a&gt;.
Use the source code as you desire for your own projects.&lt;/p&gt;
&lt;h2&gt;Development environment configuration&lt;/h2&gt;
&lt;p&gt;Change into the directory where you keep your Python
&lt;a href="/virtual-environments-virtualenvs-venvs.html"&gt;virtual environments&lt;/a&gt;.
Create a new virtualenv for this project using the following
command.&lt;/p&gt;
&lt;p&gt;Start the Django project by creating a new
&lt;a href="/virtual-environments-virtualenvs-venvs.html"&gt;virtual environment&lt;/a&gt;
using the following command. I recommend using a separate directory
such as &lt;code&gt;~/venvs/&lt;/code&gt; (the tilde is a shortcut for your user's &lt;code&gt;home&lt;/code&gt;
directory) so that you always know where all your virtualenvs are
located.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python3 -m venv ~/venvs/djsentry
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Activate the virtualenv with the &lt;code&gt;activate&lt;/code&gt; shell script:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt; ~/venvs/djsentry/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;After the above command is executed, the command prompt will
change so that the name of the virtualenv is prepended to the
original command prompt format, so if your prompt is simply
&lt;code&gt;$&lt;/code&gt;, it will now look like the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;djsentry&lt;span class="o"&gt;)&lt;/span&gt; $
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Remember, you have to activate your virtualenv in every new terminal
window where you want to use dependencies in the virtualenv.&lt;/p&gt;
&lt;p&gt;We can now install the &lt;a href="https://pypi.org/project/Django/"&gt;Django&lt;/a&gt;
package into the activated but otherwise empty virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install django==3.1 sentry-sdk==0.16.5
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Look for output similar to the following to confirm the appropriate
packages were installed correctly from PyPI.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;djsentry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt; &lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mf"&gt;3.1&lt;/span&gt; &lt;span class="n"&gt;sentry&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;sdk&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mf"&gt;0.16&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;
  &lt;span class="n"&gt;Downloading&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;bd5624546912082a1bd2709d0edc0685f5c7827a278d806a20cf6adea28&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Django&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;7.8&lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;████████████████████████████████&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mf"&gt;7.8&lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt; &lt;span class="mf"&gt;6.3&lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; 
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;sentry&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;sdk&lt;/span&gt;
  &lt;span class="n"&gt;Downloading&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;f4&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;49&lt;/span&gt;&lt;span class="n"&gt;f899856e3a83e02bc88f2c4945aa0bda4f56b804baa9f71e6664a574a2&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sentry_sdk&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.16&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;113&lt;/span&gt;&lt;span class="n"&gt;kB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;████████████████████████████████&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;122&lt;/span&gt;&lt;span class="n"&gt;kB&lt;/span&gt; &lt;span class="mf"&gt;33.7&lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; 
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;asgiref&lt;/span&gt;&lt;span class="o"&gt;~=&lt;/span&gt;&lt;span class="mf"&gt;3.2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;d5&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;eb&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;64725&lt;/span&gt;&lt;span class="n"&gt;b25f991010307fd18a9e0c1f0e6dff2f03622fc4bcbcdb2244f60d6&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;asgiref&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;sqlparse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ee&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;6e821932&lt;/span&gt;&lt;span class="n"&gt;f413a5c4b76be9c5936e313e4fc626b33f16e027866e1d60f588&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sqlparse&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;pytz&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;4&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;a4&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;879454&lt;/span&gt;&lt;span class="n"&gt;d49688e2fad93e59d7d4efda580b783c745fd2ec2a3adf87b0808d&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pytz&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2020.1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;urllib3&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.10&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="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sentry&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;9&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;f0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;a391d1463ebb1b233795cabfc0ef38d3db4442339de68f847026199e69d7&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;urllib3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.25&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;certifi&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sentry&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;c4&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="n"&gt;c4fe722df5343c33226f0b4e0bb042e4dc13483228b4718baf286f86d87&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;certifi&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2020.6&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;span class="n"&gt;Installing&lt;/span&gt; &lt;span class="n"&gt;collected&lt;/span&gt; &lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;asgiref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sqlparse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pytz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urllib3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;certifi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sentry&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;sdk&lt;/span&gt;
&lt;span class="n"&gt;Successfully&lt;/span&gt; &lt;span class="n"&gt;installed&lt;/span&gt; &lt;span class="n"&gt;asgiref&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="n"&gt;certifi&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2020.6&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.1&lt;/span&gt; &lt;span class="n"&gt;pytz&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2020.1&lt;/span&gt; &lt;span class="n"&gt;sentry&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;sdk&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.16&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="n"&gt;sqlparse&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;urllib3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.25&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We can get started coding the application now that we have all of our
required dependencies installed.&lt;/p&gt;
&lt;h2&gt;Coding the initial application&lt;/h2&gt;
&lt;p&gt;We have everything we need to start building our application.&lt;/p&gt;
&lt;p&gt;We can use the &lt;a href="/django.html"&gt;Django&lt;/a&gt; &lt;code&gt;django-admin&lt;/code&gt; tool to create
the boilerplate code structure to get our project started.
Change into the directory where you develop your applications. For
example, I typically use &lt;code&gt;/Users/matt/devel/py/&lt;/code&gt; for all of my
Python projects. Then run the following command to start a Django
project named &lt;code&gt;djsentry&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;django-admin.py startproject djsentry
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Note that in this tutorial we are using the same name for both the
virtualenv and the Django project directory, but they can be
different names if you prefer that for organizing your own projects.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;django-admin&lt;/code&gt; command creates a directory named &lt;code&gt;djsentry&lt;/code&gt;
along with several subdirectories that you should be familiar with
if you have previously worked with Django.&lt;/p&gt;
&lt;p&gt;Change directories into the new project.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;cd djsentry
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Create a new Django app within &lt;code&gt;djsentry&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python manage.py startapp errors
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Django will generate a new folder named &lt;code&gt;errors&lt;/code&gt; for the project.
We should update the URLs so the app is accessible before we write
our &lt;code&gt;views.py&lt;/code&gt; code.&lt;/p&gt;
&lt;p&gt;Open &lt;code&gt;djsentry/djsentry/urls.py&lt;/code&gt;. Add the highlighted
lines so that URL resolver will check the &lt;code&gt;errors&lt;/code&gt; app
for additional routes to match with URLs that are requested of
this Django application.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# djsentry/djsentry/urls.py&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;include&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;


&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;errors.urls&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;admin/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Save &lt;code&gt;djsentry/djsentry/urls.py&lt;/code&gt; and open
&lt;code&gt;djsentry/djsentry/settings.py&lt;/code&gt;.
Add the &lt;code&gt;errors&lt;/code&gt; app to &lt;code&gt;settings.py&lt;/code&gt; by inserting
the highlighted line:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# djsentry/djsentry/settings.py&lt;/span&gt;
&lt;span class="c1"&gt;# Application definition&lt;/span&gt;

&lt;span class="n"&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.admin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.auth&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.contenttypes&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.sessions&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.messages&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.staticfiles&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="s1"&gt;&amp;#39;errors&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Make sure you change the default &lt;code&gt;DEBUG&lt;/code&gt; and &lt;code&gt;SECRET_KEY&lt;/code&gt;
values in &lt;code&gt;settings.py&lt;/code&gt; before you deploy any code to production. Secure
your app properly with the information from the Django
&lt;a href="https://docs.djangoproject.com/en/stable/howto/deployment/checklist/"&gt;production deployment checklist&lt;/a&gt;
so that you do not add your project to the list of hacked applications
on the web.&lt;/p&gt;
&lt;p&gt;Save and close &lt;code&gt;settings.py&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next change into the &lt;code&gt;djsentry/errors&lt;/code&gt; directory. Create
a new file named &lt;code&gt;urls.py&lt;/code&gt; to contain routes for the &lt;code&gt;errors&lt;/code&gt; app.&lt;/p&gt;
&lt;p&gt;Add all of these lines to the empty &lt;code&gt;djsentry/errors/urls.py&lt;/code&gt;
file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# djsentry/errors/urls.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&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="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;^$&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;errors_index&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="s2"&gt;&amp;quot;index&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Save &lt;code&gt;djsentry/errors/urls.py&lt;/code&gt;. Open
&lt;code&gt;djsentry/errors/views.py&lt;/code&gt; to add the
following two highlighted lines. You can keep the boilerplate comment
"# Create your views here." or delete like I usually do.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# djsentry/errors/views.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.shortcuts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;


&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;errors_index&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;~~&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;render&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="s1"&gt;&amp;#39;index.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Next, create a directory for your template files named &lt;code&gt;templates&lt;/code&gt; under
the &lt;code&gt;djmaps/maps&lt;/code&gt; app directory.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;mkdir templates
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Create a new file named &lt;code&gt;index.html&lt;/code&gt; within
&lt;code&gt;djsentry/errors/templates&lt;/code&gt; that contains the
following &lt;a href="/django-templates.html"&gt;Django template language&lt;/a&gt; markup.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;First step for errors&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
   &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Hello, world!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We can test out this static page to make sure all of our code is
correct before we start adding the meat of the functionality to
the project. Change into the base directory of your Django project
where the &lt;code&gt;manage.py&lt;/code&gt; file is located. Execute the development
server with the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python manage.py runserver
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The Django development server should start up with no issues other than
an unapplied migrations warning.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;Watching&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="n"&gt;changes&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;StatReloader&lt;/span&gt;
&lt;span class="n"&gt;Performing&lt;/span&gt; &lt;span class="n"&gt;system&lt;/span&gt; &lt;span class="n"&gt;checks&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;System&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="n"&gt;identified&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;issues&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;silenced&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;

&lt;span class="n"&gt;You&lt;/span&gt; &lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt; &lt;span class="n"&gt;unapplied&lt;/span&gt; &lt;span class="n"&gt;migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Your&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="n"&gt;may&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;work&lt;/span&gt; &lt;span class="n"&gt;properly&lt;/span&gt; &lt;span class="n"&gt;until&lt;/span&gt; &lt;span class="n"&gt;you&lt;/span&gt; &lt;span class="n"&gt;apply&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;migrations&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contenttypes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sessions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;Run&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;python manage.py migrate&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;apply&lt;/span&gt; &lt;span class="n"&gt;them&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;August&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2020&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;57&lt;/span&gt;
&lt;span class="n"&gt;Django&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="mf"&gt;3.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;using&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;djsentry.settings&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;Starting&lt;/span&gt; &lt;span class="n"&gt;development&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="mf"&gt;127.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="n"&gt;Quit&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;CONTROL&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Open a web browser and go to &lt;code&gt;localhost:8000&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/visuals/first-step.jpg" width="100%" class="shot rnd outl" alt="Plain old HTML page saying 'Hello, world!'."&gt;&lt;/p&gt;
&lt;p&gt;Our code works, but it sure does not do much yet. Let's add
sentry-sdk so we can understand how it works.&lt;/p&gt;
&lt;h2&gt;Adding Sentry and the sentry-sdk library&lt;/h2&gt;
&lt;p&gt;We can now add Sentry and test it with a bunch of errors to make sure it
is working properly.&lt;/p&gt;
&lt;p&gt;Sentry can either be &lt;a href="https://github.com/getsentry/onpremise"&gt;self-hosted&lt;/a&gt; or
used as a cloud service through &lt;a href="https://sentry.io"&gt;Sentry.io&lt;/a&gt;. In this 
tutorial we will use the cloud hosted version because it's faster than
setting up your own server as well as free for smaller projects.&lt;/p&gt;
&lt;p&gt;Go to &lt;a href="https://sentry.io"&gt;Sentry.io's homepage&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200525-sentry/sentry-homepage.jpg" width="100%" class="shot rnd outl" alt="Sentry.io homepage where you can sign up for a free account."&gt;&lt;/p&gt;
&lt;p&gt;Sign into your account or sign up for a new free account. You will be at
the main account dashboard after logging in or completing the Sentry sign
up process.&lt;/p&gt;
&lt;p&gt;There are no errors logged on our account dashboard yet, which is as 
expected because we have not yet connected our account to our Django
project.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200525-sentry/sentry-empty-dashboard.jpg" width="100%" class="shot rnd outl" alt="Blank Sentry account dashboard."&gt;&lt;/p&gt;
&lt;p&gt;Create a new Sentry Project just for this application by clicking
"Projects" in the left sidebar to go to the Projects page.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200525-sentry/create-project.jpg" width="100%" class="shot rnd outl" alt="Button to create a new Sentry project."&gt;&lt;/p&gt;
&lt;p&gt;On the Projects page, click the "Create Project" button in the top right
corner of the page.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200525-sentry/create-new-project-screen.jpg" width="100%" class="shot rnd outl" alt="Create a new Sentry project."&gt;&lt;/p&gt;
&lt;p&gt;You can either choose "Django" or select "Python". I usually just choose 
"Python" if I do not yet know what framework I'll be using to build my 
application. Next, give your new Project a name and then press the "Create 
Project" button. Our new project is ready to integrate with our Python code.&lt;/p&gt;
&lt;p&gt;We need the unique identifier for our account and project to authorize our
Python code to send errors to this Sentry instance. The easiest way to get
what we need is to go to the 
&lt;a href="https://docs.sentry.io/platforms/python/django/"&gt;Python+Django documentation page&lt;/a&gt;
and read how to configure the SDK.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200525-sentry/python-sentry-quickstart.jpg" width="100%" class="shot rnd outl" alt="The Sentry docs show you exactly what you need to export to connect to your account."&gt; &lt;/p&gt;
&lt;p&gt;Copy the string parameter for the &lt;code&gt;init&lt;/code&gt; method and set it
&lt;a href="https://www.twilio.com/blog/2017/01/how-to-set-environment-variables.html"&gt;as an environment variable&lt;/a&gt;
rather than having it exposed in your project's code.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;SENTRY_DSN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://yourkeygoeshere.ingest.sentry.io/project-number&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Make sure to replace "yourkeygoeshere" with your own unique identifier
and "project-number" with the ID that matches the project you just
created.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Check that the &lt;code&gt;SENTRY_DSN&lt;/code&gt; is set properly in your shell using the &lt;code&gt;echo&lt;/code&gt;
command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$SENTRY_DSN&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Next, update &lt;code&gt;settings.py&lt;/code&gt; with the following highlighted new lines:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# settings.py&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sentry_sdk&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sentry_sdk.integrations.django&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DjangoIntegration&lt;/span&gt;


&lt;span class="c1"&gt;# Build paths inside the project like this: BASE_DIR / &amp;#39;subdir&amp;#39;.&lt;/span&gt;
&lt;span class="n"&gt;BASE_DIR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;At the bottom of the file after the line with &lt;code&gt;STATIC_URL&lt;/code&gt;, add the
Sentry configuration:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;STATIC_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/static/&amp;#39;&lt;/span&gt;

&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="n"&gt;sentry_sdk&lt;/span&gt;&lt;span class="o"&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;dsn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SENTRY_DSN&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="n"&gt;integrations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;DjangoIntegration&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;

&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="c1"&gt;# If you wish to associate users to errors (assuming you are using&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="c1"&gt;# django.contrib.auth) you may enable sending PII data.&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="n"&gt;send_default_pii&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now that we have the configuration in place we can deliberately make some
errors happen to test the connection to Sentry's service.&lt;/p&gt;
&lt;h2&gt;Testing Sentry's error catching&lt;/h2&gt;
&lt;p&gt;We'll change some of the existing code to deliberately throw exceptions
to make sure everything is working properly.&lt;/p&gt;
&lt;p&gt;Start by opening &lt;code&gt;errors/views.py&lt;/code&gt; and updating it with one new
highlighted line that will automatically throw a generic Exception
when this function is called.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# djsentry/errors/views.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.shortcuts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;errors_index&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;~~&lt;/span&gt;    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;testing exception&amp;#39;&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;render&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="s1"&gt;&amp;#39;index.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Go to &lt;code&gt;localhost:8000&lt;/code&gt; in your browser and you will immediately get this
exception page when running the development server:&lt;/p&gt;
&lt;p&gt;&lt;img src='/img/200823-django-sentry/exception-thrown-debug-page.jpg' width='100%' alt='Django development mode debug page when the Exception is raised.' class='shot rnd outl'&gt;&lt;/p&gt;
&lt;p&gt;We can also try out code that does not simply raise an exception but instead
will definitely create one when executed, like this division by zero 
operation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# djsentry/errors/views.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.shortcuts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;errors_index&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;~~&lt;/span&gt;    &lt;span class="n"&gt;division_by_zero&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;render&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="s1"&gt;&amp;#39;index.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img src='/img/200823-django-sentry/division-by-zero-exception.jpg' width='100%' alt='Django development mode debug page when the Exception occurs.' class='shot rnd outl'&gt;&lt;/p&gt;
&lt;p&gt;If those exceptions both appear in the Sentry dashboard like this, you're all
set:&lt;/p&gt;
&lt;p&gt;&lt;img src='/img/200823-django-sentry/sentry-dashboard.jpg' width='100%' alt='Sentry dashboard with the exceptions that just occurred..' class='shot rnd outl'&gt;&lt;/p&gt;
&lt;p&gt;The above exceptions were just a couple of generic ways to test that everything
is working to send error information to Sentry. This configuration will
also handle the 
&lt;a href="https://docs.djangoproject.com/en/stable/ref/exceptions/"&gt;many other Django exceptions&lt;/a&gt;
you are likely to see when building the rest of your Django project.&lt;/p&gt;
&lt;h2&gt;Additional resources&lt;/h2&gt;
&lt;p&gt;We just finished building a Django project that uses Sentry for 
centralized error handling.&lt;/p&gt;
&lt;p&gt;Next, try out some of these other related &lt;a href="/django.html"&gt;Django&lt;/a&gt; tutorials:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/blog/track-daily-user-data-django-user-visit.html"&gt;Tracking Daily User Data in Django with django-user-visit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/blog/bootstrap-4-django-template.html"&gt;Quickly Use Bootstrap 4 in a Django Template with a CDN&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/blog/maps-django-web-applications-projects-mapbox.html"&gt;How to Add Maps to Django Web App Projects with Mapbox&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you have questions or comments about this tutorial, please contact me
via Twitter &lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;, or
on GitHub &lt;a href="https://github.com/mattmakai"&gt;@mattmakai&lt;/a&gt;.
See something wrong with this post? Fork
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/200823-sentry-handle-exceptions-django-projects.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Sun, 23 Aug 2020 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2020-08-23:blog/sentry-handle-exceptions-django-projects.html</guid></item><item><title>How to Transcribe Speech Recordings into Text with Python</title><link>https://www.fullstackpython.com/blog/transcribe-recordings-speech-text-assemblyai.html</link><description>&lt;p&gt;When you have a recording where one or more people are talking, it's useful
to have a highly accurate and automated way to extract the spoken words into
text. Once you have the text, you can use it for further analysis or
as an accessibility feature.&lt;/p&gt;
&lt;p&gt;In this tutorial, we'll use a high accuracy speech-to-text web application
programming interface called &lt;a href="https://www.assemblyai.com/"&gt;AssemblyAI&lt;/a&gt; to
extract text from an MP3 recording (many other formats are supported as well).&lt;/p&gt;
&lt;p&gt;With the code from this tutorial, you will be able to take an audio file
that contains speech
&lt;a href="https://www.fullstackpython.com/audio/fsp-object-relational-mappers.mp3"&gt;such as this example one I recorded&lt;/a&gt;
and output a highly accurate text transcription like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;An object relational mapper is a code library that automates the transfer of 
data stored in relational, databases into objects that are more commonly used
in application code or EMS are useful because they provide a high level 
abstraction upon a relational database that allows developers to write Python 
code instead of sequel to create read update and delete, data and schemas in 
their database. Developers can use the programming language. They are 
comfortable with to work with a database instead of writing SQL...

(the text goes on from here but I abbreviated it at this point)
&lt;/pre&gt;&lt;/div&gt;


&lt;h2&gt;Tutorial requirements&lt;/h2&gt;
&lt;p&gt;Throughout this tutorial we are going to use the following dependencies,
which we will install in just a moment. Make sure you also have Python 3,
&lt;a href="https://www.python.org/downloads/"&gt;preferably 3.6 or newer installed&lt;/a&gt;,
in your environment:&lt;/p&gt;
&lt;p&gt;We will use the following dependencies to complete this
tutorial:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://requests.readthedocs.io/"&gt;requests&lt;/a&gt;
  &lt;a href="https://pypi.org/project/requests/"&gt;version 2.24.0&lt;/a&gt; to make HTTP requests to
  the &lt;a href="https://www.assemblyai.com/"&gt;AssemblyAI&lt;/a&gt; speech-to-text
  &lt;a href="/application-programming-interfaces.html"&gt;API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;An &lt;a href="https://www.assemblyai.com/"&gt;AssemblyAI&lt;/a&gt; account, 
  which you can sign up for a 
  &lt;a href="https://app.assemblyai.com/login/"&gt;free API access key here&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All code in this blog post is available open source under the MIT license
on GitHub under the
&lt;a href="https://github.com/fullstackpython/blog-code-examples"&gt;transcribe-speech-text-script directory of the blog-code-examples repository&lt;/a&gt;.
Use the source code as you desire for your own projects.&lt;/p&gt;
&lt;h2&gt;Setting up the development environment&lt;/h2&gt;
&lt;p&gt;Change into the directory where you keep your Python
&lt;a href="/virtual-environments-virtualenvs-venvs.html"&gt;virtual environments&lt;/a&gt;.
I keep mine in a subdirectory named &lt;code&gt;venvs&lt;/code&gt; within my user's home 
directory. Create a new virtualenv for this project using the following
command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python3 -m venv ~/venvs/pytranscribe
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Activate the virtualenv with the &lt;code&gt;activate&lt;/code&gt; shell script:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt; ~/venvs/pytranscribe/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;After the above command is executed, the command prompt will
change so that the name of the virtualenv is prepended to the
original command prompt format, so if your prompt is simply
&lt;code&gt;$&lt;/code&gt;, it will now look like the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;pytranscribe&lt;span class="o"&gt;)&lt;/span&gt; $
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Remember, you have to activate your virtualenv in every new terminal
window where you want to use dependencies in the virtualenv.&lt;/p&gt;
&lt;p&gt;We can now install the &lt;code&gt;requests&lt;/code&gt; package into the activated 
but otherwise empty virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install requests==2.24.0
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Look for output similar to the following to confirm the appropriate
packages were installed correctly from PyPI.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(pytranscribe) $ pip install requests==2.24.0
Collecting requests==2.24.0
  Using cached https://files.pythonhosted.org/packages/45/1e/0c169c6a5381e241ba7404532c16a21d86ab872c9bed8bdcd4c423954103/requests-2.24.0-py2.py3-none-any.whl
Collecting certifi&amp;gt;=2017.4.17 (from requests==2.24.0)
  Using cached https://files.pythonhosted.org/packages/5e/c4/6c4fe722df5343c33226f0b4e0bb042e4dc13483228b4718baf286f86d87/certifi-2020.6.20-py2.py3-none-any.whl
Collecting urllib3!=1.25.0,!=1.25.1,&amp;lt;1.26,&amp;gt;=1.21.1 (from requests==2.24.0)
  Using cached https://files.pythonhosted.org/packages/9f/f0/a391d1463ebb1b233795cabfc0ef38d3db4442339de68f847026199e69d7/urllib3-1.25.10-py2.py3-none-any.whl
Collecting chardet&amp;lt;4,&amp;gt;=3.0.2 (from requests==2.24.0)
  Using cached https://files.pythonhosted.org/packages/bc/a9/01ffebfb562e4274b6487b4bb1ddec7ca55ec7510b22e4c51f14098443b8/chardet-3.0.4-py2.py3-none-any.whl
Collecting idna&amp;lt;3,&amp;gt;=2.5 (from requests==2.24.0)
  Using cached https://files.pythonhosted.org/packages/a2/38/928ddce2273eaa564f6f50de919327bf3a00f091b5baba8dfa9460f3a8a8/idna-2.10-py2.py3-none-any.whl
Installing collected packages: certifi, urllib3, chardet, idna, requests
Successfully installed certifi-2020.6.20 chardet-3.0.4 idna-2.10 requests-2.24.0 urllib3-1.25.10
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We have all of our required dependencies installed so we can get started 
coding the application.&lt;/p&gt;
&lt;h2&gt;Uploading, initiating and transcribing audio&lt;/h2&gt;
&lt;p&gt;We have everything we need to start building our application that
will transcribe audio into text. We're going to build this
application in three files:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/fullstackpython/blog-code-examples/blob/master/transcribe-speech-text-script/upload_audio_file.py"&gt;upload_audio_file.py&lt;/a&gt;: 
  uploads your audio file to a secure place on AssemblyAI's service so
  it can be access for processing. If your audio file is already accessible
  with a public URL, you don't need to do this step, you can just follow 
  &lt;a href="https://docs.assemblyai.com/overview/getting-started"&gt;this quickstart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fullstackpython/blog-code-examples/blob/master/transcribe-speech-text-script/initiate_transcription.py"&gt;initiate_transcription.py&lt;/a&gt;:
  tells the API which file to transcribe and to start immediately&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fullstackpython/blog-code-examples/blob/master/transcribe-speech-text-script/get_transcription.py"&gt;get_transcription.py&lt;/a&gt;:
  prints the status of the transcription if it is still processing, or
  displays the results of the transcription when the process is complete&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Create a new directory named &lt;code&gt;pytranscribe&lt;/code&gt; to store these files as 
we write them. Then change into the new project directory.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;mkdir pytranscribe
cd pytranscribe
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We also need to export our AssemblyAI API key as an environment variable.
&lt;a href="https://app.assemblyai.com/login/"&gt;Sign up for an AssemblyAI account&lt;/a&gt; 
and log in to the 
&lt;a href="https://app.assemblyai.com/dashboard/"&gt;AssemblyAI dashboard&lt;/a&gt;, then
copy "Your API token" as shown in this screenshot:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200809-transcription-assemblyai/assemblyai-dashboard.png" width="100%" alt="AssemblyAI dashboard." class="shot rnd outl"&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;ASSEMBLYAI_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-api-key-here
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Note that you must use the &lt;code&gt;export&lt;/code&gt; command in every command line window
that you want this key to be accessible. The scripts we are writing will
not be able to access the API if you do not have the token exported as
&lt;code&gt;ASSEMBLYAI_KEY&lt;/code&gt; in the environment you are running the script.&lt;/p&gt;
&lt;p&gt;Now that we have our project directory created and the API key set as an
environment variable, let's move on to writing the code for the first file 
that will upload audio files to the AssemblyAI service.&lt;/p&gt;
&lt;h2&gt;Uploading the audio file for transcription&lt;/h2&gt;
&lt;p&gt;Create a new file named &lt;code&gt;upload_audio_file.py&lt;/code&gt; and place the following
code in it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;argparse&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;


&lt;span class="n"&gt;API_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://api.assemblyai.com/v2/&amp;quot;&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;upload_file_to_api&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Checks for a valid file and then uploads it to AssemblyAI&lt;/span&gt;
&lt;span class="sd"&gt;    so it can be saved to a secure URL that only that service can access.&lt;/span&gt;
&lt;span class="sd"&gt;    When the upload is complete we can then initiate the transcription&lt;/span&gt;
&lt;span class="sd"&gt;    API call.&lt;/span&gt;
&lt;span class="sd"&gt;    Returns the API JSON if successful, or None if file does not exist.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&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;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5242880&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;rb&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk_size&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;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;break&lt;/span&gt;
                &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;

    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;authorization&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ASSEMBLYAI_KEY&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;API_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;upload&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&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;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;read_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&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;response&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;/pre&gt;&lt;/div&gt;


&lt;p&gt;The above code imports the &lt;code&gt;argparse&lt;/code&gt;, &lt;code&gt;os&lt;/code&gt; and &lt;code&gt;requests&lt;/code&gt; packages
so that we can use them in this script. The &lt;code&gt;API_URL&lt;/code&gt; is a constant
that has the base URL of the AssemblyAI service. We define the
&lt;code&gt;upload_file_to_api&lt;/code&gt; function with a single argument, &lt;code&gt;filename&lt;/code&gt;
that should be a string with the absolute path to a file and its
filename.&lt;/p&gt;
&lt;p&gt;Within the function, we check that the file exists, then use Request's 
&lt;a href="https://requests.readthedocs.io/en/master/user/advanced/#chunk-encoded-requests"&gt;chunked transfer encoding&lt;/a&gt;
to stream large files to the AssemblyAI API.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;os&lt;/code&gt; module's &lt;code&gt;getenv&lt;/code&gt; function reads the API that was set on the 
command line using the &lt;code&gt;export&lt;/code&gt; command with the &lt;code&gt;getenv&lt;/code&gt;. Make sure
that you use that &lt;code&gt;export&lt;/code&gt; command in the terminal where you are
running this script otherwise that &lt;code&gt;ASSEMBLYAI_KEY&lt;/code&gt; value will be
blank. When in doubt, use &lt;code&gt;echo $ASSEMBLY_AI&lt;/code&gt; to see if the value
matches your API key.&lt;/p&gt;
&lt;p&gt;To use the &lt;code&gt;upload_file_to_api&lt;/code&gt; function, append the following lines of
code in the &lt;code&gt;upload_audio_file.py&lt;/code&gt; file so that we can properly
execute this code as a script called with the &lt;code&gt;python&lt;/code&gt; command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;filename&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;upload_filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;
    &lt;span class="n"&gt;response_json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;upload_file_to_api&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upload_filename&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;response_json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;file does not exist&amp;quot;&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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;File uploaded to URL: &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response_json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;upload_url&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The code above creates an &lt;code&gt;ArgumentParser&lt;/code&gt; object that allows the
application to obtain a single argument from the command line
to specify the file we want to access, read and upload to the
AssmeblyAI service. &lt;/p&gt;
&lt;p&gt;If the file does not exist, the script will print a message that 
the file couldn't be found. In the happy path where we do find the
correct file at that path, then the file is uploaded using
the code in &lt;code&gt;upload_file_to_api&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;Execute the completed &lt;code&gt;upload_audio_file.py&lt;/code&gt; script by running it on
the command line with the &lt;code&gt;python&lt;/code&gt; command. Replace &lt;code&gt;FULL_PATH_TO_FILE&lt;/code&gt;
with an absolute path to the file you want to upload, such as 
&lt;code&gt;/Users/matt/devel/audio.mp3&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python upload_audio_file.py FULL_PATH_TO_FILE
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Assuming the file is found at the location that you specified, when the 
script finishes uploading the file, it will print a message like this one
with a unique URL:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="n"&gt;uploaded&lt;/span&gt; &lt;span class="n"&gt;to&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;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;cdn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assemblyai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;upload&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;463&lt;/span&gt;&lt;span class="n"&gt;ce27f&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0922&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;ea9&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="n"&gt;ce4&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;3353&lt;/span&gt;&lt;span class="n"&gt;d84b5638&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This URL is not public, it can only be used by the AssemblyAI service, so no
one else will be able to access your file and its contents except for you
and their transcription API.&lt;/p&gt;
&lt;p&gt;The part that is important is the last section of the URL, in this example
it is &lt;code&gt;463ce27f-0922-4ea9-9ce4-3353d84b5638&lt;/code&gt;. Save that unique identifier 
because we need to pass it into the next script that initiates the 
transcription service.&lt;/p&gt;
&lt;h2&gt;Initiate transcription&lt;/h2&gt;
&lt;p&gt;Next, we'll write some code to kick off the transcription. Create a
new file named &lt;code&gt;initiate_transcription.py&lt;/code&gt;. Add the following
code to the new file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;argparse&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;


&lt;span class="n"&gt;API_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://api.assemblyai.com/v2/&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;CDN_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://cdn.assemblyai.com/&amp;quot;&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initiate_transcription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Sends a request to the API to transcribe a specific&lt;/span&gt;
&lt;span class="sd"&gt;    file that was previously uploaded to the API. This will&lt;/span&gt;
&lt;span class="sd"&gt;    not immediately return the transcription because it takes&lt;/span&gt;
&lt;span class="sd"&gt;    a moment for the service to analyze and perform the&lt;/span&gt;
&lt;span class="sd"&gt;    transcription, so there is a different function to retrieve&lt;/span&gt;
&lt;span class="sd"&gt;    the results.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;API_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;transcript&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;audio_url&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;CDN_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;upload/&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_id&lt;/span&gt;&lt;span class="p"&gt;)])}&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;authorization&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ASSEMBLYAI_KEY&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;content-type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;application/json&amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&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;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&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;response&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;/pre&gt;&lt;/div&gt;


&lt;p&gt;We have the same imports as the previous script and we've added a 
new constant, &lt;code&gt;CDN_URL&lt;/code&gt; that matches the separate URL where AssemblyAI
stores the uploaded audio files.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;initiate_transcription&lt;/code&gt; function essentially just sets up
a single HTTP request to the AssemblyAI API to start the transcription
process on the audio file at the specific URL passed in. This is why
passing in the &lt;code&gt;file_id&lt;/code&gt; is important: that completes the URL of the
audio file that we are telling AssemblyAI to retrieve.&lt;/p&gt;
&lt;p&gt;Finish the file by appending this code so that it can be easily
invoked from the command line with arguments.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;file_id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;file_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file_id&lt;/span&gt;
    &lt;span class="n"&gt;response_json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;initiate_transcription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response_json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Start the script by running the &lt;code&gt;python&lt;/code&gt; command on the 
&lt;code&gt;initiate_transcription&lt;/code&gt; file and pass in the unique file identifier
you saved from the previous step.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# the FILE_IDENTIFIER is returned in the previous step and will&lt;/span&gt;
&lt;span class="c1"&gt;# look something like this: 463ce27f-0922-4ea9-9ce4-3353d84b5638&lt;/span&gt;
python initiate_transcription.py FILE_IDENTIFIER
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The API will send back a JSON response that this script prints to
the command line.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;audio_end_at&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;acoustic_model&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;assemblyai_default&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;: None, 
 &lt;span class="s1"&gt;&amp;#39;audio_url&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;https://cdn.assemblyai.com/upload/463ce27f-0922-4ea9-9ce4-3353d84b5638&amp;#39;&lt;/span&gt;, 
 &lt;span class="s1"&gt;&amp;#39;speed_boost&amp;#39;&lt;/span&gt;: False, &lt;span class="s1"&gt;&amp;#39;language_model&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;assemblyai_default&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;redact_pii&amp;#39;&lt;/span&gt;: False, 
 &lt;span class="s1"&gt;&amp;#39;confidence&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;webhook_status_code&amp;#39;&lt;/span&gt;: None, 
 &lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;gkuu2krb1-8c7f-4fe3-bb69-6b14a2cac067&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;status&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;queued&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;boost_param&amp;#39;&lt;/span&gt;: None, 
 &lt;span class="s1"&gt;&amp;#39;words&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;format_text&amp;#39;&lt;/span&gt;: True, &lt;span class="s1"&gt;&amp;#39;webhook_url&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;punctuate&amp;#39;&lt;/span&gt;: True, 
 &lt;span class="s1"&gt;&amp;#39;utterances&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;audio_duration&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;auto_highlights&amp;#39;&lt;/span&gt;: False, 
 &lt;span class="s1"&gt;&amp;#39;word_boost&amp;#39;&lt;/span&gt;: &lt;span class="o"&gt;[]&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;dual_channel&amp;#39;&lt;/span&gt;: None, &lt;span class="s1"&gt;&amp;#39;audio_start_from&amp;#39;&lt;/span&gt;: None&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Take note of the value of the &lt;code&gt;id&lt;/code&gt; key in the JSON response. This is the
transcription identifier we need to use to retrieve the transcription result.
In this example, it is &lt;code&gt;gkuu2krb1-8c7f-4fe3-bb69-6b14a2cac067&lt;/code&gt;. Copy the 
transcription identifier in your own response because we will need it to 
check when the transcription process has completed in the next step.&lt;/p&gt;
&lt;h2&gt;Retrieving the transcription result&lt;/h2&gt;
&lt;p&gt;We have uploaded and begun the transcription process, so let's get the
result as soon as it is ready. &lt;/p&gt;
&lt;p&gt;How long it takes to get the results back can depend on the size of the file,
so this next script will send an HTTP request to the API and report back 
the status of the transcription, or print the output if it's complete.&lt;/p&gt;
&lt;p&gt;Create a third Python file named &lt;code&gt;get_transcription.py&lt;/code&gt; and put the following
code into it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;argparse&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;


&lt;span class="n"&gt;API_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://api.assemblyai.com/v2/&amp;quot;&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_transcription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transcription_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Requests the transcription from the API and returns the JSON&lt;/span&gt;
&lt;span class="sd"&gt;    response.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;API_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;transcript/&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transcription_id&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;authorization&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ASSEMBLYAI_KEY&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&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;response&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="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;transcription_id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;transcription_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transcription_id&lt;/span&gt;
    &lt;span class="n"&gt;response_json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_transcription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transcription_id&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;response_json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;status&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;completed&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response_json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;words&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot; &amp;quot;&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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;current status of transcription request: &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="n"&gt;response_json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;status&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The code above has the same imports as the other scripts. In this
new &lt;code&gt;get_transcription&lt;/code&gt; function, we simply call the AssemblyAI API
with our API key and the &lt;em&gt;transcription identifier&lt;/em&gt; from the previous
step (not the file identifier). We retrieve the JSON response and
return it.&lt;/p&gt;
&lt;p&gt;In the main function we handle the transcription identifier that
is passed in as a command line argument and pass it into the
&lt;code&gt;get_transcription&lt;/code&gt; function. If the response JSON from the
&lt;code&gt;get_transcription&lt;/code&gt; function contains a &lt;code&gt;completed&lt;/code&gt; status then we
print the results of the transcription. Otherwise, print the
current status which is either &lt;code&gt;queued&lt;/code&gt; or &lt;code&gt;processing&lt;/code&gt; before
it is &lt;code&gt;completed&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Call the script using the command line and the transcription identifier
from the previous section:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python get_transcription.py TRANSCRIPTION_ID
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If the service has not yet started working on the transcript then it
will return &lt;code&gt;queued&lt;/code&gt; like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;current status of transcription request: queued
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;When the service is currently working on the audio file it will
return &lt;code&gt;processing&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;current status of transcription request: processing
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;When the process is completed, our script will return the text of
the transcription, like you see here:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;An object relational mapper is a code library that automates the transfer of 
data stored &lt;span class="k"&gt;in&lt;/span&gt; relational, databases into objects that are more commonly used
&lt;span class="k"&gt;in&lt;/span&gt; application code or EMS are useful because they provide a high level 

...&lt;span class="o"&gt;(&lt;/span&gt;output abbreviated&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;That's it, we've got our transcription! &lt;/p&gt;
&lt;p&gt;You may be wondering what to do if the accuracy isn't where you need 
it to be for your situation. That is where
&lt;a href="https://docs.assemblyai.com/guides/boosting-accuracy-for-keywords-or-phrases"&gt;boosting accuracy for keywords or phrases&lt;/a&gt;
comes in. You can use either of those two methods to boost the accuracy
of your recordings to an acceptable level for your situation.&lt;/p&gt;
&lt;h2&gt;What's next?&lt;/h2&gt;
&lt;p&gt;We just finished writing some scripts that call the AssemblyAI API to
transcribe recordings with speech into text output.&lt;/p&gt;
&lt;p&gt;Next, take a look at some of their more advanced documentation that goes
beyond the basics in this tutorial:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.assemblyai.com/faqs/supported-file-formats"&gt;Supported file formats&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.assemblyai.com/guides/transcribing-dual-channel-stereo-recordings"&gt;Transcribing dual channel/stereo recordings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.assemblyai.com/guides/getting-speaker-labels-speaker-diarization"&gt;Getting speaker labels (speaker diarization)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Questions? Let me know via an issue ticket on
&lt;a href="https://github.com/mattmakai/fullstackpython.com/issues"&gt;the Full Stack Python repository&lt;/a&gt;,
on Twitter
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;.
See something wrong with this post? Fork
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/200809-transcribe-recordings-speech-text-assemblyai.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Mon, 13 Sep 2021 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2020-08-09:blog/transcribe-recordings-speech-text-assemblyai.html</guid></item><item><title>Tracking Daily User Data in Django with django-user-visit</title><link>https://www.fullstackpython.com/blog/track-daily-user-data-django-user-visit.html</link><description>&lt;p&gt;It can be tedious to figure out what data to track, create data models
and build &lt;a href="https://docs.djangoproject.com/en/stable/topics/http/middleware/"&gt;middleware&lt;/a&gt; for your &lt;a href="/django.html"&gt;Django&lt;/a&gt; project if you just want to
collect some basic information about clients that connect to your web application
. Fortunately, the library &lt;a href="https://github.com/yunojuno/django-user-visit"&gt;django-user-visit&lt;/a&gt;
is a handy Django project that quickly handles all
of this complexity for you. In this tutorial, we'll learn
how to use django-user-visit in a new Django project
to add daily visit data tracking to Django projects.&lt;/p&gt;
&lt;p&gt;When we're done, we can view information like the following in the Django Admin:&lt;/p&gt;
&lt;p&gt;&lt;img src='/img/200719-django-user-visit/user-visit-record.png' width='100%' alt='Django Admin with django-user-visit information' class='shot rnd outl'&gt;&lt;/p&gt;
&lt;h2&gt;Project Requirements&lt;/h2&gt;
&lt;p&gt;Ensure you have Python 3 installed, because Python 2 reached its
end-of-life at the beginning of 2020 and is no longer supported.
Preferrably, you should have
&lt;a href="https://www.python.org/downloads/"&gt;Python 3.7 or greater installed&lt;/a&gt;
in your &lt;a href="/development-environments.html"&gt;development environment&lt;/a&gt;.
This tutorial will also use:&lt;/p&gt;
&lt;p&gt;We will use the following dependencies to complete this
tutorial:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/django.html"&gt;Django&lt;/a&gt; &lt;a href="/web-frameworks.html"&gt;web framework&lt;/a&gt;,
  &lt;a href="https://www.djangoproject.com/download/"&gt;version 3.0.8&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/yunojuno/django-user-visit"&gt;django-user-visit&lt;/a&gt;,
  &lt;a href="https://pypi.org/project/django-user-visit/"&gt;version 0.4&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All code in this blog post is available open source under the MIT license
on GitHub under the
&lt;a href="https://github.com/fullstackpython/blog-code-examples"&gt;track-daily-user-data-django-user-visit directory of the blog-code-examples repository&lt;/a&gt;.
Use the source code as you desire for your own projects.&lt;/p&gt;
&lt;h2&gt;Development environment set up&lt;/h2&gt;
&lt;p&gt;Change into the directory where you keep your Python
&lt;a href="/virtual-environments-virtualenvs-venvs.html"&gt;virtual environments&lt;/a&gt;.
Create a new virtualenv for this project using the following
command.&lt;/p&gt;
&lt;p&gt;Start the Django project by creating a new
&lt;a href="/virtual-environments-virtualenvs-venvs.html"&gt;virtual environment&lt;/a&gt;
using the following command. I recommend using a separate directory
such as &lt;code&gt;~/venvs/&lt;/code&gt; (the tilde is a shortcut for your user's &lt;code&gt;home&lt;/code&gt;
directory) so that you always know where all your virtualenvs are
located.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python3 -m venv ~/venvs/djuservisit
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Activate the virtualenv with the &lt;code&gt;activate&lt;/code&gt; shell script:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt; ~/venvs/djuservisit/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;After the above command is executed, the command prompt will
change so that the name of the virtualenv is prepended to the
original command prompt format, so if your prompt is simply
&lt;code&gt;$&lt;/code&gt;, it will now look like the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;djuservisit&lt;span class="o"&gt;)&lt;/span&gt; $
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Remember, you have to activate your virtualenv in every new terminal
window where you want to use dependencies in the virtualenv.&lt;/p&gt;
&lt;p&gt;We can now install the &lt;a href="https://pypi.org/project/Django/"&gt;Django&lt;/a&gt;
package into the activated but otherwise empty virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install django==3.0.8 django-user-visit==0.4
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Look for output similar to the following to confirm the appropriate
packages were installed correctly from PyPI.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;djuservisit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt; &lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;-&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;visit&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mf"&gt;0.4&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ca&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ab&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;5e004&lt;/span&gt;&lt;span class="n"&gt;afa025a6fb640c6e983d4983e6507421ff01be224da79ab7de7a21f&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Django&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;-&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;visit&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mf"&gt;0.4&lt;/span&gt;
  &lt;span class="n"&gt;Downloading&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ef&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;d3ec22c3a897192e267389d6ee59ce1858f5ede262b078f93211aff110e7&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;django_user_visit&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;sqlparse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ee&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;6e821932&lt;/span&gt;&lt;span class="n"&gt;f413a5c4b76be9c5936e313e4fc626b33f16e027866e1d60f588&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sqlparse&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;asgiref&lt;/span&gt;&lt;span class="o"&gt;~=&lt;/span&gt;&lt;span class="mf"&gt;3.2&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;d5&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;eb&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;64725&lt;/span&gt;&lt;span class="n"&gt;b25f991010307fd18a9e0c1f0e6dff2f03622fc4bcbcdb2244f60d6&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;asgiref&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;pytz&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;4&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;a4&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;879454&lt;/span&gt;&lt;span class="n"&gt;d49688e2fad93e59d7d4efda580b783c745fd2ec2a3adf87b0808d&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pytz&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2020.1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;span class="n"&gt;Collecting&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;agents&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;2.1&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;-&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;visit&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;be&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;82e4&lt;/span&gt;&lt;span class="n"&gt;d20a7716d8e5de98b948edcecff9bb237e6718c3831bd92794fe9821&lt;/span&gt;&lt;span class="o"&gt;/&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;agents&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gz&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;ua&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.9&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="n"&gt;from&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;agents&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;2.1&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;-&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;visit&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;d16b08db329fd440eed366d35e4dd7195c9babb4ecac5218f28081522a2&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ua_parser&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.10&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;span class="n"&gt;Installing&lt;/span&gt; &lt;span class="n"&gt;collected&lt;/span&gt; &lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sqlparse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;asgiref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pytz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ua&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;,&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;agents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;-&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;visit&lt;/span&gt;
  &lt;span class="n"&gt;Running&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&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;agents&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt;
&lt;span class="n"&gt;Successfully&lt;/span&gt; &lt;span class="n"&gt;installed&lt;/span&gt; &lt;span class="n"&gt;asgiref&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;-&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;visit&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.4&lt;/span&gt; &lt;span class="n"&gt;pytz&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2020.1&lt;/span&gt; &lt;span class="n"&gt;sqlparse&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;ua&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.10&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&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;agents&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.1&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Our dependencies are installed so we can now create our 
project and start coding.&lt;/p&gt;
&lt;h2&gt;Creating the application&lt;/h2&gt;
&lt;p&gt;We have everything we need to start building our application.&lt;/p&gt;
&lt;p&gt;We can use the &lt;a href="/django.html"&gt;Django&lt;/a&gt; &lt;code&gt;django-admin&lt;/code&gt; tool to create
the boilerplate code structure to get our project started.
Change into the directory where you develop your applications. For
example, I typically use &lt;code&gt;/Users/matt/devel/py/&lt;/code&gt; for all of my
Python projects. Then run the following command to start a Django
project named &lt;code&gt;djuservisit&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;django-admin.py startproject djuservisit
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Note that in this tutorial we are using the same name for both the
virtualenv and the Django project directory, but they can be
different names if you prefer that for organizing your own projects.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;django-admin&lt;/code&gt; command creates a directory named &lt;code&gt;djuservisit&lt;/code&gt;
along with several subdirectories that you should be familiar with
if you have previously worked with Django.&lt;/p&gt;
&lt;p&gt;Change directories into the new project.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;cd djuservisit
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Open &lt;code&gt;djuservisit/djuservisit/settings.py&lt;/code&gt;. Add the &lt;code&gt;user_visits&lt;/code&gt; 
app and its middleware to &lt;code&gt;settings.py&lt;/code&gt; by inserting the two 
highlighted lines:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# djuservisit/djuservisit/settings.py&lt;/span&gt;
&lt;span class="c1"&gt;# Application definition&lt;/span&gt;

&lt;span class="n"&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.admin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.auth&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.contenttypes&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.sessions&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.messages&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.staticfiles&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="s1"&gt;&amp;#39;user_visit&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;MIDDLEWARE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; 
    &lt;span class="s1"&gt;&amp;#39;django.middleware.security.SecurityMiddleware&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.sessions.middleware.SessionMiddleware&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.middleware.common.CommonMiddleware&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.middleware.csrf.CsrfViewMiddleware&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.auth.middleware.AuthenticationMiddleware&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.messages.middleware.MessageMiddleware&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.middleware.clickjacking.XFrameOptionsMiddleware&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="s1"&gt;&amp;#39;user_visit.middleware.UserVisitMiddleware&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Make sure you change the default &lt;code&gt;DEBUG&lt;/code&gt; and &lt;code&gt;SECRET_KEY&lt;/code&gt;
values in &lt;code&gt;settings.py&lt;/code&gt; before you deploy any code to production. Secure
your app properly with the information from the Django
&lt;a href="https://docs.djangoproject.com/en/stable/howto/deployment/checklist/"&gt;production deployment checklist&lt;/a&gt;
so that you do not add your project to the list of hacked applications
on the web.&lt;/p&gt;
&lt;p&gt;Save and close &lt;code&gt;settings.py&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Create a &lt;a href="https://docs.djangoproject.com/en/stable/ref/django-admin/"&gt;Django superuser&lt;/a&gt;
so you can access the Django Admin. Go to the base directory of this project
and use the &lt;code&gt;manage.py&lt;/code&gt; file with the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python manage.py createsuperuser
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Follow the prompts and enter values for the username, email address and
password that you want your local Django superuser to have. Next, we'll
test out how this library works when a user visits a page created by
our Django web app.&lt;/p&gt;
&lt;h2&gt;Testing django-user-visit&lt;/h2&gt;
&lt;p&gt;Let's test out our bare-bones application. Execute the development server
with the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python manage.py runserver
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The Django development server should start up with no issues.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;Watching&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="n"&gt;changes&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;StatReloader&lt;/span&gt;
&lt;span class="n"&gt;Performing&lt;/span&gt; &lt;span class="n"&gt;system&lt;/span&gt; &lt;span class="n"&gt;checks&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;System&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="n"&gt;identified&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;issues&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;silenced&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;July&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2020&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;41&lt;/span&gt;
&lt;span class="n"&gt;Django&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;using&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;djuservisit.settings&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;Starting&lt;/span&gt; &lt;span class="n"&gt;development&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="mf"&gt;127.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="n"&gt;Quit&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;CONTROL&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Open a web browser and go to "http://localhost:8000".&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200719-django-user-visit/django-success.png" width="100%" class="shot rnd outl" alt="Default Django page."&gt;&lt;/p&gt;
&lt;p&gt;That's the default page provided by Django in the absence of any other URLs
to serve at the root URL, but it works for our purposes. &lt;/p&gt;
&lt;p&gt;Go to the Django Admin by changing the URL in your browser to
"http://localhost:8000/admin". The Django Admin login page will appear.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200719-django-user-visit/django-admin-login.png" width="100%" class="shot rnd outl" alt="Django admin default login screen."&gt;&lt;/p&gt;
&lt;p&gt;Enter the username and password of the superuser you just created with
the &lt;code&gt;manage.py&lt;/code&gt; command to log in. Next, you will see the Django admin
dashboard.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200719-django-user-visit/django-admin-dashboard.png" width="100%" class="shot rnd outl" alt="Django admin dashboard."&gt;&lt;/p&gt;
&lt;p&gt;The "User visit log" has already been added to the Admin. Click on
the "User visits" link.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200719-django-user-visit/django-admin-django-user-visit.png" width="100%" class="shot rnd outl" alt="django-user-visit list in the Django admin dashboard."&gt;&lt;/p&gt;
&lt;p&gt;The list of all users that have visited by day will show up.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200719-django-user-visit/django-user-visit-details.png" width="100%" class="shot rnd outl" alt="django-user-visit details page in the Django admin."&gt;&lt;/p&gt;
&lt;p&gt;Click on any of the visits to see more detailed data about the record,
just like you would with any Django Admin extension.&lt;/p&gt;
&lt;p&gt;That library was pretty easy to install for the information that it 
aggregates for you. Next, let's take a closer look at the 
&lt;a href="/django-orm.html"&gt;Django ORM&lt;/a&gt; model that powers this library.&lt;/p&gt;
&lt;h2&gt;Inspecting the django-user-visit model&lt;/h2&gt;
&lt;p&gt;We confirmed that django-user-visit is properly installed. Let's take a closer
look at the model the library provides to store the user data.&lt;/p&gt;
&lt;p&gt;Take a look at the source code for 
&lt;a href="https://github.com/yunojuno/django-user-visit/blob/master/user_visit/models.py"&gt;django-user-visit/user_visit/models.py&lt;/a&gt;
on GitHub. Below is an excerpt with the relevant lines of that source file.
I've highlighted a few lines that will be discussed below the code excerpt.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;## ... source code abbreviated ...&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserVisit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    Record of a user visiting the site on a given day.&lt;/span&gt;
&lt;span class="sd"&gt;    This is used for tracking and reporting - knowing the volume of visitors&lt;/span&gt;
&lt;span class="sd"&gt;    to the site, and being able to report on someone&amp;#39;s interaction with the site.&lt;/span&gt;
&lt;span class="sd"&gt;    We record minimal info required to identify user sessions, plus changes in&lt;/span&gt;
&lt;span class="sd"&gt;    IP and device. This is useful in identifying suspicious activity (multiple&lt;/span&gt;
&lt;span class="sd"&gt;    logins from different locations).&lt;/span&gt;
&lt;span class="sd"&gt;    Also helpful in identifying support issues (as getting useful browser data&lt;/span&gt;
&lt;span class="sd"&gt;    out of users can be very difficult over live chat).&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="o"&gt;~~&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;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;        &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AUTH_USER_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;user_visits&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CASCADE&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;help_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;The time at which the first visit of the day was recorded&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;session_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;help_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Django session identifier&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;remote_addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;help_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;Client IP address (from X-Forwarded-For HTTP header, &amp;quot;&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;or REMOTE_ADDR request property)&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="n"&gt;ua_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;        &lt;span class="s2"&gt;&amp;quot;User agent (raw)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Client User-Agent HTTP header&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UUIDField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;editable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;help_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;MD5 hash generated from request properties&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;help_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;The time at which the database record was created (!=timestamp)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;auto_now_add&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;objects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UserVisitManager&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;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;get_latest_by&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;timestamp&amp;quot;&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__str__&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; visited the site on &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__repr__&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;lt;UserVisit id=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; user_id=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; date=&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;&amp;gt;&amp;quot;&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="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&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="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="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Set hash property and save object.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;md5&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hexdigest&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="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&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="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;user_agent&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;user_agents&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserAgent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Return UserAgent object from the raw user_agent string.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;user_agents&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ua_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;date&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;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Extract the date of the visit from the timestamp.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# see https://github.com/python/typeshed/issues/2928 re. return type&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;md5&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;hashlib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_Hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Generate MD5 hash used to identify duplicate visits.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;        &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;md5&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="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&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;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;  &lt;span class="c1"&gt;# noqa: S303&lt;/span&gt;
        &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_key&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remote_addr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ua_string&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&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;h&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;A few things to note based on the highlighted above: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;UserVisit&lt;/code&gt; model matches up with the
  &lt;a href="https://docs.djangoproject.com/en/stable/ref/contrib/auth/"&gt;Django user model&lt;/a&gt; 
  using the &lt;code&gt;user = models.ForeignKey...&lt;/code&gt; line&lt;/li&gt;
&lt;li&gt;The code uses the &lt;code&gt;save&lt;/code&gt; function to ensure the some of the fields 
  are automatically populated, such as the &lt;code&gt;hash&lt;/code&gt; property using the
  &lt;code&gt;hashlib&lt;/code&gt; module&lt;/li&gt;
&lt;li&gt;This library has a dependency on the 
  &lt;a href="https://pypi.org/project/user-agents/"&gt;user_agents library&lt;/a&gt; to parse the
  &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent"&gt;User-Agent&lt;/a&gt;
  of the browser the client is using&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Reading the source code for libraries like django-user-visit is helpful
not only to know what it is doing under the covers, but also to learn new
ways to code your own applications.&lt;/p&gt;
&lt;p&gt;Take a look at the &lt;a href="/django-code-examples.html"&gt;Django code examples&lt;/a&gt; and
&lt;a href="/django-extensions-plug-ins-related-libraries.html"&gt;Django extensions&lt;/a&gt; pages
to see more projects with good Python example code that you can learn from.&lt;/p&gt;
&lt;h2&gt;Additional resources&lt;/h2&gt;
&lt;p&gt;We just finished building an app that tracks daily user visits with the
django-user-visit library.&lt;/p&gt;
&lt;p&gt;Next, try out some of these other related &lt;a href="/django.html"&gt;Django&lt;/a&gt; tutorials:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/blog/bootstrap-4-django-template.html"&gt;Quickly Use Bootstrap 4 in a Django Template with a CDN&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/blog/maps-django-web-applications-projects-mapbox.html"&gt;How to Add Maps to Django Web App Projects with Mapbox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/blog/monitor-django-projects-web-apps-rollbar.html"&gt;Monitoring Django Projects with Rollbar&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Questions? Let me know via
&lt;a href="https://github.com/mattmakai/fullstackpython.com/issues"&gt;a GitHub issue ticket on the Full Stack Python repository&lt;/a&gt;,
on Twitter
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;.
If you see an issue or error in this tutorial, please
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/200719-track-daily-user-data-django-user-visit.markdown"&gt;fork the source repository on GitHub&lt;/a&gt;
and submit a pull request with the fix.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Sun, 19 Jul 2020 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2020-07-19:blog/track-daily-user-data-django-user-visit.html</guid></item><item><title>Quickly Use Bootstrap 4 in a Django Template with a CDN</title><link>https://www.fullstackpython.com/blog/bootstrap-4-django-template.html</link><description>&lt;p&gt;The &lt;a href="/django.html"&gt;Django&lt;/a&gt; &lt;a href="/web-frameworks.html"&gt;web framework&lt;/a&gt;
makes it easy to render HTML using the &lt;a href="/django-templates.html"&gt;Django template engine&lt;/a&gt;.
However, the default styling on HTML pages usually need a
&lt;a href="/cascading-style-sheets.html"&gt;Cascading Style Sheet (CSS)&lt;/a&gt; framework such as 
Bootstrap to make the design look decent.
In this beginner's tutorial, we'll use the &lt;a href="/bootstrap-css.html"&gt;Bootstrap&lt;/a&gt; 
&lt;a href="/content-delivery-networks-cdns.html"&gt;Content Delivery Network (CDN)&lt;/a&gt;
to quickly add Bootstrap to a rendered HTML page.&lt;/p&gt;
&lt;p&gt;Here is what the &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; element styling will look like at the end
of this tutorial:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200705-bootstrap-4-django-template/hello-world-bootstrap-style.jpg" width="100%" class="shot rnd outl" alt="Bootstrap-enhanced HTML page saying 'Hello, world!'."&gt;&lt;/p&gt;
&lt;h2&gt;Tutorial Requirements&lt;/h2&gt;
&lt;p&gt;Throughout this tutorial we are going to use the following dependencies,
which we will install in just a moment. Make sure you also have Python 3,
&lt;a href="https://www.python.org/downloads/"&gt;preferrably 3.7 or newer installed&lt;/a&gt;,
in your environment:&lt;/p&gt;
&lt;p&gt;We will use the following dependencies to complete this
tutorial:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/django.html"&gt;Django&lt;/a&gt; &lt;a href="/web-frameworks.html"&gt;web framework&lt;/a&gt;,
  &lt;a href="https://www.djangoproject.com/download/"&gt;version 3.0.8&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All code in this blog post is available open source under the MIT license
on GitHub under the
&lt;a href="https://github.com/fullstackpython/blog-code-examples"&gt;bootstrap-4-django-template directory of the blog-code-examples repository&lt;/a&gt;.
Use the source code as you desire for your own projects.&lt;/p&gt;
&lt;h2&gt;Development environment set up&lt;/h2&gt;
&lt;p&gt;Change into the directory where you keep your Python
&lt;a href="/virtual-environments-virtualenvs-venvs.html"&gt;virtual environments&lt;/a&gt;.
Create a new virtualenv for this project using the following
command.&lt;/p&gt;
&lt;p&gt;Start the Django project by creating a new
&lt;a href="/virtual-environments-virtualenvs-venvs.html"&gt;virtual environment&lt;/a&gt;
using the following command. I recommend using a separate directory
such as &lt;code&gt;~/venvs/&lt;/code&gt; (the tilde is a shortcut for your user's &lt;code&gt;home&lt;/code&gt;
directory) so that you always know where all your virtualenvs are
located.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python3 -m venv ~/venvs/djbootstrap4
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Activate the virtualenv with the &lt;code&gt;activate&lt;/code&gt; shell script:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt; ~/venvs/djbootstrap4/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;After the above command is executed, the command prompt will
change so that the name of the virtualenv is prepended to the
original command prompt format, so if your prompt is simply
&lt;code&gt;$&lt;/code&gt;, it will now look like the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;djbootstrap4&lt;span class="o"&gt;)&lt;/span&gt; $
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Remember, you have to activate your virtualenv in every new terminal
window where you want to use dependencies in the virtualenv.&lt;/p&gt;
&lt;p&gt;We can now install the &lt;a href="https://pypi.org/project/Django/"&gt;Django&lt;/a&gt;
package into the activated but otherwise empty virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install django==3.0.8
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Look for output similar to the following to confirm the appropriate
packages were installed correctly from PyPI.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Collecting django
  Using cached https://files.pythonhosted.org/packages/ca/ab/5e004afa025a6fb640c6e983d4983e6507421ff01be224da79ab7de7a21f/Django-3.0.8-py3-none-any.whl
Collecting sqlparse&amp;gt;=0.2.2 (from django)
  Using cached https://files.pythonhosted.org/packages/85/ee/6e821932f413a5c4b76be9c5936e313e4fc626b33f16e027866e1d60f588/sqlparse-0.3.1-py2.py3-none-any.whl
Collecting asgiref~=3.2 (from django)
  Using cached https://files.pythonhosted.org/packages/d5/eb/64725b25f991010307fd18a9e0c1f0e6dff2f03622fc4bcbcdb2244f60d6/asgiref-3.2.10-py3-none-any.whl
Collecting pytz (from django)
  Using cached https://files.pythonhosted.org/packages/4f/a4/879454d49688e2fad93e59d7d4efda580b783c745fd2ec2a3adf87b0808d/pytz-2020.1-py2.py3-none-any.whl
Installing collected packages: sqlparse, asgiref, pytz, django
Successfully installed asgiref-3.2.10 django-3.0.8 pytz-2020.1 sqlparse-0.3.1
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We can get started coding the application now that we have all of our
required dependencies installed.&lt;/p&gt;
&lt;h2&gt;Building our application&lt;/h2&gt;
&lt;p&gt;Let's begin coding our application.&lt;/p&gt;
&lt;p&gt;We can use the &lt;a href="/django.html"&gt;Django&lt;/a&gt; &lt;code&gt;django-admin&lt;/code&gt; tool to create
the boilerplate code structure to get our project started.
Change into the directory where you develop your applications. For
example, I typically use &lt;code&gt;/Users/matt/devel/py/&lt;/code&gt; for all of my
Python projects. Then run the following command to start a Django
project named &lt;code&gt;djbootstrap4&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;django-admin.py startproject djbootstrap4
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Note that in this tutorial we are using the same name for both the
virtualenv and the Django project directory, but they can be
different names if you prefer that for organizing your own projects.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;django-admin&lt;/code&gt; command creates a directory named &lt;code&gt;djbootstrap4&lt;/code&gt;
along with several subdirectories that you should be familiar with
if you have previously worked with Django.&lt;/p&gt;
&lt;p&gt;Change directories into the new project.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;cd djbootstrap4
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Create a new Django app within &lt;code&gt;djbootstrap4&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python manage.py startapp bootstrap4
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Django will generate a new folder named &lt;code&gt;bootstrap4&lt;/code&gt; for the project.
We should update the URLs so the app is accessible before we write
our &lt;code&gt;views.py&lt;/code&gt; code.&lt;/p&gt;
&lt;p&gt;Open &lt;code&gt;djbootstrap4/djbootstrap4/urls.py&lt;/code&gt;. Add the highlighted
lines so that URL resolver will check the &lt;code&gt;bootstrap4&lt;/code&gt; app
for additional routes to match with URLs that are requested of
this Django application.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# djbootstrap4/djbootstrap4/urls.py&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;include&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;


&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bootstrap4.urls&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;admin/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Save &lt;code&gt;djbootstrap4/djbootstrap4/urls.py&lt;/code&gt; and open
&lt;code&gt;djbootstrap4/djbootstrap4/settings.py&lt;/code&gt;.
Add the &lt;code&gt;bootstrap4&lt;/code&gt; app to &lt;code&gt;settings.py&lt;/code&gt; by inserting
the highlighted line:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# djbootstrap4/djbootstrap4/settings.py&lt;/span&gt;
&lt;span class="c1"&gt;# Application definition&lt;/span&gt;

&lt;span class="n"&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.admin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.auth&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.contenttypes&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.sessions&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.messages&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.staticfiles&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="s1"&gt;&amp;#39;bootstrap4&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Make sure you change the default &lt;code&gt;DEBUG&lt;/code&gt; and &lt;code&gt;SECRET_KEY&lt;/code&gt;
values in &lt;code&gt;settings.py&lt;/code&gt; before you deploy any code to production. Secure
your app properly with the information from the Django
&lt;a href="https://docs.djangoproject.com/en/stable/howto/deployment/checklist/"&gt;production deployment checklist&lt;/a&gt;
so that you do not add your project to the list of hacked applications
on the web.&lt;/p&gt;
&lt;p&gt;Save and close &lt;code&gt;settings.py&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next change into the &lt;code&gt;djbootstrap4/bootstrap4&lt;/code&gt; directory. Create
a new file named &lt;code&gt;urls.py&lt;/code&gt; to contain routes for the &lt;code&gt;bootstrap4&lt;/code&gt; app.&lt;/p&gt;
&lt;p&gt;Add all of these lines to the empty &lt;code&gt;djbootstrap4/bootstrap4/urls.py&lt;/code&gt;
file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# djbootstrap4/bootstrap4/urls.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&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="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bootstrap4_index&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="s2"&gt;&amp;quot;index&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Save &lt;code&gt;djbootstrap4/bootstrap4/urls.py&lt;/code&gt;. Open
&lt;code&gt;djbootstrap4/bootstrap4/views.py&lt;/code&gt; to add the
following two highlighted lines. You can keep the boilerplate comment
"# Create your views here." or delete like I usually do.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# djbootstrap4/bootstrap4/views.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.shortcuts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;


&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;bootstrap4_index&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;~~&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;render&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="s1"&gt;&amp;#39;index.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Next, create a directory for your template files named &lt;code&gt;templates&lt;/code&gt; under
the &lt;code&gt;djmaps/maps&lt;/code&gt; app directory.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;mkdir templates
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Create a new file named &lt;code&gt;index.html&lt;/code&gt; within
&lt;code&gt;djbootstrap4/bootstrap4/templates&lt;/code&gt; that contains the
following &lt;a href="/django-templates.html"&gt;Django template language&lt;/a&gt; markup.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;First step for bootstrap4&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
   &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Hello, world!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We can test out this static page to make sure all of our code is
correct before we start adding the meat of the functionality to
the project. Change into the base directory of your Django project
where the &lt;code&gt;manage.py&lt;/code&gt; file is located. Execute the development
server with the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python manage.py runserver
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The Django development server should start up with no issues other than
an unapplied migrations warning.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;Watching&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="n"&gt;changes&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;StatReloader&lt;/span&gt;
&lt;span class="n"&gt;Performing&lt;/span&gt; &lt;span class="n"&gt;system&lt;/span&gt; &lt;span class="n"&gt;checks&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;System&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="n"&gt;identified&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;issues&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;silenced&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;

&lt;span class="n"&gt;You&lt;/span&gt; &lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt; &lt;span class="n"&gt;unapplied&lt;/span&gt; &lt;span class="n"&gt;migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Your&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="n"&gt;may&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;work&lt;/span&gt; &lt;span class="n"&gt;properly&lt;/span&gt; &lt;span class="n"&gt;until&lt;/span&gt; &lt;span class="n"&gt;you&lt;/span&gt; &lt;span class="n"&gt;apply&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;migrations&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contenttypes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sessions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;Run&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;python manage.py migrate&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;apply&lt;/span&gt; &lt;span class="n"&gt;them&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;

&lt;span class="n"&gt;July&lt;/span&gt; &lt;span class="mi"&gt;05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2020&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;59&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;58&lt;/span&gt;
&lt;span class="n"&gt;Django&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;using&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;djbootstrap4.settings&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;Starting&lt;/span&gt; &lt;span class="n"&gt;development&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="mf"&gt;127.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="n"&gt;Quit&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;CONTROL&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Open a web browser and go to "http://localhost:8000".&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/visuals/first-step.jpg" width="100%" class="shot rnd outl" alt="Plain old HTML page saying 'Hello, world!'."&gt;&lt;/p&gt;
&lt;p&gt;With our base application working, we can now add Bootstrap.&lt;/p&gt;
&lt;h2&gt;Integrating Bootstrap&lt;/h2&gt;
&lt;p&gt;Time to add Bootstrap into the template so we can use its styling.&lt;/p&gt;
&lt;p&gt;Open &lt;code&gt;djbootstrap4/bootstrap4/templates/index.html&lt;/code&gt; back up and
add or modify the following highlighted lines, which are very
similar to what you will find in the 
&lt;a href="https://getbootstrap.com/docs/4.5/getting-started/introduction/"&gt;Bootstrap introduction guide&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;!&lt;/span&gt;&lt;span class="n"&gt;DOCTYPE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;en&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;utf-8&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;viewport&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;width=device-width, initial-scale=1, shrink-to-fit=no&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;stylesheet&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;integrity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;crossorigin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;anonymous&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;bootstrap4&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="o"&gt;!&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;!--&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;JavaScript&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;!--&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;jQuery&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Popper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Bootstrap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;JS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://code.jquery.com/jquery-3.5.1.slim.min.js&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;integrity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;crossorigin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;anonymous&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;integrity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;crossorigin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;anonymous&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;integrity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;crossorigin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;anonymous&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The above new lines in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; section add a couple of meta elements 
that are important to Bootstrap's styling, and add the mandatory Bootstrap
stylesheet.&lt;/p&gt;
&lt;p&gt;We keep the same &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; header, which will automatically get the CSS
styling. Then there are 3 &lt;em&gt;optional&lt;/em&gt; script elements that pull in
Bootstrap &lt;a href="/javascript.html"&gt;JavaScript&lt;/a&gt; for more advanced features.
We are not using them in this tutorial because we just wanted to 
quickly show how to use the CDN and with this in place you can see
in the 
&lt;a href="https://getbootstrap.com/docs/4.5/content/reboot/"&gt;Bootstrap content docs&lt;/a&gt;
what you want to add to the template next.&lt;/p&gt;
&lt;p&gt;Refresh the page at "http://localhost:8000" and you should see "Hello, world!"
change fonts.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200705-bootstrap-4-django-template/hello-world-bootstrap-style.jpg" width="100%" class="shot rnd outl" alt="Bootstrap-enhanced HTML page saying 'Hello, world!'."&gt;&lt;/p&gt;
&lt;p&gt;If you see that, it means everything works as expected.&lt;/p&gt;
&lt;h2&gt;What now?&lt;/h2&gt;
&lt;p&gt;We just added Bootstrap via the CDN so we can use it in our Django template.
This was the absolute simplest way to add Bootstrap to a single Django
page and now there's a ton more you can do with it.&lt;/p&gt;
&lt;p&gt;Next, try out some of these other related &lt;a href="/django.html"&gt;Django&lt;/a&gt; tutorials:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/bootstrap-css.html"&gt;More Bootstrap resources&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/blog/maps-django-web-applications-projects-mapbox.html"&gt;How to Add Maps to Django Web App Projects with Mapbox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/blog/monitor-django-projects-web-apps-rollbar.html"&gt;Monitoring Django Projects with Rollbar&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Questions? Contact me via Twitter
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;. I am also on GitHub with
the username &lt;a href="https://github.com/mattmakai"&gt;mattmakai&lt;/a&gt;.
If you see an issue or error in this tutorial, please
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/200705-bootstrap-4-django-template.markdown"&gt;fork the source repository on GitHub&lt;/a&gt;
and submit a pull request with the fix.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Sun, 05 Jul 2020 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2020-07-05:blog/bootstrap-4-django-template.html</guid></item><item><title>How to Report Errors in Flask Web Apps with Sentry</title><link>https://www.fullstackpython.com/blog/report-errors-flask-web-apps-sentry.html</link><description>&lt;p&gt;&lt;a href="/flask.html"&gt;Flask&lt;/a&gt; web applications are highly customizable by developers
thanks to the &lt;a href="/web-frameworks.html"&gt;framework&lt;/a&gt;'s extension-based
architecture, but that flexibility can sometimes lead to more errors
when you run the application due to rough edges between the libraries.&lt;/p&gt;
&lt;p&gt;Reporting errors is crucial to running a well-functioning Flask web
application, so this tutorial will guide you through adding a free, basic
&lt;a href="https://sentry.io"&gt;Sentry&lt;/a&gt; configuration to a fresh Flask project.&lt;/p&gt;
&lt;h2&gt;Tutorial Requirements&lt;/h2&gt;
&lt;p&gt;Ensure you have Python 3 installed, because Python 2 reached its
end-of-life at the beginning of 2020 and is no longer supported.
Preferrably, you should have
&lt;a href="https://www.python.org/downloads/"&gt;Python 3.7 or greater installed&lt;/a&gt;
in your &lt;a href="/development-environments.html"&gt;development environment&lt;/a&gt;.
This tutorial will also use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/flask.html"&gt;Flask&lt;/a&gt; web framework,
  &lt;a href="https://github.com/pallets/flask/releases/tag/1.1.2"&gt;version 1.1.2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;a hosted Sentry instance on &lt;a href="https://sentry.io"&gt;sentry.io&lt;/a&gt;, which we'll
  need an account to access&lt;/li&gt;
&lt;li&gt;the &lt;a href="https://pypi.org/project/sentry-sdk/"&gt;Sentry Python helper library&lt;/a&gt; to
  send exception data to our Sentry instance, with the
  &lt;a href="https://docs.sentry.io/platforms/python/flask/"&gt;Flask integration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All code in this blog post is available open source under the MIT license
on GitHub under the
&lt;a href="https://github.com/fullstackpython/blog-code-examples/tree/master/report-errors-flask-web-apps-sentry"&gt;report-errors-flask-web-apps-sentry directory of the blog-code-examples&lt;/a&gt;
repository. Use the source code as you desire for your own projects.&lt;/p&gt;
&lt;h2&gt;Development environment set up&lt;/h2&gt;
&lt;p&gt;Change into the directory where you keep your Python
&lt;a href="/virtual-environments-virtualenvs-venvs.html"&gt;virtual environments&lt;/a&gt;.
Create a new virtualenv for this project using the following
command.&lt;/p&gt;
&lt;p&gt;Install the Flask and Sentry-SDK code libraries into a new Python 
virtual environment using the following commands:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python -m venv sentryflask
&lt;span class="nb"&gt;source&lt;/span&gt; sentryflask/bin/activate

pip install flask&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.1.2 sentry-sdk&lt;span class="o"&gt;[&lt;/span&gt;flask&lt;span class="o"&gt;]==&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.15.1
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Note that we installed the Flask integration as part of the Sentry
SDK, which is why the dependency is &lt;code&gt;sentry-sdk[flask]&lt;/code&gt; rather than
just &lt;code&gt;sentry-sdk&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;Now that we have all of our dependencies installed we can code up a 
little application to show how the error reporting works.&lt;/p&gt;
&lt;h2&gt;Creating the application&lt;/h2&gt;
&lt;p&gt;We have everything we need to start building our application. Create
a new directory for your project. I've called mine 
&lt;a href="https://github.com/fullstackpython/blog-code-examples/tree/master/report-errors-flask-web-apps-sentry"&gt;report-errors-flask-web-apps-sentry&lt;/a&gt;
in the examples repository but you can use a shorter name if you
prefer. Open a new file named &lt;code&gt;app.py&lt;/code&gt; and write the following code in it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# app.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;escape&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;


&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/divide/&amp;lt;int:numerator&amp;gt;/by/&amp;lt;int:denominator&amp;gt;/&amp;#39;&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;hello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numerator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;denominator&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;numerator&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;denominator&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;numerator&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; can be divided by &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;denominator&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; times.&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The above code is a short Flask application that allows input via the URL for
two integer values: a numerator and a denominator.&lt;/p&gt;
&lt;p&gt;Save the file and run it using the &lt;code&gt;flask run&lt;/code&gt; command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;env &lt;span class="nv"&gt;FLASK_APP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;app.py flask run
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If you see the following output on the command line that means the development
server is working properly:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt; * Serving Flask app &lt;span class="s2"&gt;&amp;quot;app.py&amp;quot;&lt;/span&gt;
 * Environment: production
   WARNING: This is a development server. Do not use it &lt;span class="k"&gt;in&lt;/span&gt; a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ &lt;span class="o"&gt;(&lt;/span&gt;Press CTRL+C to quit&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Test it by going to http://localhost:5000/divide/50/by/10/ and you will
get the following output in your web browser:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200630-python-flask-sentry/division-success.png" width="100%" class="shot rnd outl" alt="Successful division of 50 by 10."&gt;&lt;/p&gt;
&lt;p&gt;With our base application working, we can now add error reporting for
the situations that do not work as expected.&lt;/p&gt;
&lt;h2&gt;Adding Sentry to the Flask app&lt;/h2&gt;
&lt;p&gt;It's time to add Sentry with the Flask integration into the mix, so that we 
can easily see when the route errors out due to bad input.&lt;/p&gt;
&lt;p&gt;Sentry can either be &lt;a href="https://github.com/getsentry/onpremise"&gt;self-hosted&lt;/a&gt; or
used as a cloud service through &lt;a href="https://sentry.io"&gt;Sentry.io&lt;/a&gt;. In this 
tutorial we will use the cloud hosted version because it's faster than
setting up your own server as well as free for smaller projects.&lt;/p&gt;
&lt;p&gt;Go to &lt;a href="https://sentry.io"&gt;Sentry.io's homepage&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200525-sentry/sentry-homepage.jpg" width="100%" class="shot rnd outl" alt="Sentry.io homepage where you can sign up for a free account."&gt;&lt;/p&gt;
&lt;p&gt;Sign into your account or sign up for a new free account. You will be at
the main account dashboard after logging in or completing the Sentry sign
up process.&lt;/p&gt;
&lt;p&gt;There are no errors logged on our account dashboard yet, which is as 
expected because we have not yet connected our account to our Python 
application.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200525-sentry/sentry-empty-dashboard.jpg" width="100%" class="shot rnd outl" alt="Blank Sentry account dashboard."&gt;&lt;/p&gt;
&lt;p&gt;You'll want to create a new Sentry Project just for this application so
click "Projects" in the left sidebar to go to the Projects page.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200525-sentry/create-project.jpg" width="100%" class="shot rnd outl" alt="Button to create a new Sentry project."&gt;&lt;/p&gt;
&lt;p&gt;On the Projects page, click the "Create Project" button in the top right
corner of the page.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200525-sentry/create-new-project-screen.jpg" width="100%" class="shot rnd outl" alt="Create a new Sentry project."&gt;&lt;/p&gt;
&lt;p&gt;You can either choose "Flask" or select "Python". I usually just choose 
"Python" if I do not yet know what framework I'll be using to build my 
application. Next, give your new Project a name and then press the "Create 
Project" button. Our new project is ready to integrate with our Python code.&lt;/p&gt;
&lt;p&gt;We need the unique identifier for our account and project to authorize our
Python code to send errors to this Sentry instance. The easiest way to get
what we need is to go to the 
&lt;a href="https://docs.sentry.io/platforms/python/flask/"&gt;Python+Flask documentation page&lt;/a&gt;
and read how to configure the SDK.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200525-sentry/python-sentry-quickstart.jpg" width="100%" class="shot rnd outl" alt="The Sentry docs show you exactly what you need to export to connect to your account."&gt; &lt;/p&gt;
&lt;p&gt;Copy the string parameter for the &lt;code&gt;init&lt;/code&gt; method and set it
&lt;a href="https://www.twilio.com/blog/2017/01/how-to-set-environment-variables.html"&gt;as an environment variable&lt;/a&gt;
rather than having it exposed in your project's code.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;SENTRY_DSN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://yourkeygoeshere.ingest.sentry.io/project-number&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Make sure to replace "yourkeygoeshere" with your own unique identifier
and "project-number" with the ID that matches the project you just
created.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Check that the &lt;code&gt;SENTRY_DSN&lt;/code&gt; is set properly in your shell using the &lt;code&gt;echo&lt;/code&gt;
command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$SENTRY_DSN&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Update &lt;code&gt;app.py&lt;/code&gt; with the following highlighted lines of code.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# app.py                                                                                                                                                                                &lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sentry_sdk&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;escape&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sentry_sdk.integrations.flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FlaskIntegration&lt;/span&gt;


&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="n"&gt;sentry_sdk&lt;/span&gt;&lt;span class="o"&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;dsn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SENTRY_DSN&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;integrations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;FlaskIntegration&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/divide/&amp;lt;int:numerator&amp;gt;/by/&amp;lt;int:denominator&amp;gt;/&amp;#39;&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;hello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numerator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;denominator&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;numerator&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;denominator&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;numerator&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; can be divided by &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;denominator&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; times.&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The above new lines of code initialize the Sentry client and allow it to
properly send any errors that occur over to the right Sentry service.&lt;/p&gt;
&lt;h2&gt;Testing the Sentry Integration&lt;/h2&gt;
&lt;p&gt;The Sentry dashboard shows that the service is still waiting for events.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200630-python-flask-sentry/waiting-for-events.jpg" width="100%" class="shot rnd outl" alt="Sentry dashboard, without any error data shown."&gt;&lt;/p&gt;
&lt;p&gt;Let's make an error happen to see if we've properly connected the Flask integration
with our application.&lt;/p&gt;
&lt;p&gt;Try to divide by zero, by going to http://localhost:5000/divide/50/by/0/ in
your web browser. You should get an "Internal Server Error".&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200630-python-flask-sentry/internal-server-error.png" width="100%" class="shot rnd outl" alt="Flask HTTP status code 500 for internal server error."&gt;&lt;/p&gt;
&lt;p&gt;Back over in the Sentry dashboard, the error appears in the list.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200630-python-flask-sentry/zero-division-error.png" width="100%" class="shot rnd outl" alt="Sentry dashboard showing the exact ZeroDivisionError."&gt;&lt;/p&gt;
&lt;p&gt;We can drill into the error by clicking on it and get a ton more information,
not just about our application but also about the client that visited the
site. This is handy if you have an issue in a specific browser or other
type of client when building an &lt;a href="/application-programming-interfaces.html"&gt;API&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200630-python-flask-sentry/error-details.jpg" width="100%" class="shot rnd outl" alt="ZeroDivisionError error details in the Sentry user interface."&gt;&lt;/p&gt;
&lt;p&gt;With that in place, you can now build out the rest of your Flask application
knowing that all of the exceptions will be tracked in Sentry.&lt;/p&gt;
&lt;h2&gt;What's next?&lt;/h2&gt;
&lt;p&gt;We just finished building a Flask app to show how quickly the hosted 
version of Sentry can be added to applications so you do not lose 
track of your error messages.&lt;/p&gt;
&lt;p&gt;Next, you can try one of these tutorials to add other useful features to your
new application:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/blog/respond-sms-text-messages-python-flask.html"&gt;Responding to SMS Text Messages with Python &amp;amp; Flask&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/blog/develop-flask-web-apps-docker-containers-macos.html"&gt;Develop and Run Flask Apps within Docker Containers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/blog/okta-user-auth-existing-flask-web-app.html"&gt;Add Okta Authentication to an Existing Flask App&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can also determine what to code next in your Python project by reading
the &lt;a href="/table-of-contents.html"&gt;Full Stack Python table of contents page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Questions? Contact me via Twitter
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;. I am also on GitHub with
the username &lt;a href="https://github.com/mattmakai"&gt;mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you see an issue or error in this tutorial, please
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/200630-report-errors-flask-web-apps-sentry.markdown"&gt;fork the source repository on GitHub&lt;/a&gt;
and submit a pull request with the fix.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Thu, 02 Jul 2020 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2020-06-30:blog/report-errors-flask-web-apps-sentry.html</guid></item><item><title>Reporting Exceptions in Python Scripts with Sentry</title><link>https://www.fullstackpython.com/blog/report-exceptions-python-scripts-sentry.html</link><description>&lt;p&gt;Python scripts are the glue that keep many applications and their 
infrastructure running, but when one of your scripts throws an exception 
you may not know about it immediately unless you have a central place to 
aggregate the errors. That's where adding &lt;a href="https://sentry.io/"&gt;Sentry&lt;/a&gt;
can solved this distributed error logging problem. &lt;/p&gt;
&lt;p&gt;In this tutorial, we'll see how to quickly add Sentry to a new or existing 
Python script to report errors into a centralized location for further 
&lt;a href="/debugging.html"&gt;debugging&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Development environment setup&lt;/h2&gt;
&lt;p&gt;Make sure you have Python 3 installed. As of right now, 
&lt;a href="https://www.python.org/downloads/"&gt;Python 3.8.3&lt;/a&gt; is the latest
version of Python.&lt;/p&gt;
&lt;p&gt;During this tutorial we're also going to use: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a hosted Sentry instance on &lt;a href="https://sentry.io"&gt;sentry.io&lt;/a&gt;, which we'll
  need an account to access&lt;/li&gt;
&lt;li&gt;the &lt;a href="https://pypi.org/project/sentry-sdk/"&gt;Sentry Python helper library&lt;/a&gt; to
  send exception data to our Sentry instance&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Install the above code libraries into a new 
&lt;a href="/virtual-environments-virtualenvs-venvs.html"&gt;Python virtual environment&lt;/a&gt;
using the following commands:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python -m venv sentryscript
&lt;span class="nb"&gt;source&lt;/span&gt; sentryscript/bin/activate

pip install sentry-sdk&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.14.4
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Our &lt;a href="/development-environments.html"&gt;development environment&lt;/a&gt; is now 
ready and we can write some code that will throw exceptions to demonstrate
how to use Sentry.&lt;/p&gt;
&lt;p&gt;Note that all of the code for this tutorial can be found within the
&lt;a href="https://github.com/fullstackpython/blog-code-examples"&gt;blog-code-examples&lt;/a&gt;
Git repository on GitHub under the 
&lt;a href="https://github.com/fullstackpython/blog-code-examples/tree/master/python-script-sentry"&gt;python-script-sentry&lt;/a&gt;
directory.&lt;/p&gt;
&lt;h2&gt;An Example Script for Loading Python Modules&lt;/h2&gt;
&lt;p&gt;We'll start by writing a small but useful script that prints out the
names of all modules within a Python package, then add Sentry to it
when it becomes apparent that capturing exceptions would be a
useful addition.&lt;/p&gt;
&lt;p&gt;Create a new file named &lt;code&gt;module_loader.py&lt;/code&gt; and write the
following lines of code in it to allow us to easily execute it
on the command line.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;argparse&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;import_submodules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;package&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="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;package&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;package_to_load&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;import_submodules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;package_to_load&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&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;r&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The above code takes an argument when the script is invoked from the
command line and uses the value as an input into the stub
&lt;code&gt;import_submodules&lt;/code&gt; function that will contain code to walk the
tree of modules within the package.&lt;/p&gt;
&lt;p&gt;Nextt, add the following highlighted lines of code to use &lt;code&gt;importlib&lt;/code&gt; and
&lt;code&gt;pkgutil&lt;/code&gt; to recursively import modules from the package if one is
found that matches the name sent in as the &lt;code&gt;package&lt;/code&gt; argument.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;argparse&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;importlib&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pkgutil&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;import_submodules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;Import all submodules of a module, recursively, including subpackages.&lt;/span&gt;

&lt;span class="s2"&gt;~~    :param package: package (name or actual module)&lt;/span&gt;
&lt;span class="s2"&gt;~~    :type package: str | module&lt;/span&gt;
&lt;span class="s2"&gt;~~    :rtype: dict[str, types.ModuleType]&lt;/span&gt;
&lt;span class="s2"&gt;~~    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;package&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;~~&lt;/span&gt;        &lt;span class="n"&gt;package&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;importlib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;import_module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;loader&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;is_pkg&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pkgutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;walk_packages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__path__&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;        &lt;span class="n"&gt;full_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;.&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;            &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;importlib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;import_module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;is_pkg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;                &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;import_submodules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;ModuleNotFoundError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;mnfe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;module not found: &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;general_exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;general_exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;package&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;package_to_load&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;import_submodules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;package_to_load&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&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;r&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The new code above loops through all packages with the
&lt;code&gt;walk_package&lt;/code&gt; function in the &lt;code&gt;pkgutil&lt;/code&gt; standard library
module and tries to import it using the &lt;code&gt;import_module&lt;/code&gt; on
the package name plus package as a string. If the
result is successful, the function will recursively call
itself to import submodules within the imported package.
If a module is not found, or some other issue occurs, exceptions
are caught so that the script does not fail but instead can
continue processing potential modules.&lt;/p&gt;
&lt;p&gt;Test the full script to see what it prints out with an arbitrary
package on the command line:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python module_loader.py importlib
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The above example generates the output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;importlib._bootstrap
importlib._bootstrap_external
importlib.abc
importlib.machinery
importlib.resources
importlib.util
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Trying to inspect a package that is not installed will give an error. Use
the script with a package that is not installed in your current environment.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python module_loader.py flask
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The above command produces the following traceback due to an expected 
&lt;code&gt;ModuleNotFoundError&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;Traceback&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;most&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="k"&gt;call&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;module_loader.py&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;import_submodules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;package_to_load&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;module_loader.py&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;import_submodules&lt;/span&gt;
    &lt;span class="n"&gt;package&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;importlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;import_module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/importlib/__init__.py&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;127&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;import_module&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_bootstrap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_gcd_import&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;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;lt;frozen importlib._bootstrap&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;1006&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_gcd_import&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;lt;frozen importlib._bootstrap&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;983&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_find_and_load&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;lt;frozen importlib._bootstrap&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;965&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_find_and_load_unlocked&lt;/span&gt;
&lt;span class="n"&gt;ModuleNotFoundError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;No&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;named&lt;/span&gt; &lt;span class="c"&gt;&amp;#39;flask&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If you install Flask into your current environment the module is found and
the application will go through the list of modules and submodules.&lt;/p&gt;
&lt;p&gt;Our example script is usable but what if we run this code or something similar
on one or more servers that we don't check that often? That's where it would
be helpful to have a way to aggregate one or more scripts' exception output
in a single place. Sentry can help us to accomplish that goal.&lt;/p&gt;
&lt;h2&gt;Adding Exception Reporting with Sentry&lt;/h2&gt;
&lt;p&gt;Sentry can either be &lt;a href="https://github.com/getsentry/onpremise"&gt;self-hosted&lt;/a&gt; or
used as a cloud service through &lt;a href="https://sentry.io"&gt;Sentry.io&lt;/a&gt;. In this 
tutorial we will use the cloud hosted version because it's faster than
setting up your own server as well as free for smaller projects.&lt;/p&gt;
&lt;p&gt;Go to &lt;a href="https://sentry.io"&gt;Sentry.io's homepage&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200525-sentry/sentry-homepage.jpg" width="100%" class="shot rnd outl" alt="Sentry.io homepage where you can sign up for a free account."&gt;&lt;/p&gt;
&lt;p&gt;Sign into your account or sign up for a new free account. You will be at 
the main account dashboard after logging in or completing the Sentry sign 
up process.&lt;/p&gt;
&lt;p&gt;There are no errors logged on our account dashboard yet, which is as 
expected because we have not yet connected our account to the Python 
script.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200525-sentry/sentry-empty-dashboard.jpg" width="100%" class="shot rnd outl" alt="Blank Sentry account dashboard."&gt;&lt;/p&gt;
&lt;p&gt;You'll want to create a new Sentry Project just for this application so
click "Projects" in the left sidebar to go to the Projects page.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200525-sentry/create-project.jpg" width="100%" class="shot rnd outl" alt="Button to create a new Sentry project."&gt;&lt;/p&gt;
&lt;p&gt;On the Projects page, click the "Create Project" button in the top right
corner of the page.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200525-sentry/create-new-project-screen.jpg" width="100%" class="shot rnd outl" alt="Create a new Sentry project."&gt;&lt;/p&gt;
&lt;p&gt;Select Python, give your new Project a name and then press the "Create Project"
button. Our new project is ready to integrate with our Python script.&lt;/p&gt;
&lt;p&gt;We need the unique identifier for our account and project to authorize our
Python code to send errors to this Sentry instance. The easiest way to get
what we need is to go to the 
&lt;a href="https://docs.sentry.io/error-reporting/quickstart/?platform=python"&gt;Python getting started documentation page&lt;/a&gt;
and scroll down to the "Configure the SDK" section.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200525-sentry/python-sentry-quickstart.jpg" width="100%" class="shot rnd outl" alt="The Sentry docs show you exactly what you need to export to connect to your account."&gt;&lt;/p&gt;
&lt;p&gt;Copy the string parameter for the &lt;code&gt;init&lt;/code&gt; method and 
&lt;a href="https://www.twilio.com/blog/2017/01/how-to-set-environment-variables.html"&gt;set it as an environment variable&lt;/a&gt; 
rather than exposing it directly in your application code.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;SENTRY_DSN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://yourkeygoeshere.ingest.sentry.io/project-number&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Make sure to replace "yourkeygoeshere" with your own unique identifier
and "project-number" with the ID that matches the project you just
created.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Check that the &lt;code&gt;SENTRY_DSN&lt;/code&gt; is set properly in your shell using the &lt;code&gt;echo&lt;/code&gt;
command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$SENTRY_DSN&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Modify the application to send exception information to Sentry now 
that we have our unique identifier. Open &lt;code&gt;module_loader.py&lt;/code&gt; again and
update the following highlighted lines of code.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;argparse&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;importlib&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pkgutil&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sentry_sdk&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sentry_sdk&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;capture_exception&lt;/span&gt;

&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="c1"&gt;# find on https://docs.sentry.io/error-reporting/quickstart/?platform=python&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="n"&gt;sentry_sdk&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dsn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SENTRY_DSN&amp;#39;&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;import_submodules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Import all submodules of a module, recursively, including subpackages.&lt;/span&gt;

&lt;span class="sd"&gt;    :param package: package (name or actual module)&lt;/span&gt;
&lt;span class="sd"&gt;    :type package: str | module&lt;/span&gt;
&lt;span class="sd"&gt;    :rtype: dict[str, types.ModuleType]&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;package&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;package&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;importlib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;import_module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;loader&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;is_pkg&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pkgutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;walk_packages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__path__&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;full_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;.&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;importlib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;import_module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;full_name&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;is_pkg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;import_submodules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;ModuleNotFoundError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;mnfe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;module not found: &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;            &lt;span class="n"&gt;capture_exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mnfe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;general_exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;general_exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;            &lt;span class="n"&gt;capture_exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;general_exception&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;results&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;package&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;package_to_load&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;import_submodules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;package_to_load&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&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;r&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;These new lines of code import the 
&lt;a href="https://github.com/getsentry/sentry-python"&gt;Sentry Python SDK&lt;/a&gt; and &lt;code&gt;os&lt;/code&gt;
library (to read system environment variables). The application then
initializes the Sentry SDK with the string found in the &lt;code&gt;SENTRY_DSN&lt;/code&gt;
environment variable. Down in the &lt;code&gt;import_submodules&lt;/code&gt; function we
then call the &lt;code&gt;capture_exception&lt;/code&gt; SDK function whenever a
&lt;code&gt;ModuleNotFoundException&lt;/code&gt; is thrown or another exception which would
be caught within the broader &lt;code&gt;Exception&lt;/code&gt; bucket.&lt;/p&gt;
&lt;p&gt;Now that our code is in place, let's test out the new Sentry integration.&lt;/p&gt;
&lt;h2&gt;Testing the Script and Viewing Exceptions&lt;/h2&gt;
&lt;p&gt;The easiest way to test out whether the Sentry code is working or not is
to try to import a module that does not exist. Let's say you make a
typo in your command and try to run the script on &lt;code&gt;importliba&lt;/code&gt; instead
of &lt;code&gt;importlib&lt;/code&gt; (maybe because you are using an awful Macbook Pro "butterfly"
keyboard instead of a durable keyboard). Try it out and see what happens:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python module_loader.py importliba
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The script will run and finish but there will be errors because that
module does not exist. Thanks to our new code, we can view the
errors in Sentry.&lt;/p&gt;
&lt;p&gt;Check the Sentry dashboard to see the error.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200525-sentry/exception-in-dashboard.jpg" width="100%" class="shot rnd outl" alt="Viewing the first exception in the Sentry dashboard."&gt;&lt;/p&gt;
&lt;p&gt;We can also click into the error to learn more about what happened.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200525-sentry/detailed-error-report.jpg" width="100%" class="shot rnd outl" alt="The exception details in the Sentry dashboard."&gt;&lt;/p&gt;
&lt;p&gt;You can also receive email reports on the errors that occur so that
you do not have to always stay logged into the dashboard.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200525-sentry/sentry-email.jpg" width="100%" class="shot rnd outl" alt="The exception via email."&gt;&lt;/p&gt;
&lt;p&gt;With that all configured, we've now got a great base to expand the script 
and build better error handling with Sentry as our Python application 
becomes more complex.&lt;/p&gt;
&lt;h2&gt;What's Next?&lt;/h2&gt;
&lt;p&gt;We just created an example script that outputs all of the modules and 
submodules in a package, then added Sentry to it so that it would report 
any exceptions back to our central hosted instance.&lt;/p&gt;
&lt;p&gt;That's just a simple introduction to Sentry, so next you'll want to
read one of the following articles to do more with it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.sentry.io/platforms/python/"&gt;Python Sentry docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.sentry.io/platforms/python/flask/"&gt;How to use Sentry with Flask&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.sentry.io/platforms/python/celery/"&gt;Integrating Sentry into Celery task queues&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can also get an idea of what to code next in your Python project by 
reading the 
&lt;a href="/table-of-contents.html"&gt;Full Stack Python table of contents page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Questions? Contact me via Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;. I'm also on GitHub with
the username &lt;a href="https://github.com/mattmakai"&gt;mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Something wrong with this post? Fork 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/200525-python-exceptions-sentry.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Mon, 25 May 2020 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2020-05-25:blog/report-exceptions-python-scripts-sentry.html</guid></item><item><title>Exporting pandas DataFrames into SQLite with SQLAlchemy</title><link>https://www.fullstackpython.com/blog/export-pandas-dataframes-sqlite-sqlalchemy.html</link><description>&lt;p&gt;It is common when performing exploratory &lt;a href="/data-analysis.html"&gt;data analysis&lt;/a&gt;,
&lt;a href="/blog/learn-pandas-basic-commands-explore-covid-19-data.html"&gt;for example when examining COVID-19 data with pandas&lt;/a&gt;, 
to load from files like a CSV, XML, or JSON into a
&lt;a href="/pandas.html"&gt;pandas&lt;/a&gt; DataFrame. You may then do some work with the
data in the DataFrame and want to store it in a more durable location
like a &lt;a href="/databases.html"&gt;relational database&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This tutorial walks through how to load a pandas DataFrame from a CSV
file, pull out some data from the full data set, then save the
subset of data to a &lt;a href="/sqlite.html"&gt;SQLite&lt;/a&gt; database using
&lt;a href="/sqlalchemy.html"&gt;SQLAlchemy&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Configuring our development environment&lt;/h2&gt;
&lt;p&gt;Make sure you have Python 3 installed. As of right now, 
&lt;a href="https://www.python.org/downloads/"&gt;Python 3.8.2&lt;/a&gt; is the latest
version of Python.&lt;/p&gt;
&lt;p&gt;During this tutorial we're also going to use: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/pandas.html"&gt;pandas&lt;/a&gt; (&lt;a href="https://pandas.pydata.org/"&gt;project homepage&lt;/a&gt;
  and &lt;a href="https://github.com/pandas-dev/pandas"&gt;source code&lt;/a&gt;), version 1.0.3
  in this tutorial&lt;/li&gt;
&lt;li&gt;&lt;a href="/sqlalchemy.html"&gt;SQLAlchemy&lt;/a&gt; 
  (&lt;a href="https://www.sqlalchemy.org/"&gt;project homepage&lt;/a&gt; and
  &lt;a href="https://github.com/sqlalchemy/sqlalchemy"&gt;source code&lt;/a&gt;), version 1.3.15 
  for this tutorial&lt;/li&gt;
&lt;li&gt;&lt;a href="/sqlite.html"&gt;SQLite&lt;/a&gt; (&lt;a href="https://sqlite.org/index.html"&gt;project homepage&lt;/a&gt;
  and &lt;a href="https://www.sqlite.org/src/doc/trunk/README.md"&gt;source code&lt;/a&gt;), 
  which Python 
  &lt;a href="https://docs.python.org/3/library/sqlite3.html"&gt;includes a connector for as part of the Python standard library&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Install the above code libraries into a new 
&lt;a href="/virtual-environments-virtualenvs-venvs.html"&gt;Python virtual environment&lt;/a&gt;
using the following commands:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python -m venv pandasexport
&lt;span class="nb"&gt;source&lt;/span&gt; pandasexport/bin/activate

pip install &lt;span class="nv"&gt;pandas&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.0.3 &lt;span class="nv"&gt;sqlalchemy&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.3.15
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Our &lt;a href="/development-environments.html"&gt;development environment&lt;/a&gt; is now 
ready to download an example COVID-19 data set, load it into a pandas 
DataFrame, perform some analysis on it then save into a SQLite database.&lt;/p&gt;
&lt;h2&gt;Obtaining COVID-19 data&lt;/h2&gt;
&lt;p&gt;Go to the 
&lt;a href="https://www.ecdc.europa.eu/en/publications-data/download-todays-data-geographic-distribution-covid-19-cases-worldwide"&gt;download today’s data on the geographic distribution of COVID-19 cases worldwide&lt;/a&gt; 
page in your web browser. It should look something like the following
screenshot. &lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200330-pandas-sqlite/covid-19-data-download.jpg" width="100%" class="shot rnd outl" alt="Download the CSV version of the COVID-19 March 29, 2020 data."&gt;&lt;/p&gt;
&lt;p&gt;There should be a link to download the
data in CSV format, but the organization has changed the page layout
several times in the past few weeks, which makes it difficult to find
formats other than Excel (XLSX). If you have trouble obtaining the
CSV version, just download 
&lt;a href="https://raw.githubusercontent.com/fullstackpython/blog-code-examples/master/pandas-covid-19/covid-19-cases-march-28-2020.csv"&gt;this one from GitHub&lt;/a&gt;
which is pegged to a copy downloaded on March 28th, 2020.&lt;/p&gt;
&lt;h2&gt;Importing the CSV into pandas&lt;/h2&gt;
&lt;p&gt;The raw data is in a CSV file and we need to load it into memory via a
pandas DataFrame.&lt;/p&gt;
&lt;p&gt;Start by running the Python Read-Evaluate-Print Loop (REPL) on the
command line:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python

&amp;gt;&amp;gt;&amp;gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The REPL is ready to execute code, but we first need to import the pandas
library so we can use it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pandas&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;read_csv&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;covid-19-cases-march-28-2020.csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ISO-8859-1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The data is now loaded into the &lt;code&gt;df&lt;/code&gt; variable which is an instance of the
&lt;a href="https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html"&gt;pandas DataFrame&lt;/a&gt;
class.&lt;/p&gt;
&lt;p&gt;When we run the &lt;code&gt;count&lt;/code&gt; function on this DataFrame, we get back that it
has 7320 rows.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Next, we'll take this set of 7320 rows of data and slice out only
the rows that pertain to the United States.&lt;/p&gt;
&lt;h2&gt;Creating a new DataFrame from the original DataFrame&lt;/h2&gt;
&lt;p&gt;We can pick out all of the rows of data for a single country using
a pandas function to match the &lt;code&gt;countriesAndTerritories&lt;/code&gt; column
to the country of our choice.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;save_df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;countriesAndTerritories&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;United_States_of_America&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;save_df&lt;/code&gt; variable contains the smaller subset of data. You can
find out what's in it by having it print itself:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;save_df&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;You should see something like the following output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;         dateRep  day  month  year  cases  deaths   countriesAndTerritories geoId countryterritoryCode  popData2018
7082  28/03/2020   28      3  2020  18695     411  United_States_of_America    US                  USA  327167434.0
7083  27/03/2020   27      3  2020  16797     246  United_States_of_America    US                  USA  327167434.0
7084  26/03/2020   26      3  2020  13963     249  United_States_of_America    US                  USA  327167434.0
7085  25/03/2020   25      3  2020   8789     211  United_States_of_America    US                  USA  327167434.0
7086  24/03/2020   24      3  2020  11236     119  United_States_of_America    US                  USA  327167434.0
...          ...  ...    ...   ...    ...     ...                       ...   ...                  ...          ...
7166  04/01/2020    4      1  2020      0       0  United_States_of_America    US                  USA  327167434.0
7167  03/01/2020    3      1  2020      0       0  United_States_of_America    US                  USA  327167434.0
7168  02/01/2020    2      1  2020      0       0  United_States_of_America    US                  USA  327167434.0
7169  01/01/2020    1      1  2020      0       0  United_States_of_America    US                  USA  327167434.0
7170  31/12/2019   31     12  2019      0       0  United_States_of_America    US                  USA  327167434.0

[89 rows x 10 columns]
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;89 rows of data out of the original 7320 rows. Let's proceed with
saving this subset to a SQLite relational database.&lt;/p&gt;
&lt;h2&gt;Saving the DataFrame to SQLite&lt;/h2&gt;
&lt;p&gt;We are going to use &lt;a href="/sqlalchemy.html"&gt;SQLAlchemy&lt;/a&gt; to create a connection
to a new SQLite database, which in this example will be stored in file 
named &lt;code&gt;save_pandas.db&lt;/code&gt;. You can of course save the file with whatever name
you want and in any location, not just the directory where you are 
executing the Python REPL.&lt;/p&gt;
&lt;p&gt;Start by importing the &lt;code&gt;create_engine&lt;/code&gt; function from the &lt;code&gt;sqlalchemy&lt;/code&gt;
library.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sqlalchemy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;create_engine&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Create the connection using the imported &lt;code&gt;create_engine&lt;/code&gt; function
and then invoking the &lt;code&gt;connect&lt;/code&gt; method on it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_engine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;sqlite:///save_pandas.db&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sqlite_connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We set &lt;code&gt;echo=True&lt;/code&gt; to see all of the output that comes from our
database connection. When the connection is successful you will
see output similar to the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;29&lt;/span&gt; &lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;44&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;08&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;198&lt;/span&gt; &lt;span class="n"&gt;INFO&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Engine&lt;/span&gt; &lt;span class="n"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="n"&gt;plain&lt;/span&gt; &lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;60&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="n"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;anon_1&lt;/span&gt;
&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;29&lt;/span&gt; &lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;44&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;08&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;198&lt;/span&gt; &lt;span class="n"&gt;INFO&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Engine&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;29&lt;/span&gt; &lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;44&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;08&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;199&lt;/span&gt; &lt;span class="n"&gt;INFO&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Engine&lt;/span&gt; &lt;span class="n"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="n"&gt;unicode&lt;/span&gt; &lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;60&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="n"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;anon_1&lt;/span&gt;
&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;29&lt;/span&gt; &lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;44&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;08&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;199&lt;/span&gt; &lt;span class="n"&gt;INFO&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Engine&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;sqlalchemy&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="n"&gt;x7fd4d932ec88&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Set a variable name with the string of a table name you would like
to create. Then use that variable when invoking the &lt;code&gt;to_sql&lt;/code&gt;
method on the &lt;code&gt;save_df&lt;/code&gt; object, which is our pandas DataFrame that
is a subset of the original data set with 89 rows filtered from
the original 7320.&lt;/p&gt;
&lt;p&gt;Note that in this case we are going to fail if the table already
exists in the database. You can change &lt;code&gt;if_exists&lt;/code&gt; to to &lt;code&gt;replace&lt;/code&gt;
or &lt;code&gt;append&lt;/code&gt; and add your own exception handling in a more robust
version of this program. Check the 
&lt;a href="https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.to_sql.html"&gt;pandas.DataFrame.to_sql&lt;/a&gt;
documentation for the extensive details on your options.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;sqlite_table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Covid19&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;save_df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_sql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sqlite_table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sqlite_connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;if_exists&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;fail&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The echo output should spin up with a bunch of output. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;29&lt;/span&gt; &lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;45&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;09&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;066&lt;/span&gt; &lt;span class="n"&gt;INFO&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Engine&lt;/span&gt; &lt;span class="n"&gt;PRAGMA&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;tab&lt;/span&gt;&lt;span class="n"&gt;le_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Covid19&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;29&lt;/span&gt; &lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;45&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;09&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;066&lt;/span&gt; &lt;span class="n"&gt;INFO&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Engine&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;29&lt;/span&gt; &lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;45&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;09&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;067&lt;/span&gt; &lt;span class="n"&gt;INFO&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Engine&lt;/span&gt; &lt;span class="n"&gt;PRAGMA&lt;/span&gt; &lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;tab&lt;/span&gt;&lt;span class="n"&gt;le_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Covid19&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;29&lt;/span&gt; &lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;45&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;09&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;067&lt;/span&gt; &lt;span class="n"&gt;INFO&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Engine&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;29&lt;/span&gt; &lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;45&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;09&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;069&lt;/span&gt; &lt;span class="n"&gt;INFO&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Engine&lt;/span&gt; 
&lt;span class="n"&gt;CREATE&lt;/span&gt; &lt;span class="nb"&gt;TAB&lt;/span&gt;&lt;span class="n"&gt;LE&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Covid19&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;&amp;quot;index&amp;quot;&lt;/span&gt; &lt;span class="n"&gt;BIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="s"&gt;&amp;quot;dateRep&amp;quot;&lt;/span&gt; &lt;span class="n"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="n"&gt;BIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;month&lt;/span&gt; &lt;span class="n"&gt;BIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="n"&gt;BIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;cases&lt;/span&gt; &lt;span class="n"&gt;BIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;deaths&lt;/span&gt; &lt;span class="n"&gt;BIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="s"&gt;&amp;quot;countriesAndTerritories&amp;quot;&lt;/span&gt; &lt;span class="n"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="s"&gt;&amp;quot;geoId&amp;quot;&lt;/span&gt; &lt;span class="n"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="s"&gt;&amp;quot;countryterritoryCode&amp;quot;&lt;/span&gt; &lt;span class="n"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="s"&gt;&amp;quot;popData2018&amp;quot;&lt;/span&gt; &lt;span class="n"&gt;FLOAT&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;29&lt;/span&gt; &lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;45&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;09&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;069&lt;/span&gt; &lt;span class="n"&gt;INFO&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Engine&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;29&lt;/span&gt; &lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;45&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;09&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;070&lt;/span&gt; &lt;span class="n"&gt;INFO&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Engine&lt;/span&gt; &lt;span class="n"&gt;COMMIT&lt;/span&gt;
&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;29&lt;/span&gt; &lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;45&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;09&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;070&lt;/span&gt; &lt;span class="n"&gt;INFO&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Engine&lt;/span&gt; &lt;span class="n"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;INDEX&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;ix_Covid19_index&amp;quot;&lt;/span&gt; &lt;span class="kr"&gt;ON&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Covid19&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;index&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;29&lt;/span&gt; &lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;45&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;09&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;070&lt;/span&gt; &lt;span class="n"&gt;INFO&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Engine&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;29&lt;/span&gt; &lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;45&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;09&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;071&lt;/span&gt; &lt;span class="n"&gt;INFO&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Engine&lt;/span&gt; &lt;span class="n"&gt;COMMIT&lt;/span&gt;
&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;29&lt;/span&gt; &lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;45&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;09&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;072&lt;/span&gt; &lt;span class="n"&gt;INFO&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Engine&lt;/span&gt; &lt;span class="n"&gt;BEGIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;implicit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;29&lt;/span&gt; &lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;45&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;09&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;074&lt;/span&gt; &lt;span class="n"&gt;INFO&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Engine&lt;/span&gt; &lt;span class="n"&gt;INSERT&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="n"&gt;O&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Covid19&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;index&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;dateRep&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cases&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deaths&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;countriesAndTerritories&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;geoId&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;countryterritoryCode&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;popData2018&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;VAL&lt;/span&gt;&lt;span class="n"&gt;UES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;29&lt;/span&gt; &lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;45&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;09&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;074&lt;/span&gt; &lt;span class="n"&gt;INFO&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Engine&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mf"&gt;7082&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="mf"&gt;28&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;18695&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;411&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;327167434.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;7083&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="mf"&gt;27&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;27&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;16797&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;246&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;327167434.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;7084&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="mf"&gt;26&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;13963&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;249&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;327167434.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;7085&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="mf"&gt;25&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;8789&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;211&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;327167434.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;7086&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="mf"&gt;24&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;11236&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;119&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;327167434.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;7087&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="mf"&gt;23&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;23&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;8459&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;131&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;327167434.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;7088&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="mf"&gt;22&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;7123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;327167434.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;7089&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="mf"&gt;21&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;5374&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;110&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;327167434.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="mf"&gt;...&lt;/span&gt; &lt;span class="n"&gt;displaying&lt;/span&gt; &lt;span class="mf"&gt;10&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="mf"&gt;89&lt;/span&gt; &lt;span class="kr"&gt;to&lt;/span&gt;&lt;span class="n"&gt;tal&lt;/span&gt; &lt;span class="n"&gt;bound&lt;/span&gt; &lt;span class="n"&gt;parameter&lt;/span&gt; &lt;span class="n"&gt;sets&lt;/span&gt; &lt;span class="mf"&gt;...&lt;/span&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;7169&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;327167434.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;7170&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="mf"&gt;31&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;12&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2019&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2019&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;327167434.0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;29&lt;/span&gt; &lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;45&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;09&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;074&lt;/span&gt; &lt;span class="n"&gt;INFO&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Engine&lt;/span&gt; &lt;span class="n"&gt;COMMIT&lt;/span&gt;
&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;29&lt;/span&gt; &lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;45&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;09&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;075&lt;/span&gt; &lt;span class="n"&gt;INFO&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Engine&lt;/span&gt; &lt;span class="n"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="n"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sqlite_master&lt;/span&gt; &lt;span class="n"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="nb"&gt;tab&lt;/span&gt;&lt;span class="n"&gt;le&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;OR&lt;/span&gt;&lt;span class="n"&gt;DER&lt;/span&gt; &lt;span class="n"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;29&lt;/span&gt; &lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;45&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;09&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;075&lt;/span&gt; &lt;span class="n"&gt;INFO&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Engine&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Our table with all of its data should now be all set. Close the database
connection.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;sqlite_connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We can take a look at the data through the &lt;code&gt;sqlite3&lt;/code&gt; command line viewer 
to make sure it was properly saved to the SQLite file.&lt;/p&gt;
&lt;p&gt;On the command line (&lt;strong&gt;not in the Python REPL&lt;/strong&gt;), type:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sqlite3
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This will open up the command line prompt to interact with SQLite
databases. However, we are not yet connected to our &lt;code&gt;save_pandas.db&lt;/code&gt;
file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;SQLite&lt;/span&gt; &lt;span class="nv"&gt;version&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;.&lt;span class="mi"&gt;28&lt;/span&gt;.&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="mi"&gt;2019&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;04&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;:&lt;span class="mi"&gt;49&lt;/span&gt;:&lt;span class="mi"&gt;49&lt;/span&gt;
&lt;span class="nv"&gt;Enter&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;.help&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;usage&lt;/span&gt; &lt;span class="nv"&gt;hints&lt;/span&gt;.
&lt;span class="nv"&gt;Connected&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;a&lt;/span&gt; &lt;span class="nv"&gt;transient&lt;/span&gt; &lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;memory&lt;/span&gt; &lt;span class="nv"&gt;database&lt;/span&gt;.
&lt;span class="nv"&gt;Use&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;.open FILENAME&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;reopen&lt;/span&gt; &lt;span class="nv"&gt;on&lt;/span&gt; &lt;span class="nv"&gt;a&lt;/span&gt; &lt;span class="nv"&gt;persistent&lt;/span&gt; &lt;span class="nv"&gt;database&lt;/span&gt;.
&lt;span class="nv"&gt;sqlite&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Use the &lt;code&gt;.open&lt;/code&gt; command with our &lt;code&gt;save_pandas.db&lt;/code&gt; file name to
access the database. Then use a standard SQL query to obtain all
of the records from the &lt;code&gt;Covid19&lt;/code&gt; table.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sqlite&amp;gt; .open save_pandas.db
sqlite&amp;gt; select * from Covid19;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The SQLite explorer should produce output like you see below:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="mf"&gt;7082&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;28&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;28&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;18695&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;411&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7083&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;27&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;27&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;16797&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;246&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7084&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;26&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;26&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;13963&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;249&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7085&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;25&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;25&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;8789&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;211&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7086&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;24&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;24&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;11236&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;119&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7087&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;23&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;23&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;8459&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;131&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7088&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;22&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;22&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;7123&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;80&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7089&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;21&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;21&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;5374&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;110&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7090&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;4835&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7091&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;19&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;19&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2988&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;42&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7092&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;18&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;18&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1766&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;23&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7093&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;17&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;17&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;887&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;16&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7094&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;16&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;16&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;823&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;12&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7095&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;15&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;15&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;777&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7096&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;14&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;14&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;511&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;7&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7097&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;13&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;13&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;351&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7098&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;12&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;12&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;287&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7099&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;11&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;11&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;271&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7100&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;200&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;5&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7101&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;09&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;9&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;121&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;4&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7102&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;08&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;8&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;95&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7103&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;07&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;7&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;105&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7104&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;06&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;6&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;74&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7105&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;05&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;5&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;34&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7106&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;04&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;4&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;22&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7107&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;14&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;4&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7108&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7109&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7110&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;29&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;29&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;6&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7111&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;28&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;28&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7112&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;27&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;27&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;6&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7113&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;26&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;26&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7114&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;25&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;25&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;18&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7115&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;24&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;24&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7116&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;23&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;23&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7117&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;22&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;22&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;19&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7118&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;21&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;21&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7119&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7120&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;19&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;19&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7121&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;18&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;18&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7122&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;17&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;17&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7123&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;16&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;16&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7124&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;15&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;15&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7125&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;14&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;14&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7126&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;13&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;13&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7127&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;12&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;12&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7128&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;11&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;11&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7129&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7130&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;09&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;9&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7131&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;08&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;8&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7132&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;07&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;7&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7133&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;06&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;6&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7134&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;05&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;5&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7135&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;04&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;4&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7136&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7137&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7138&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7139&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;31&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;31&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7140&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;30&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;30&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7141&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;29&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;29&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7142&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;28&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;28&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7143&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;27&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;27&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7144&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;26&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;26&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7145&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;25&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;25&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7146&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;24&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;24&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7147&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;23&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;23&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7148&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;22&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;22&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7149&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;21&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;21&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7150&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7151&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;19&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;19&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7152&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;18&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;18&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7153&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;17&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;17&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7154&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;16&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;16&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7155&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;15&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;15&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7156&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;14&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;14&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7157&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;13&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;13&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7158&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;12&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;12&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7159&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;11&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;11&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7160&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7161&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;09&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;9&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7162&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;08&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;8&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7163&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;07&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;7&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7164&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;06&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;6&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7165&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;05&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;5&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7166&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;04&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;4&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7167&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;03&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7168&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;02&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7169&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;01&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2020&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="mf"&gt;7170&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;31&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;12&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2019&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;31&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;12&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;2019&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;United_States_of_America&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;US&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="mf"&gt;327167434.0&lt;/span&gt;
&lt;span class="n"&gt;sqlite&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;All of the data with the &lt;code&gt;countriesAndTerritories&lt;/code&gt; column matching
&lt;code&gt;United_States_of_America&lt;/code&gt; is there! We successfully exported the
data from the DataFrame into the SQLite database file.&lt;/p&gt;
&lt;h2&gt;What's Next?&lt;/h2&gt;
&lt;p&gt;We just imported data from a CSV into a pandas DataFrame, selected a 
subset of that data then saved it to a relational database.&lt;/p&gt;
&lt;p&gt;You should take a look at the 
&lt;a href="/blog/learn-pandas-basic-commands-explore-covid-19-data.html"&gt;Learning pandas by Exploring COVID-19 Data&lt;/a&gt;
tutorial to learn more about how to select subsets of data from a
larger DataFrame, or head to the &lt;a href="/pandas.html"&gt;pandas&lt;/a&gt; page for
more tutorials by the rest of the Python community.&lt;/p&gt;
&lt;p&gt;You can also get an idea of what to code next in your Python project by 
reading the 
&lt;a href="/table-of-contents.html"&gt;Full Stack Python table of contents page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Questions? Contact me via Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;. I'm also on GitHub with
the username &lt;a href="https://github.com/mattmakai"&gt;mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Something wrong with this post? Fork 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/200330-pandas-dataframes-sqlalchemy.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Mon, 30 Mar 2020 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2020-03-30:blog/export-pandas-dataframes-sqlite-sqlalchemy.html</guid></item><item><title>Learning pandas by Exploring COVID-19 Data</title><link>https://www.fullstackpython.com/blog/learn-pandas-basic-commands-explore-covid-19-data.html</link><description>&lt;p&gt;The 
&lt;a href="https://www.ecdc.europa.eu/en"&gt;European Centre for Disease Prevention and Control&lt;/a&gt; 
provides 
&lt;a href="https://www.ecdc.europa.eu/en/publications-data/download-todays-data-geographic-distribution-covid-19-cases-worldwide"&gt;daily-updated worldwide COVID-19 data&lt;/a&gt;
that is easy to download in JSON, CSV or XML formats. In this tutorial,
we will use the &lt;a href="/pandas.html"&gt;pandas&lt;/a&gt; data analysis tool on the
comma-separated values (CSV) data to learn some of the basic pandas
commands and explore what is contained within the data set.&lt;/p&gt;
&lt;h2&gt;Configuring our development environment&lt;/h2&gt;
&lt;p&gt;Make sure you have Python 3 installed. As of right now, 
&lt;a href="https://www.python.org/downloads/"&gt;Python 3.8.2&lt;/a&gt; is the latest.&lt;/p&gt;
&lt;p&gt;During this tutorial we're also going to use 
&lt;a href="https://pandas.pydata.org/"&gt;pandas&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Install it now into a new virtual environment with the following
commands:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python -m venv covidpandas
&lt;span class="nb"&gt;source&lt;/span&gt; covidpandas/bin/activate

pip install pandas
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We are now ready to get the COVID-19 data and start analyzing it with
pandas.&lt;/p&gt;
&lt;h2&gt;Obtaining the COVID-19 data&lt;/h2&gt;
&lt;p&gt;Go to the 
&lt;a href="https://www.ecdc.europa.eu/en/publications-data/download-todays-data-geographic-distribution-covid-19-cases-worldwide"&gt;download today’s data on the geographic distribution of COVID-19 cases worldwide&lt;/a&gt; 
page in your web browser. It should look something like the following
screenshot. &lt;/p&gt;
&lt;p&gt;&lt;img src="/img/200328-covid-19-pandas/covid-19-data-download.png" width="100%" class="shot rnd outl" alt="Download the CSV version of the COVID-19 data."&gt;&lt;/p&gt;
&lt;p&gt;There should be a link to download the
data in CSV format, but the organization has changed the page layout
several times in the past few weeks, which makes it difficult to find
formats other than Excel (XLSX). If you have trouble obtaining the
CSV version, just download 
&lt;a href="https://raw.githubusercontent.com/fullstackpython/blog-code-examples/master/pandas-covid-19/covid-19-cases-march-28-2020.csv"&gt;this one from GitHub&lt;/a&gt;
which is pegged to a copy downloaded on March 28th, 2020.&lt;/p&gt;
&lt;h2&gt;Importing the CSV into pandas&lt;/h2&gt;
&lt;p&gt;We have the data in a CSV now we need to import it into a pandas
DataFrame.&lt;/p&gt;
&lt;p&gt;Start by running the Python REPL:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python

&amp;gt;&amp;gt;&amp;gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The REPL is ready to go, now we need to import pandas so we can read
the data we downloaded.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pandas&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;read_csv&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;covid-19-cases-march-28-2020.csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Don't worry if you get an error like
&lt;code&gt;UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe7...&lt;/code&gt;. 
Run this command instead which explicitly sets the file encoding 
so pandas can properly read the CSV.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# make sure the file name of the csv matches your file&amp;#39;s name!&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;covid-19-cases-march-28-2020.csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ISO-8859-1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We now have our data loaded into a 
&lt;a href="https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html"&gt;pandas DataFrame&lt;/a&gt;
and can start running code to poke and prod and what's inside the
data set.&lt;/p&gt;
&lt;h2&gt;Running pandas commands&lt;/h2&gt;
&lt;p&gt;Let's first take a peek at what a sample of the data looks like. I
typically run the &lt;code&gt;head&lt;/code&gt; and &lt;code&gt;tail&lt;/code&gt; functions when I open something
up to find out what are contained in the first five and last five rows.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;You should see six lines of output: one as the columns header and the
first five rows of data from the CSV:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;      dateRep  day  month  year  cases  deaths countriesAndTerritories geoId countryterritoryCode  popData2018
0  28/03/2020   28      3  2020     16       1             Afghanistan    AF                  AFG   37172386.0
1  27/03/2020   27      3  2020      0       0             Afghanistan    AF                  AFG   37172386.0
2  26/03/2020   26      3  2020     33       0             Afghanistan    AF                  AFG   37172386.0
3  25/03/2020   25      3  2020      2       0             Afghanistan    AF                  AFG   37172386.0
4  24/03/2020   24      3  2020      6       1             Afghanistan    AF                  AFG   37172386.0
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;tail&lt;/code&gt; function looks at the last five rows in a DataFrame.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;df.tail()
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;code&gt;tail&lt;/code&gt; output will look something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;         dateRep  day  month  year  cases  deaths countriesAndTerritories geoId countryterritoryCode  popData2018
7315  25/03/2020   25      3  2020      0       0                Zimbabwe    ZW                  ZWE   14439018.0
7316  24/03/2020   24      3  2020      0       1                Zimbabwe    ZW                  ZWE   14439018.0
7317  23/03/2020   23      3  2020      0       0                Zimbabwe    ZW                  ZWE   14439018.0
7318  22/03/2020   22      3  2020      1       0                Zimbabwe    ZW                  ZWE   14439018.0
7319  21/03/2020   21      3  2020      1       0                Zimbabwe    ZW                  ZWE   14439018.0
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Note that you can also pass an integer into &lt;code&gt;head&lt;/code&gt; or &lt;code&gt;tail&lt;/code&gt; like
&lt;code&gt;df.head(10)&lt;/code&gt; to get the first or last &lt;strong&gt;n&lt;/strong&gt; number of rows.&lt;/p&gt;
&lt;p&gt;It looks like based on the &lt;code&gt;tail&lt;/code&gt; function we have around 7320 rows of
data (since the first row is 0 indexed). We can confirm how much
data is in each column with the &lt;code&gt;count&lt;/code&gt; function.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;df.count()
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;code&gt;count&lt;/code&gt;'s output will look like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;dateRep                    7320
day                        7320
month                      7320
year                       7320
cases                      7320
deaths                     7320
countriesAndTerritories    7320
geoId                      7306
countryterritoryCode       7254
popData2018                7311
dtype: int64
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;What if we want to look at one of those columns and find, for example,
the highest value of cases?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;df.cases.max()
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;In this data set we get 18695 as the output. What about looking at
standard statistical measures across all columns? That's where the
&lt;code&gt;describe&lt;/code&gt; function comes in handy.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;df.describe()
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;code&gt;describe&lt;/code&gt; presents standard statistical measures such as min, max,
median and mean for everything in your data set. In this case we
receive as output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;               day        month         year         cases       deaths   popData2018
count  7320.000000  7320.000000  7320.000000   7320.000000  7320.000000  7.311000e+03
mean     16.828142     2.249454  2019.990847     80.870355     3.687158  7.130483e+07
std       8.322981     1.256463     0.095239    608.270244    35.327689  2.140624e+08
min       1.000000     1.000000  2019.000000     -9.000000     0.000000  1.000000e+03
25%      10.000000     1.000000  2020.000000      0.000000     0.000000  4.137309e+06
50%      18.000000     2.000000  2020.000000      0.000000     0.000000  1.072767e+07
75%      24.000000     3.000000  2020.000000      5.000000     0.000000  5.139301e+07
max      31.000000    12.000000  2020.000000  18695.000000   971.000000  1.392730e+09
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;How about a quick view into whether or not columns' data are correlated
with each other? The &lt;code&gt;corr&lt;/code&gt; function is what we need.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;df.corr()
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;For our data set, &lt;code&gt;corr&lt;/code&gt; outputs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;                  day     month      year     cases    deaths  popData2018
day          1.000000  0.203006 -0.163665  0.063629  0.060075    -0.040677
month        0.203006  1.000000 -0.745912  0.062494  0.052707    -0.039131
year        -0.163665 -0.745912  1.000000  0.012715  0.010032    -0.006294
cases        0.063629  0.062494  0.012715  1.000000  0.716968     0.136580
deaths       0.060075  0.052707  0.010032  0.716968  1.000000     0.082229
popData2018 -0.040677 -0.039131 -0.006294  0.136580  0.082229     1.000000
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Not surprisingly, we see 1.000000 correlation between a column and itself.
We'd have to worry if we didn't see that result! For other columns it may
not make sense to look at their correlation. This is where you need to 
think about the data. There is often correlation between completely unrelated
columns just because the data is structured a certain way. &lt;/p&gt;
&lt;p&gt;If you are a developer like me without a rigorous background in statistics
(Stats 200 in college was a &lt;strong&gt;long&lt;/strong&gt; time ago), you may need to brush up
on your stats knowledge before you are able to say whether something in the
data matters or not.&lt;/p&gt;
&lt;p&gt;Let's keep going exploring the data. We can select columns and determine how
many unique items are held within it. For example, how many unique countries
and territories are listed?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;df.countriesAndTerritories.nunique()
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;In this case the result should be 196.&lt;/p&gt;
&lt;h2&gt;Asking questions of the data&lt;/h2&gt;
&lt;p&gt;Those functions are fine for basic querying to learn what's in the
data set, but how do we ask real questions by stringing together some 
commands?&lt;/p&gt;
&lt;p&gt;We now know there are 7320 rows in this set since we used the &lt;code&gt;count&lt;/code&gt;
function above. Each row represents a single day within a country. Now
to ask a question. How many days across these countries were there 10
or more cases reported?&lt;/p&gt;
&lt;p&gt;Let's create a new dataframe named df2 with the rows that only have
10 or more cases reported on that day, then count the number of rows
within it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;df2 = df[df[&amp;#39;cases&amp;#39;]&amp;gt;=10]
df2.count()
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;That should give us the value 1531. There have been 1531 instances
of 10 or more COVID-19 cases reported on a single day, across the
196 countries or terrorities listed. But the 1531 is hard to explain
to people. We should pick out a single country and show how many times
10 or more cases were reported on one day. How about a smaller
country like Vietnam that is not being reported on as much as China,
the United States or Italy?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;df2[df2[&amp;#39;countriesAndTerritories&amp;#39;]==&amp;#39;Vietnam&amp;#39;]
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This will give us the full output of data by column:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;         dateRep  day  month  year  cases  deaths countriesAndTerritories geoId countryterritoryCode  popData2018
7217  28/03/2020   28      3  2020     16       0                 Vietnam    VN                  VNM   95540395.0
7219  26/03/2020   26      3  2020     14       0                 Vietnam    VN                  VNM   95540395.0
7220  25/03/2020   25      3  2020     11       0                 Vietnam    VN                  VNM   95540395.0
7222  23/03/2020   23      3  2020     24       0                 Vietnam    VN                  VNM   95540395.0
7226  19/03/2020   19      3  2020     15       0                 Vietnam    VN                  VNM   95540395.0
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We can also use the &lt;code&gt;count&lt;/code&gt; function here to confirm there have been
five days in which 10 or more new cases have been reported in Vietnam
so far:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;df2[df2[&amp;#39;countriesAndTerritories&amp;#39;]==&amp;#39;Vietnam&amp;#39;].count()
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We get the output of 5 for the columns. Unfortunately, when you look at
the full data it appears these rows are all very recent and the virus
is just beginning to spread more widely there. Let's hope they along
with every other country is able to turn the tide, flatten the curve
and keep more people from getting sick as we continue onwards.&lt;/p&gt;
&lt;p&gt;That's a good spot to leave off, but we covered a lot of pandas ground
in this tutorial!&lt;/p&gt;
&lt;h2&gt;What's next?&lt;/h2&gt;
&lt;p&gt;We just imported and took a look at what's in the European Centre 
for Disease Prevention and Control's COVID-19 data set using
&lt;a href="/pandas.html"&gt;pandas&lt;/a&gt;. That was a quick tour of some basic pandas
commands and I strongly recommend you peruse the 
&lt;a href="https://pandas.pydata.org/pandas-docs/stable/reference/frame.html"&gt;DataFrame documentation list&lt;/a&gt;
to learn about all of the other handy functions that this tool
provides to developers.&lt;/p&gt;
&lt;p&gt;You can also get an idea of what to code next in your Python project by 
reading the 
&lt;a href="/table-of-contents.html"&gt;Full Stack Python table of contents page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Questions? Contact me via Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;. I'm also on GitHub with
the username &lt;a href="https://github.com/mattmakai"&gt;mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Something wrong with this post? Fork 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/200328-explore-covid-pandas.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Sat, 28 Mar 2020 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2020-03-28:blog/learn-pandas-basic-commands-explore-covid-19-data.html</guid></item><item><title>The Best Resources for Developers to Learn Finance</title><link>https://www.fullstackpython.com/blog/best-resources-developers-learn-finance.html</link><description>&lt;p&gt;Software developers should understand the basics of finance not only 
to manage their own money but also to understand how businesses' software 
projects are funded. &lt;/p&gt;
&lt;p&gt;Understanding how other people who work in accounting, finance and project 
management think about business and finance in particular can help you make 
better architectural decisions when trying to build maintainable systems.
Code is only one aspect of a large software project so working with others
and viewing the world through their discipline will help you immensely as
you advance your career.&lt;/p&gt;
&lt;h2&gt;Newsletters &amp;amp; Podcasts on Finance&lt;/h2&gt;
&lt;p&gt;The fastest way to take a first step in improving your financial literacy
is to subscribe to a few free newsletters that regularly hit your inbox,
or a podcast if listening better fits your daily routine. I read and listen
to each of the following newsletters and podcasts to pick up on unfamiliar
topics then do more of my own research if I do not understand what they
are talking or writing about.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.bloomberg.com/opinion/authors/ARbTQlRLRjE/matthew-s-levine"&gt;Money Stuff by Matt Levine of Bloomberg&lt;/a&gt;
  (&lt;a href="https://link.mail.bloombergbusiness.com/join/4wm/moneystuff-signup"&gt;newsletter sign up form&lt;/a&gt;)
  is a hilarious must-read daily newsletter that covers the world of
  finance and breaks down many absurd situations such as financial
  fraud, insider trading, or competing interests in credit default swaps.
  Amazingly, the author stays out of political topics, which I find very
  refreshing because many other journalists seem to force their own biases 
  about finance down your throat even if you do not want their opinions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://endlessmetrics.substack.com"&gt;Endless Metrics&lt;/a&gt; explains financial
  topics in a way that's easy for anyone without a finance background to 
  understand. For example, 
  &lt;a href="https://endlessmetrics.substack.com/p/reading-a-gdp-chart"&gt;what the heck is GDP and how do you read a GDP chart?&lt;/a&gt;. 
  What I love most about this newsletter is that the author will often 
  venture into finance-related topics he's interested in and then explain 
  those subjects while grounding them with useful charts and data.
  This analytic approach closely matches how my developer brain processes
  information!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.bloomberg.com/authors/AT2bBytfUHQ/john-authers"&gt;Points of Return by John Auther&lt;/a&gt;
  (&lt;a href="http://link.mail.bloombergbusiness.com/join/4wm/opinion-authers-signup"&gt;newsletter sign up form&lt;/a&gt;).
  This author is incredibly knowledgeable about finance and typically 
  provides a solid grounding in long-term fundamentals rather than the
  short-term hyperbole that is pervasive in cable television financial
  journalism.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.bloomberg.com/podcasts/odd_lots"&gt;Odd Lots&lt;/a&gt; covers kind
  of whatever topics the hosts find interesting such as pandemic bonds, 
  repo market disruption, sovereign debt restructuring and emerging 
  markets. That's why it's so good - the hosts bring on an expert in that 
  topic and ask a ton of great questions because they want to learn
  what's going on for themselves. You follow along with them as they
  try to understand some of the oft-esoteric subject areas of finance.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Books, Websites and Magazines for Finance&lt;/h2&gt;
&lt;p&gt;Newsletters and podcasts are great for prodding you into discovering
topics you did not know you needed to learn. When you discover something
that you want to go deeper on in finance, here are a few of my favorite
books and websites that range from the very basics of finance to broader
macroeconomic data trends.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;I learned most of my basic finance knowledge when I read 
  &lt;a href="https://www.amazon.com/Financial-Intelligence-Professionals-Really-Numbers/dp/1422119149"&gt;Financial Intelligence for IT Professionals&lt;/a&gt;
  in graduate school (&lt;a href="https://www.virginia.edu/"&gt;go Hoos&lt;/a&gt;!). The book
  is well-written, straightforward and accessible, particularly because
  it clearly targets its software developer audience.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://dqydj.com"&gt;Don't Quit Your Day Job&lt;/a&gt; uses a ton of metrics
  and statistics to ground their articles on financial topics that
  are often relevant specifically to software developers. For example,
  the article on 
  &lt;a href="https://dqydj.com/number-of-developers-in-america-and-per-state/"&gt;How Many Developers are There in America, and Where Do They Live?&lt;/a&gt;
  is fascinating and especially useful because they explain their
  data sources and analysis methodology.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://money.com/"&gt;Money Magazine&lt;/a&gt; can be useful to pick up in paper 
  edition for a few months to understand personal finance basics. After a 
  few months you'll discover the articles and topics tend to recycle so 
  there are diminishing returns to reading it after you have familiarized 
  yourself with most of the topics.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.longtermtrends.net/"&gt;Longtermtrends&lt;/a&gt; aggregates long term
  high-level financial data and displays it. I find looking at these
  charts gets me away from the day-to-day "oh the stock market is down"
  and towards thinking about what happens when you invest money over many 
  years or decades.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Specific Articles on Financial Topics&lt;/h2&gt;
&lt;p&gt;The following individual articles I have found to be both well-written and
extremely useful for specific scenarios such as evaluating stock-based
equity compensation, or negotiating your salary.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.kalzumeus.com/2012/01/23/salary-negotiation/"&gt;Salary negotiation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://blog.alexmaccaw.com/an-engineers-guide-to-stock-options"&gt;Stock Options&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/jlevy/og-equity-compensation"&gt;Open Guide to Equity Compensation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Sun, 08 Mar 2020 00:00:00 -0500</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2020-03-08:blog/best-resources-developers-learn-finance.html</guid></item><item><title>Basic Data Types in Python 3: Booleans</title><link>https://www.fullstackpython.com/blog/python-basic-data-types-booleans.html</link><description>&lt;p&gt;Welcome back to our ongoing series of blog posts on basic data types in 
&lt;a href="/python-2-or-3.html"&gt;Python 3&lt;/a&gt;! Last time, we explored the functionality of
&lt;a href="/blog/python-basic-data-types-strings.html"&gt;strings&lt;/a&gt;. Today, we dive in to
another key data type - booleans. Booleans (and "boolean logic") are an 
important concept in programming, representing the concept of "true" and "false".&lt;/p&gt;
&lt;p&gt;If you're learning Python, you might also want to
&lt;a href="https://www.twilio.com/quest/download"&gt;check out TwilioQuest 3&lt;/a&gt;.
You'll learn about basic data types like the boolean, and much more about 
Python programming.&lt;/p&gt;
&lt;p&gt;Ready to learn how to use booleans in Python 3? Let's get started!&lt;/p&gt;
&lt;h2&gt;Booleans in Python 3&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not"&gt;Booleans&lt;/a&gt;
are a concept that exists in every programming language. A boolean represents
the idea of "true" or "false". When you are writing a program, there
are often circumstances where you want to execute different code in different
situations. Booleans enable our code to do just that.&lt;/p&gt;
&lt;p&gt;You can declare a boolean value in your code using the keywords &lt;code&gt;True&lt;/code&gt; and 
&lt;code&gt;False&lt;/code&gt; (note the uppercase). The following code would create two boolean
values and assign them to variables.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;mullet_looks_good&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;span class="n"&gt;python_is_fun&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;More commonly, a boolean value is returned as a result of some kind of
comparison. The following code example would store a boolean value of &lt;code&gt;False&lt;/code&gt;
in the &lt;code&gt;have_same_name&lt;/code&gt; variable after using the 
&lt;a href="https://docs.python.org/3/library/stdtypes.html#comparisons"&gt;equality comparison operator&lt;/a&gt;,
the &lt;code&gt;==&lt;/code&gt; symbol.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;my_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Wammu&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;your_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Kars&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;have_same_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;my_name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;your_name&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;Boolean logic&lt;/h3&gt;
&lt;p&gt;Booleans are used in your code to make it behave differently based on current
conditions within your program. You can use boolean values and comparisons in 
conjunction with the &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;elif&lt;/code&gt;, and &lt;code&gt;else&lt;/code&gt; keyoards as one means to achieve 
this.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;my_age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;my_age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;One hundred years old! Very impressive.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;my_age&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Awwww. Just a baby.&amp;quot;&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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Ah - a very fine age indeed&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;In addition to testing for truth, you can also check if conditions are not
true using the &lt;code&gt;not&lt;/code&gt; keyword.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;favorite_team&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Vikings&amp;quot;&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;favorite_team&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Vikings&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Oh - how unfortunate.&amp;quot;&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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Skol, Vikings!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;More complex boolean logic&lt;/h3&gt;
&lt;p&gt;Sometimes you will need to evaluate multiple conditions in your boolean logic.
For this purpose, you'll combine the &lt;code&gt;and&lt;/code&gt; and &lt;code&gt;or&lt;/code&gt; keywords. The &lt;code&gt;and&lt;/code&gt; keyword
compares two boolean values and returns &lt;code&gt;True&lt;/code&gt; if both are true. The &lt;code&gt;or&lt;/code&gt; keyword
compares two values and returns &lt;code&gt;True&lt;/code&gt; if any of the statements are true.&lt;/p&gt;
&lt;p&gt;Let's look at an example. That uses the &lt;code&gt;in&lt;/code&gt; keyword to see if a string is
inside a &lt;strong&gt;list&lt;/strong&gt; of values (we'll cover lists in a future article).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;favs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Donatello&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Raphael&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Michelangelo&amp;quot;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;favs&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Donatello&amp;quot;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;favs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Those are my favorite ninja turtles too!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Michelangelo&amp;quot;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;favs&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Donatello&amp;quot;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;favs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Well, one out of two isn&amp;#39;t bad...&amp;quot;&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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Huh - not what I would have chosen.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Booleans are an important tool in any programming language. Using boolean logic,
your code can react to data inside your program, and carry out different
instructions under different circumstances. Hopefully, you've learned a bit 
about how to work with booleans in Python 3! Stay tuned for more blog posts in 
this series to learn more about basic data types like strings, numbers, 
booleans, lists, and dictionaries.&lt;/p&gt;
&lt;p&gt;Also, be sure to 
&lt;a href="https://www.twilio.com/quest/download"&gt;download and play TwilioQuest 3&lt;/a&gt;
to learn even more about Python! &lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Kevin Whinnery</dc:creator><pubDate>Fri, 15 Nov 2019 00:00:00 -0500</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2019-11-15:blog/python-basic-data-types-booleans.html</guid></item><item><title>Basic Data Types in Python 3: Strings</title><link>https://www.fullstackpython.com/blog/python-basic-data-types-strings.html</link><description>&lt;p&gt;There is a lot to learn on your Python journey when you are
&lt;a href="/learning-programming.html"&gt;new to the programming language&lt;/a&gt;. Once you are 
comfortable writing and executing code, your first stop becomes understanding 
how to represent data in
your code. No matter the language, there are a few basic data types you'll use
all the time - strings, numbers, booleans, lists, and dictionaries.&lt;/p&gt;
&lt;p&gt;Those data types, and how to use them in Python 3, are the topic of this blog 
post series. Today, we're starting with &lt;strong&gt;strings&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;If you're learning Python, you might also want to
&lt;a href="https://www.twilio.com/quest/download"&gt;check out TwilioQuest 3&lt;/a&gt;.
You'll learn about basic data types and much more about Python programming.&lt;/p&gt;
&lt;p&gt;Ready to learn how to use strings in Python 3? Let's get started!&lt;/p&gt;
&lt;h2&gt;Strings in Python 3&lt;/h2&gt;
&lt;p&gt;One of the most common data types in any programming language is a &lt;code&gt;string&lt;/code&gt;. A
&lt;strong&gt;string&lt;/strong&gt; represents a series of characters, which you would use to represent
usernames, blog posts, tweets, or any text content in your code. You can create
a string and assign it to a variable like this.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;my_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Jonathan Joestar&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;Strings are "immutable"&lt;/h3&gt;
&lt;p&gt;In Python, strings are considered &lt;a href="https://www.merriam-webster.com/dictionary/immutable"&gt;immutable&lt;/a&gt; - 
once you create them, they can't be changed. You can, however, use a variety of
methods to create new strings from existing strings. This type of work in
programming is called &lt;strong&gt;string manipulation&lt;/strong&gt;. Some web developers joke that at
the end of the day, their job is just mashing strings together - and this isn't
far from the truth!&lt;/p&gt;
&lt;p&gt;Here are some common tasks you might undertake when using strings in your code.&lt;/p&gt;
&lt;h3&gt;Common task - combining strings together&lt;/h3&gt;
&lt;p&gt;Combining strings together - &lt;strong&gt;concatenating&lt;/strong&gt; them - is a very common task. In
Python 3, you can use the &lt;code&gt;+&lt;/code&gt; operator for this purpose. You can use the &lt;code&gt;+&lt;/code&gt;
operator multiple times to concatenate multiple strings.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;first_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Jonathan&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;last_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Joestar&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;full_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;first_name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot; &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;last_name&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;Common task - inserting data into strings&lt;/h3&gt;
&lt;p&gt;Another common task with strings is inserting data into a specific place
within a string. In programming, we call this &lt;strong&gt;string interpolation&lt;/strong&gt;. Python 3
provides a handy tool for doing this called &lt;a href="https://www.python.org/dev/peps/pep-0498/"&gt;"f" strings&lt;/a&gt;.
The "f" in "f strings" stands for &lt;strong&gt;format&lt;/strong&gt; - you can insert other data from
your program into a string when you define it rather than doing complex string
concatenation as demonstrated previously.&lt;/p&gt;
&lt;p&gt;Here is an example of creating a formatted string - note the letter &lt;code&gt;f&lt;/code&gt; is
included just before the first double quote when defining the &lt;code&gt;message&lt;/code&gt; variable.
When you want to insert data from your program into the string, you can include 
it between two "curly braces" - the &lt;code&gt;{&lt;/code&gt; and &lt;code&gt;}&lt;/code&gt; characters.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;first_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Jonathan&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;last_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Joestar&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;

&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;My name is &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;last_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, and I am &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; years old.&amp;quot;&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h3&gt;Common task - using built-in string methods to manipulate strings&lt;/h3&gt;
&lt;p&gt;String objects have a number of &lt;a href="https://docs.python.org/3/library/stdtypes.html#string-methods"&gt;methods&lt;/a&gt;
to perform common tasks, like changing the case of strings or trimming their 
content. Below, you'll find a few examples. In two of these examples, we are
creating a string variable, and then assigning the same variable a new value,
which is the result of calling a method on a string object.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example 1:&lt;/strong&gt; Convert a string to all caps using the &lt;code&gt;upper&lt;/code&gt; method.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;example_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;am I stoked enough yet?&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;example_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;example_string&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;example_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# prints &amp;quot;AM I STOKED ENOUGH YET?&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Example 2:&lt;/strong&gt; Replace all instances of the word &lt;code&gt;kale&lt;/code&gt; with &lt;code&gt;tacos&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;example_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;We&amp;#39;re having kale for dinner! Yay kale!&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;example_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;example_string&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;kale&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;tacos&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;example_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# prints &amp;quot;We&amp;#39;re having tacos for dinner! Yay tacos!&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Example 3:&lt;/strong&gt; Split a comma-delimited string into a list of strings.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;example_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Apples,Oranges,Pears&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;groceries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;example_string&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;,&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Code below prints:&lt;/span&gt;
&lt;span class="c1"&gt;# Apples&lt;/span&gt;
&lt;span class="c1"&gt;# Oranges&lt;/span&gt;
&lt;span class="c1"&gt;# Pears&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;groceries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;a href="https://docs.python.org/3/library/stdtypes.html#string-methods"&gt;Check our more strings can do&lt;/a&gt;
in the Python 3 docs!&lt;/p&gt;
&lt;h2&gt;Type casting&lt;/h2&gt;
&lt;p&gt;Frequently, you will want to convert data from one type into another. In 
programming, we call this process &lt;strong&gt;type casting&lt;/strong&gt;. There are a number of 
&lt;strong&gt;functions&lt;/strong&gt; built in to Python which allow us to do these type conversions
on basic data types.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example 1:&lt;/strong&gt; Convert a number into a string using the &lt;code&gt;str&lt;/code&gt; function.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;example_number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
&lt;span class="n"&gt;converted&lt;/span&gt; &lt;span class="o"&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;example_number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;The meaning of life is &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;converted&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Example 2:&lt;/strong&gt; Convert a string into a whole number (integer) using &lt;code&gt;int&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;example_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;converted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;example_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Two plus two equals &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="n"&gt;converted&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Strings of text are one of the most common pieces of data you will work with
in programming. Hopefully, you've learned a bit about how to work with strings
in Python 3! Stay tuned for more blog posts in this series to learn more about
basic data types like strings, numbers, booleans, lists, and dictionaries.&lt;/p&gt;
&lt;p&gt;Also, be sure to 
&lt;a href="https://www.twilio.com/quest/download"&gt;download and play TwilioQuest 3&lt;/a&gt;
to learn even more about Python! &lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Kevin Whinnery</dc:creator><pubDate>Fri, 18 Oct 2019 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2019-10-18:blog/python-basic-data-types-strings.html</guid></item><item><title>Developer-led Sales for Startups</title><link>https://www.fullstackpython.com/blog/developer-led-sales-startups.html</link><description>&lt;p&gt;This blog post contains the slides along with a loose transcript
from my talk on the promises and perils of developer-led sales as an
early-stage company method to acquire customers. &lt;/p&gt;
&lt;p&gt;I gave this talk remotely to &lt;a href="http://www.ubiquity.vc/"&gt;Ubiquity.VC&lt;/a&gt; 
portfolio company startup founders and the Extended Team on June 26, 2019.&lt;/p&gt;
&lt;hr /&gt;
&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/01-title.jpg" width="100%" class="shot rnd outl" alt="Title slide for this talk on Developer-Led Sales for Startups."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/02-author-info.jpg" width="100%" class="shot rnd outl" alt="Information about the author, Matt Makai."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
Hey folks, my name is &lt;a href="/about-author.html"&gt;Matt Makai&lt;/a&gt;. I serve the
&lt;a href="https://www.youtube.com/watch?v=TF129ioe8kc"&gt;Developer Network&lt;/a&gt; at
&lt;a href="https://www.twilio.com/"&gt;Twilio&lt;/a&gt;. I'm also part of the Extended
Team at &lt;a href="https://www.ubiquity.vc/"&gt;Ubiquity Ventures&lt;/a&gt;.
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/03-dream.jpg" width="100%" class="shot rnd outl" alt="Section title slide for the dream of developer-led sales."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
I often meet startup founders who ask me "how can my company do what
Twilio does with developers?"&lt;/p&gt;
&lt;p&gt;
I respond by asking, "what do see Twilio doing and want to replicate 
for developers who interact with your business?"
&lt;/p&gt;
&lt;p&gt;
That's when they usually tell me about their "the dev-led sales dream".
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/04-dev-events.jpg" width="100%" class="shot rnd outl" alt="Developers at a hackathon working on code."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
Their dream is that software developers go to tech events, like 
hackathons, conferences and meetups. While at an event, developers 
see a new &lt;a href="/application-programming-interfaces.html"&gt;API&lt;/a&gt; 
or technical product that looks interesting.
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/05-code.jpg" width="100%" class="shot rnd outl" alt="Code in a text editor."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
Immediately those developers implement the API in their own projects.
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/06-joint-success.jpg" width="100%" class="shot rnd outl" alt="Your revenue grows with their usage."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
Boom, success!
&lt;/p&gt;&lt;p&gt;
You make more money as the company that the developer works
for uses increasing amounts of your product. Take that money,
reinvest in the business to grow and improve your product.
&lt;/p&gt;&lt;p&gt;
Rinse and repeat until the IPO and beyond.
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/07-early-stage.jpg" width="100%" class="shot rnd outl" alt="Early stage example dev-led sales companies."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
There are examples of this developer-led marketing and sales success 
story across many early-stage and mid-stage companies.

For example, &lt;a href="https://rollbar.com/"&gt;Rollbar&lt;/a&gt; and 
&lt;a href="https://www.datadoghq.com/"&gt;Datadog&lt;/a&gt; are doing well by 
focusing on developer adoption with great technical content for their 
monitoring and analytics tools.
&lt;/p&gt;&lt;p&gt;
&lt;a href="https://www.getpostman.com/"&gt;Postman&lt;/a&gt; recently raised 
&lt;a href="https://pitchbook.com/newsletter/postman-picks-up-50m-series-b"&gt;$50 million&lt;/a&gt;
in a Series B round of venture capital to expand their API testing
developer tools. Postman was founded in large part because the original
tool was virally adopted by developers building APIs and 
&lt;a href="/microservices.html"&gt;microservices&lt;/a&gt;.
&lt;/p&gt;&lt;p&gt;
&lt;a href="https://www.digitalocean.com/"&gt;DigitalOcean&lt;/a&gt;'s extensive
&lt;a href="https://www.digitalocean.com/community/tutorials"&gt;community-written tutorials&lt;/a&gt;
have endeared them to developers and has allowed them to compete 
in a world where providing cloud infrastructure pits you against AWS,
Google and Microsoft.
&lt;/p&gt;&lt;p&gt;
Citus Data's developer-focused &lt;a href="/databases.html"&gt;database&lt;/a&gt; 
content and &lt;a href="/postgresql.html"&gt;PostgreSQL&lt;/a&gt; offerings led them to 
&lt;a href="https://blogs.microsoft.com/blog/2019/01/24/microsoft-acquires-citus-data-re-affirming-its-commitment-to-open-source-and-accelerating-azure-postgresql-performance-and-scale/"&gt;a successful exit via Microsofta acquisition&lt;/a&gt;.
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/08-public-companies.jpg" width="100%" class="shot rnd outl" alt="Public and large private successful dev-led sales companies."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
Then you have the massive success stories of developer-focused companies,
such as publicly-traded &lt;a href="https://www.okta.com/"&gt;Okta&lt;/a&gt;
&lt;a href="https://www.pluralsight.com/"&gt;Pluralsight&lt;/a&gt;, 
&lt;a href="https://slack.com/"&gt;Slack&lt;/a&gt;
and 
&lt;a href="https://www.twilio.com/"&gt;Twilio&lt;/a&gt;. Still-private 
&lt;a href="https://stripe.com/"&gt;Stripe&lt;/a&gt; recently raised more 
venture capital money at a $22 billion valuation which is more than 
those four public companies.
&lt;/p&gt;&lt;p&gt;
&lt;a href="https://aws.amazon.com/"&gt;Amazon Web Services (AWS)&lt;/a&gt;, 
while not independent of its 
&lt;a href="https://investinganswers.com/dictionary/m/mega-cap"&gt;mega cap&lt;/a&gt;
parent company, arguably is by far the leader in cloud computing 
infrastructure due to its initial and continuing focus on grassroots
developer product adoption.
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/09-reality.jpg" width="100%" class="shot rnd outl" alt="Subsection reality."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
But how does that startup dream match the reality of the work and skills
required to execute on the vision?
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/10-devrel-skills.jpg" width="100%" class="shot rnd outl" alt="Developer relations skillset."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
You, as the founder, should serve as the chief evangelist for your 
product during the early stages of your company. That role
never stops regardless of how large and successful you become. 
&lt;p&gt;
But your time is split in a hundred different ways so eventually you 
need to hire someone with a nuanced understanding of how to 
&lt;strong&gt;appropriately&lt;/strong&gt; market your company to developers.
Hiring the wrong person for the job is going to be the equivalent of
adding a 
&lt;a href="http://wiki.c2.com/?NetNegativeProducingProgrammer"&gt;net negative producing programmer&lt;/a&gt; 
to your development team at a critical stage of growth. A poor result
could sink your entire company.
&lt;/p&gt;
&lt;p&gt;
The right person for the job likely has a combination of significant
heads-down software development experience, humility, patience,
strong writing and solid speaking abilities.
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/11-devrel-expense.jpg" width="100%" class="shot rnd outl" alt="Developer relations is expensive."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
That combination of skills is exceedingly rare, which according to 
supply and demand dictates a large compensation premium over a 
programmy-only skill set. If you've done any recruiting for top notch 
developer relations folks, you may have already discovered this issue
which is prohibitive for startup companies trying to compete with 
better-capitalized firms.
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/12-what-is-dev-led.jpg" width="100%" class="shot rnd outl" alt="Subsection for what is dev-led sales."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
We've done a bunch of talking already about the developer-led sales
dream for startups, but what exactly &lt;em&gt;is&lt;/em&gt; developer-led sales?
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/13-dev-buyers.jpg" width="100%" class="shot rnd outl" alt="Developers are your first buyers."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
Developer-led sales mean that developers are your primary customers. The
goal is to have developers see the value in your product, sign up, start
using it and spend some amount of money that is relevant to your business.
This approach typically works best with a usage-based model where
development is cheap or free then costs scale with the consumption of your
product.
&lt;/p&gt;&lt;p&gt;
For example, 
&lt;a href="https://www.twilio.com/docs/sms/api"&gt;Twilio's SMS API&lt;/a&gt; 
is free to get started with during development. Then for testing and 
production text messages can be sent and received for .00075 US dollars 
each. A developer can sign up with their credit card to test that your 
product actually does what you say it can do, then scale up as their
application goes to production.
&lt;/p&gt;
&lt;p&gt;
Developer-led sales stands in contrast to the typical business
model where a business development representative (often shortened to
"BDR") prospects for potential customers, outbound emails and calls 
those prospects, then hands off to a more senior sales representative
to work on closing a deal that is large enough to justify the time
invested in the sales cycle.
&lt;/p&gt;
&lt;p&gt;In this traditional model, your startup sells directly to 
stakeholders who control a relatively large budget and are authorized 
to spend that money. Developers usually do not have significant 
budget to spend but can often spend a small amount on a credit card 
and expense it without an issue. That's why dev-led sales is possible
as an alternative sales model.
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/14-dev-led-not.jpg" width="100%" class="shot rnd outl" alt="Dev-led sales is not sales engineering or dev-only sales."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
We should also confirm what developer-led sales is not. It's not sales 
engineering, where your technical folks support a traditional top-down
large sales deal.
&lt;p&gt;
Developer-led sales is also not developer-only sales. Other stakeholders
remain important. When your product is successfully implemented then you
still have to ensure non-developer stakeholders are part of your sales 
process.
&lt;/p&gt;
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/15-global.jpg" width="100%" class="shot rnd outl" alt="Empty map no developers using your product before launch."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
Here's another way to think about a developer-led approach to product
adoption and sales.
&lt;/p&gt;&lt;p&gt;
Before your product exists, no developers anywhere are using and
paying for what you are offering, as represented by this dark world map.
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/16-global-dev-adoption.jpg" width="100%" class="shot rnd outl" alt="Initial developer adoption upon launch."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
A few risk-taking developers who have a problem you are solving 
"raise their hands" with code by using your product or service
once you launch.
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/17-global-more-devs.jpg" width="100%" class="shot rnd outl" alt="Over time many more developers use your product."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
When you are successful, more and more developers use your products and 
serve as indicators that your product is solving a real problem. Those
developers can be your first hook into larger deals with organizations 
where those developers work.
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/18-worthwhile.jpg" width="100%" class="shot rnd outl" alt="When is dev-led sales worthwhile?"&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
When should you consider a developer-led sales strategy instead of a
tried-and-true traditional sales motion?
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/19-solving-problem.jpg" width="100%" class="shot rnd outl" alt="Solving a worthwhile developer problem."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
First, you need to be solving an actual problem that developers recognize
is an issue for them that they themselves would not want to solve. Solving
a meaty technical problem with an easy-to-use solution is
a high bar that non-technical founders often take for granted when 
pitching their product. 
&lt;/p&gt;&lt;p&gt;
Is the problem your product solves... actually a problem worth solving
for developers? It must be if you want to be successful with a developer-led
sales model.
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/20-explain-devs.jpg" width="100%" class="shot rnd outl" alt="How to explain your product to developers."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
Second, you need to be able to properly
&lt;a href="/blog/explain-products-developers.html"&gt;articulate the problem that you solve&lt;/a&gt;
to developers. That is an unexpectedly large challenge if no one on
the founding team of your tech startup is a developer themselves.
&lt;/p&gt;&lt;p&gt;
That link contains the slides and a loose transcript to another talk
that provides some guidance around telling your product's story
to developers.
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/21-cac-ltv.jpg" width="100%" class="shot rnd outl" alt="At an early stage you may not know your CAC and LTV but you should have a hypothesis for what they could be."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
Third, even at an early stage you should have some understanding of 
your own business model. The business model should include a working 
hypothesis for both the cost of customer acquisition (CAC) and lifetime 
value of a customer (LTV). Also, does the revenue derived from your
customers match a standard distribution or a power law distribution?
&lt;/p&gt;&lt;p&gt;
What do CAC, LTV and standard vs. power law distribution have to do with
a developer-led sales motion?
&lt;/p&gt;&lt;p&gt;
Ideally, your CAC will be significantly lower with a developer-led
sales strategy versus a traditional sales motion. This is one reason
established companies such as Okta 
&lt;a href="https://www.okta.com/press-room/press-releases/okta-welcomes-stormpath-to-accelerate-identity-for-developers/"&gt;acquire smaller firms such as Stormpath&lt;/a&gt; 
to create a different sales cycle based on usage from developers instead
of trying to convince companies to sign large deals before the software
has proven useful.
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/22-cloud-laws.jpg" width="100%" class="shot rnd outl" alt="Bessemer's 10 Laws of Cloud Computing and developer platform laws are fantastic foundational reading."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
Note that I am making a lot of assumptions about your understanding of
developer platforms. If what I am saying about CAC, LTV, usage-based
models and related terms are unclear, you definitely need to read 
Bessemer's articles on
&lt;a href="https://www.bvp.com/atlas/10-laws-of-cloud"&gt;10 Laws of Cloud Computing&lt;/a&gt; 
and 
&lt;a href="https://www.bvp.com/atlas/eight-laws-for-developer-platforms"&gt;8 Laws for Developer Platforms&lt;/a&gt;.
&lt;/p&gt;&lt;p&gt;
They are both incredibly helpful foundational resources for understanding 
how to build, market and sell software products for developers.
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/23-platform.jpg" width="100%" class="shot rnd outl" alt="Broad developer platform."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
Finally, if you have a product that solves a real problem for developers,
can explain it to them and have a working model of your CAC and LTV,
then a massive bonus comes into play when you have a broad platform
useful across many industries. Broad platforms that developers can use
in creative ways can potentially create new use cases for company to
sell.
&lt;/p&gt;&lt;p&gt;
For example, when Twilio started the SMS API, 2-factor authentication was
rare. The ease of integrating the API to send unique codes to pre-qualified
phone numbers to enhance security beyond a password became a new use
case for the company to sell to customers. Developers came up
with the use case and deserve the credit for figuring out what was
valuable about an SMS API.
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/24-tactics.jpg" width="100%" class="shot rnd outl" alt="Subsection for dev-led sales and marketing tactics."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
Most startup founders I talk to go straight into questions about the
tactics that Twilio and other companies use for selling to developers.
It's a bit of a "the cart before the horse" situation because you really
need to have figured out what we talked about in the previous section
as it applies to your business before you dig into the specific tactics
you will use to engage developers.
&lt;/p&gt;&lt;p&gt;
Let's assume you have determined a developer-led sales motion fits well 
with your company strategy. Now we can dig into the myriad tactics you 
can apply to grow your sales funnel. 
&lt;/p&gt;&lt;p&gt;
Most of these tactics are better described as "developer marketing" 
rather than sales. You are not going to be cold calling developers to
pitch them your service. Please make sure you never do that.
&lt;/p&gt;&lt;p&gt;
Let's dig into several of the specific tactics that can work well to
jump start developers adopting your product when it solves a problem
that they have.
&lt;/p&gt;&lt;p&gt;
We'll start with a few online tactics then review in-person "offline"
event-related activities like startup founders' dream hackathons.
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/25-docs.jpg" width="100%" class="shot rnd outl" alt="Great developers documentation is your best friend."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
By far your most important marketing asset is having stellar developer
documentation. However, documentation rarely comes up in conversations
as part of marketing and sales efforts. Why is that?
&lt;/p&gt;&lt;p&gt;
Historically, documentation has been a by-product of an actual product
or service being offered. Docs are considered a necessary evil and cost
center that needs to be minimized as a time suck for engineers to work
on "real" problems. Sometimes writers with minimal or no development
experience are hired to just get it done.
&lt;p&gt;&lt;/p&gt;
The "necessary evil" attitude makes sense in a world where documentation
lives in a large paper binder that is mailed out after a purchase is
complete. The quality has to be just enough that the customer doesn't
complain about the complexity being so high their developers cannot
eventually (after some long unpaid additional work nights and weekends) 
complete an implementation.
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/26-content.jpg" width="100%" class="shot rnd outl" alt="Timely tutorials hit topics that developers care about right now."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
Documentation is your evergreen always-to-date content, but there are
additional formats such as blog post walkthroughs, example use cases and 
brand awareness tutorials related to hot topics that developers are
currently interested in learning.
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/27-online-social.jpg" width="100%" class="shot rnd outl" alt="Online social strategies."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
Alright, I am going to start talking about online social with the 
caveat that I personally think it's difficult if not impossible to
pull off as a decent developer adoption tactic. It's usually done
so poorly that it is better to not do social channels like Twitter,
Facebook and your own Slack instance at all.
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/28-async-video.jpg" width="100%" class="shot rnd outl" alt="Asynchronously-consumed video tutorials."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
Video tutorials that teach how to use your product which you can post 
on YouTube and other sites are more difficult to create than straight
text and code, but they can be well worth the effort. YouTube is
currently the second largest search engine in the world (amazing how
both number 1 and number 2 are the same company) and the video
search results are also cross-posted in Google standard search. 
&lt;/p&gt;&lt;p&gt;
The wide reach presents a strong acquisition model if you can create
useful technical content.
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/29-streaming.jpg" width="100%" class="shot rnd outl" alt="Synchronous broadcasting and consuming video via streaming."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
Live coding on Twitch and YouTube are a nascent but promising teaching 
method.
&lt;/p&gt;&lt;p&gt;
Evergreen documentation, timely technical content, online social, 
asynchronous videos and streaming are the most common &lt;em&gt;online&lt;/em&gt; 
developer adoption tactics. Next we'll look at some offline, in-person
tactics.
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/30-conferences.jpg" width="100%" class="shot rnd outl" alt="Developers at tech conferences."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
Conferences split into a couple of categories: community-run and
vendor-run. For example, 
&lt;a href="https://developer.apple.com/wwdc/"&gt;WWDC&lt;/a&gt; is Apple's vendor-run
developer conference. They run the show and control the messaging.
&lt;a href="https://us.pycon.org/"&gt;PyCon US&lt;/a&gt; is the community-run 
Python developer conference. There are a ton of vendors there as sponsors
but no one company controls what happens at the conference.
&lt;/p&gt;&lt;p&gt;
Conferences, especially community-run ones, work well for brand awareness.
Vendor-run conferences can work well for brand awareness if your product
augments an existing company's products and solves a problem for the
developers who attend that event.
&lt;/p&gt;&lt;p&gt;
Non-developer conferences are great for sales lead generation. Think
of badge scanning and prize raffles in exchange for hearing a product
pitch. However, badge scanning and lead generation activities do not
work very well because most developers are skeptical and just want to
avoid overly pushy situations. Be considerate of your audience and
respectful.
&lt;/p&gt;&lt;p&gt;
Generally, I find you can spend a ton of money on conference sponsorships
without getting a ton of value out of them. If you want to be successful
with this tactic you should figure out your secret developer marketing
sauce on smaller, regional conferences then expand from there.
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/31-meetups.jpg" width="100%" class="shot rnd outl" alt="Developers at meetups."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
Tech meetups are good for brand awareness among primarily less experienced
developers. While there are some niche, senior developer and tech 
lead-focused meetups, most of the content at these events is aimed at
less experienced folks.
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/32-hackathons.jpg" width="100%" class="shot rnd outl" alt="Developers at hackathons."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
Professional hackathons have undergone a transition over the past several
years. Broadly speaking there is less excitement among developers to spend
a long sleep-deprived weekend working on a project with an unclear payoff.
I could be projecting my own feelings on this one but it appears that the
majority of developers in 2019 would prefer to work on their projects outside
the pressure of an arbitrary forced deadline. Hackathons can be still useful 
for getting a focused product shipped in a weekend but there seems to be less
excitement around them than a few years ago.
&lt;/p&gt;&lt;p&gt;
College hacakthons, like Hack MIT, MHacks and Hoo Hacks, on the other hand,
are going stronger than ever. There are always new classes of students who
are excited to code their ideas. However, you are playing the long game with
college hackathons and assuming that a few years down the road a student
who learned your APIs will pick it back up to solve a problem at work. For
most early startups, college hackathons won't make a ton of sense unless
you want to get quick immediate feedback from junior programmers about your
product's developer experience.
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/33-magic.jpg" width="100%" class="shot rnd outl" alt="Magical new approaches to developer relations."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
That covers the most common online and offline developer marketing and
sales tactics. Is that it?
&lt;/p&gt;&lt;p&gt;
No, there are many other tactics. However, the ones we just listed
are the most likely to be successful based on the relatively small 
investment you can make as a startup.
&lt;p&gt;&lt;/p&gt;
There are an additional set of tactics that are essentially "magic" to 
you because they rely on an existing successful foundation to properly 
execute. Building a new foundation of developer awareness is dramatically 
different from expanding developer adoption among those are not early
adoptors as well as doing a better job of serving developers who already 
use your service.
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/34-recap.jpg" width="100%" class="shot rnd outl" alt="Subsection title for recap."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
Let's wrap with a quick review of the important concepts presented in
this talk.
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/35-recap.jpg" width="100%" class="shot rnd outl" alt="Recap core ideas of talk."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
There is a startup dream of developer-led sales which is an easy path 
from launching your service to developers, having them find out and
start to use it, then becoming large paying customers.
&lt;/p&gt;&lt;p&gt;
The reality is clearly more difficult than the dream. You as the
startup founder need to be the "chief developer evangelism officer" 
and it will be expensive when you aim to hire a seasoned developer
relations employee.
&lt;/p&gt;&lt;p&gt;
We define developer-led sales as a different sales motion than a
traditional sales outreach approach. Instead, you appropriately 
market to developers who need a solution to a problem they are
facing, typically with great documentation, timely tutorials or
another respectful tactic rather than cold calling or empty
clickbait content.
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/190626-dev-led-sales/36-q-a.jpg" width="100%" class="shot rnd outl" alt="Q&amp;A slide."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
That's all the prepared materials for today. Thank you very much. Again, 
my name is &lt;a href="/about-author.html"&gt;Matt Makai&lt;/a&gt;, I am a software 
developer at &lt;a href="/twilio.html"&gt;Twilio&lt;/a&gt; on the Developer Network and 
the creator of 
&lt;a href="https://www.fullstackpython.com/"&gt;Full Stack Python&lt;/a&gt;
(&lt;a href="https://github.com/mattmakai/fullstackpython.com"&gt;source code&lt;/a&gt;).
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Thu, 11 Jul 2019 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2019-06-26:blog/developer-led-sales-startups.html</guid></item><item><title>Introduction to Ansible video course released!</title><link>https://www.fullstackpython.com/blog/introduction-ansible-videos-released.html</link><description>&lt;p&gt;Check out the just-launched video course, 
&lt;a href="https://training.talkpython.fm/courses/explore_ansible/introduction-to-ansible-with-python"&gt;Introduction to Ansible&lt;/a&gt;
on
&lt;a href="https://training.talkpython.fm/"&gt;Talk Python Training&lt;/a&gt;. This is the
perfect course for you if you want to
learn to configure servers and deploy web apps with the 
&lt;a href="https://github.com/ansible/ansible"&gt;Ansible configuration management tool&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://training.talkpython.fm/courses/explore_ansible/introduction-to-ansible-with-python"&gt;&lt;img src="/img/logos/intro-to-ansible.jpg" class="shot outl rnd" width="100%"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;My approach in this course is in less than 3 hours to teach you the core
concepts then get a ton of hands-on time creating Ansible Playbooks and
learning modules for practical applications. I also show you the errors
I frequently run into when using Ansible and how to fix them rather than
only showing the happy path.&lt;/p&gt;
&lt;p&gt;Now that this course has been published I'll be turning my attention back
to the Full Stack Python Guide to Deployments book update that uses
the latest version of Ansible, Python 3 and Ubuntu 18.04 LTS. More news
about the update coming as soon as possible. In addition, the Ansible
course pairs very well with the deployments book as they use the same
tools but give a different angle on how to learn and use them.&lt;/p&gt;
&lt;p&gt;Got questions or comments about 
&lt;a href="https://www.fullstackpython.com/"&gt;Full Stack Python&lt;/a&gt;? Send me an email or 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/issues"&gt;submit an issue ticket on GitHub&lt;/a&gt; 
to let me know how to improve the site as I continue to fill in the 
&lt;a href="https://www.fullstackpython.com/table-of-contents.html"&gt;table of contents&lt;/a&gt; 
with &lt;a href="https://www.fullstackpython.com/change-log.html"&gt;new pages&lt;/a&gt; and 
&lt;a href="https://www.fullstackpython.com/blog.html"&gt;new tutorials&lt;/a&gt;.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Sun, 13 Jan 2019 00:00:00 -0500</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2019-01-13:blog/introduction-ansible-videos-released.html</guid></item><item><title>Adding Okta Authentication to an Existing Flask Web App</title><link>https://www.fullstackpython.com/blog/okta-user-auth-existing-flask-web-app.html</link><description>&lt;p&gt;It can be a lot of work to piece together a full authentication system
if you have an existing &lt;a href="/flask.html"&gt;Flask&lt;/a&gt; web application that you are
coding. &lt;a href="https://developer.okta.com/signup/"&gt;Okta&lt;/a&gt; makes it much easier
to drop-in a complete user authentication system without a lot of
additional effort. In this tutorial we will take the 
&lt;a href="https://github.com/fullstackpython/flask-git-dashboard"&gt;Flask Git Dashboard&lt;/a&gt;
project as an example and add Okta to it.&lt;/p&gt;
&lt;h2&gt;Libraries&lt;/h2&gt;
&lt;p&gt;&lt;a href="/python-2-or-3.html"&gt;Python 3&lt;/a&gt; is required for this tutorial and we will 
also use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/flask.html"&gt;Flask&lt;/a&gt; web framework &lt;a href="https://pypi.org/project/Flask/1.0.2/"&gt;version 1.0.2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flask-oidc.readthedocs.io/en/latest/"&gt;Flask-OIDC&lt;/a&gt; where
  OIDC stands for "OpenID Connect". It provides support to use OpenID 
  Connect in Flask applications.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pypi.org/project/okta/"&gt;Okta Python helper library&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A free &lt;a href="https://developer.okta.com"&gt;Okta developer account&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of the finished code in this blog post is provided as open source 
under the MIT license on GitHub under the 
&lt;a href="https://github.com/fullstackpython/blog-code-examples"&gt;auth-existing-flask-app/finished directory of the blog-code-examples&lt;/a&gt; 
repository. Use and abuse the source code for your own applications.&lt;/p&gt;
&lt;h2&gt;Installing Dependencies&lt;/h2&gt;
&lt;p&gt;We will start out with an existing Flask web application. If you do not
have your own that you are modifying, clone this Git repository:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;git clone git@github.com:fullstackpython/blog-code-examples.git
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Next, create a new Python virtualenv for this project:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python3 -m venv flaskauth
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Activate the virtual environment with the &lt;code&gt;activate&lt;/code&gt; script:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;. ./flaskauth/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The command prompt should change after activation:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181031-okta-exist-flask/activate-virtualenv.jpg" width="100%" class="shot rnd outl" alt="Activating the flaskauth virtualenv."&gt;&lt;/p&gt;
&lt;p&gt;Remember that you will have to activate the virtualenv in every terminal 
window where you want to use the dependencies contained in this virtualenv.&lt;/p&gt;
&lt;p&gt;Change into the project directory within the &lt;code&gt;block-code-examples&lt;/code&gt; Git
repository that you cloned.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; blog-code-examples/auth-existing-flask-app/start/
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now we can install the dependencies for the existing project.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install -r requirements.txt
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Look for output similar to the following to confirm that the dependencies
successfully installed:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;amqp&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;2.1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;kombu&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Celery&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mf"&gt;4.1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;-&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="n"&gt;requirements&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;Downloading&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;7&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;cf&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="n"&gt;d4611fc67babd4ae250c9e8249c5650ae1933395488e9e7e3562b4ff24&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;amqp&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="n"&gt;kB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;████████████████████████████████&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;51&lt;/span&gt;&lt;span class="n"&gt;kB&lt;/span&gt; &lt;span class="mf"&gt;10.7&lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; 
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;six&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;dateutil&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;alembic&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Migrate&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mf"&gt;2.2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;-&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="n"&gt;requirements&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;67&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;141&lt;/span&gt;&lt;span class="n"&gt;a581104b1f6397bfa78ac9d43d8ad29a7ca43ea90a2d863fe3056e86a&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;six&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.11&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;vine&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;amqp&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;2.1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;kombu&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Celery&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mf"&gt;4.1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;-&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="n"&gt;requirements&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;Downloading&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="n"&gt;b1ebe42843c19f35edb15022ecae339fbec6db5b241a7a13c924dabf2a3&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;vine&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;span class="n"&gt;Installing&lt;/span&gt; &lt;span class="n"&gt;collected&lt;/span&gt; &lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;itsdangerous&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Werkzeug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MarkupSafe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Jinja2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SQLAlchemy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;SQLAlchemy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Mako&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;six&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;dateutil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alembic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Migrate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;billiard&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pytz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amqp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kombu&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Celery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WTForms&lt;/span&gt;
  &lt;span class="n"&gt;Running&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;MarkupSafe&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt;
  &lt;span class="n"&gt;Running&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;SQLAlchemy&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt;
  &lt;span class="n"&gt;Running&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;Mako&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt;
  &lt;span class="n"&gt;Running&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;editor&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt;
  &lt;span class="n"&gt;Running&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;alembic&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt;
  &lt;span class="n"&gt;Running&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;billiard&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt;
  &lt;span class="n"&gt;Running&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;WTForms&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt;
&lt;span class="n"&gt;Successfully&lt;/span&gt; &lt;span class="n"&gt;installed&lt;/span&gt; &lt;span class="n"&gt;Celery&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;4.1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Migrate&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;SQLAlchemy&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="n"&gt;Jinja2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.10&lt;/span&gt; &lt;span class="n"&gt;Mako&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="n"&gt;MarkupSafe&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="n"&gt;SQLAlchemy&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="n"&gt;WTForms&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.1&lt;/span&gt; &lt;span class="n"&gt;Werkzeug&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.14&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;alembic&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;amqp&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="n"&gt;billiard&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.5&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;0.4&lt;/span&gt; &lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;7.0&lt;/span&gt; &lt;span class="n"&gt;itsdangerous&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;kombu&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;4.2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;dateutil&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.7&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;editor&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="n"&gt;pytz&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2018.7&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.10&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="n"&gt;six&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.11&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;vine&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We need a couple of additional dependencies for our project to
work, &lt;code&gt;flask-oidc&lt;/code&gt; and &lt;code&gt;okta&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install flask-oidc&amp;gt;=1.4.0 okta==0.0.4
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The dependencies are now properly installed into our virtual environment.
Let's test out the application to see if we can get it running properly.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="n"&gt;FLASK_APP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;flaskdash&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="n"&gt;FLASK_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;development&lt;/span&gt;
&lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We should see the application start up with some default development time
values:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt; * Serving Flask app &lt;span class="s2"&gt;&amp;quot;flaskdash.py&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;lazy loading&lt;span class="o"&gt;)&lt;/span&gt;
 * Environment: development
 * Debug mode: on
 * Running on http://127.0.0.1:5000/ &lt;span class="o"&gt;(&lt;/span&gt;Press CTRL+C to quit&lt;span class="o"&gt;)&lt;/span&gt;
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: &lt;span class="m"&gt;203&lt;/span&gt;-814-092
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Head to localhost:5000 in your web browser and we should see a 
work-in-progress dashboard:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181031-okta-exist-flask/flask-dashboard.jpg" width="100%" class="shot rnd outl" alt="Dashboard provided by existing Flask application."&gt;&lt;/p&gt;
&lt;p&gt;It's time to get to setting up an Okta developer account so we can get the
appropriate configuration information for our application.&lt;/p&gt;
&lt;h2&gt;Okta for Authentication&lt;/h2&gt;
&lt;p&gt;Head to the &lt;a href="https://developer.okta.com/signup"&gt;Okta developers sign up page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181031-okta-exist-flask/okta-sign-up.jpg" width="100%" class="shot rnd outl" alt="Okta developers landing page for signing up."&gt;&lt;/p&gt;
&lt;p&gt;Sign up for a new account or log into your existing account.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181031-okta-exist-flask/okta-dev.jpg" width="100%" class="shot rnd outl" alt="Okta developer sign up flow."&gt;&lt;/p&gt;
&lt;p&gt;The interesting bit about the Okta developer sign up flow is that now you 
should check your email to finish creating your account. Look for an email 
like this one:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181031-okta-exist-flask/okta-email.jpg" width="100%" class="shot rnd outl" alt="Okta sign up email."&gt;&lt;/p&gt;
&lt;p&gt;Click the "Sign In" button and log into developer account using 
the temporary password found in the email. Set a new password and challenge
question. Then pick an image to match your account login process.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181031-okta-exist-flask/okta-create-account.png" width="100%" class="shot rnd outl" alt="Okta finish creating an account."&gt;&lt;/p&gt;
&lt;p&gt;Click the "Create Account" button and you will be wisked away to the
Okta developer dashboard.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181031-okta-exist-flask/dev-dashboard.png" width="100%" class="shot rnd outl" alt="Okta developer dashboard."&gt;&lt;/p&gt;
&lt;p&gt;Find the "Org URL" as shown in the following image.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181031-okta-exist-flask/okta-dev-dashboard-url.jpg" width="100%" class="shot rnd outl" alt="Okta Org URL value."&gt;&lt;/p&gt;
&lt;p&gt;We are going to use that URL in our secret credentials file so that
our Flask web app can properly connect to the Okta service.&lt;/p&gt;
&lt;p&gt;Create a new file in your project directory named 
&lt;code&gt;openidconnect_secrets.json&lt;/code&gt; with the following contents:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;quot;web&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;client_id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;{{ OKTA_CLIENT_ID }}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;client_secret&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;{{ OKTA_CLIENT_SECRET }}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;auth_uri&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;{{ OKTA_ORG_URL }}/oauth2/default/v1/authorize&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;token_uri&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;{{ OKTA_ORG_URL }}/oauth2/default/v1/token&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;issuer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;{{ OKTA_ORG_URL }}/oauth2/default&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;userinfo_uri&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;{{ OKTA_ORG_URL }}/oauth2/default/userinfo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;redirect_uris&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s2"&gt;&amp;quot;http://localhost:5000/oidc/callback&amp;quot;&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;/pre&gt;&lt;/div&gt;


&lt;p&gt;Replace the four &lt;code&gt;{{ OKTA_ORG_URL }}&lt;/code&gt; placeholders with the Org URL value
found in your dashboard. We will fill in the rest of the placeholders with 
actual values as we proceed through the tutorial. My 
&lt;code&gt;openidconnect_secret.json&lt;/code&gt; file would currently have the following
values based on my developer dashboard Org URL. 
&lt;strong&gt;Remember that your URL values will be different!&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;quot;web&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;client_id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;{{ OKTA_CLIENT_ID }}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;client_secret&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;{{ OKTA_CLIENT_SECRET }}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt;~~&lt;/span&gt;    &lt;span class="nt"&gt;&amp;quot;auth_uri&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://dev-860408.oktapreview.com/oauth2/default/v1/authorize&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt;~~&lt;/span&gt;    &lt;span class="nt"&gt;&amp;quot;token_uri&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://dev-860408.oktapreview.com/oauth2/default/v1/token&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt;~~&lt;/span&gt;    &lt;span class="nt"&gt;&amp;quot;issuer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://dev-860408.oktapreview.com/oauth2/default&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt;~~&lt;/span&gt;    &lt;span class="nt"&gt;&amp;quot;userinfo_uri&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://dev-860408.oktapreview.com/oauth2/default/userinfo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;redirect_uris&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s2"&gt;&amp;quot;http://localhost:5000/oidc/callback&amp;quot;&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;/pre&gt;&lt;/div&gt;


&lt;p&gt;Okay awesome, we have our Okta account set up so we can add the 
authentication code to our Flask application.&lt;/p&gt;
&lt;h2&gt;Updating the Flask App with Okta&lt;/h2&gt;
&lt;p&gt;We need to connect our Flask code to our new Okta account. The
recommended way of including variables such as account credentials
in a Flask application is through
&lt;a href="http://flask.pocoo.org/docs/1.0/config/"&gt;configuration handling&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Update &lt;code&gt;config.py&lt;/code&gt; the Flask code with the following highlighted lines.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&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="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;SECRET_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SECRET_KEY&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;development key&amp;#39;&lt;/span&gt;

    &lt;span class="c1"&gt;# Redis&lt;/span&gt;
    &lt;span class="n"&gt;REDIS_SERVER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;REDIS_SERVER&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;localhost&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;REDIS_PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;REDIS_PORT&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;6379&lt;/span&gt;
    &lt;span class="n"&gt;REDIS_DB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;REDIS_DB&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;REDIS_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;redis://&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;REDIS_SERVER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;REDIS_PORT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Celery task queue&lt;/span&gt;
    &lt;span class="n"&gt;CELERY_BROKER_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;CELERY_BROKER_URL&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;REDIS_URL&lt;/span&gt;
    &lt;span class="n"&gt;CELERY_RESULT_BACKEND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;CELERY_RESULT_BACKEND&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;REDIS_URL&lt;/span&gt;

    &lt;span class="c1"&gt;# database settings&lt;/span&gt;
    &lt;span class="n"&gt;SQLALCHEMY_DATABASE_URI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;DATABASE_URL&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; \
      &lt;span class="s1"&gt;&amp;#39;sqlite:///&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;abspath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
      &lt;span class="s1"&gt;&amp;#39;flaskdash.db&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;SQLALCHEMY_TRACK_MODIFICATIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="n"&gt;OIDC_CLIENT_SECRETS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;openidconnect_secrets.json&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="n"&gt;OIDC_COOKIE_SECURE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="n"&gt;OIDC_CALLBACK_ROUTE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/oidc/callback&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="n"&gt;OIDC_SCOPES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;openid&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;email&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;profile&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="n"&gt;OIDC_ID_TOKEN_COOKIE_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;oidc_token&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We first add three import lines, one to pull values from environment
variables, and the next two imports to make it possible to use OpenID
Connect and Okta in our application.&lt;/p&gt;
&lt;p&gt;The rest of the new code sets Flask application configuration
values that can be used to instantiate the OpenID Connect and
Okta clients.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;OIDC_CLIENT_SECRETS&lt;/code&gt;: the location of the OpenID Connect secrets file&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OIDC_COOKIE_SECURE&lt;/code&gt;: allows development mode for testing user login and
  registration without SSL. Your application must set this to &lt;code&gt;True&lt;/code&gt; in a
  production application.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OIDC_CALLBACK_ROUTE&lt;/code&gt;: URL in the web app for handling user logins&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OIDC_SCOPES&lt;/code&gt;: what data to request about the user when they log in. Our
  application requests the basic email, name and profile information&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SECRET_KEY&lt;/code&gt;: this is a Flask setting to keep sessions secure. The key 
  must never be made public or your web application user sessions will be
  compromised. &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Where do we get those application configuration values though? We
need to obtain them from our Okta account so go back to the
dashboard to create a new OpenID Connect application.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181031-okta-exist-flask/select-applications.jpg" width="100%" class="shot rnd outl" alt="Select applications on the Okta developer dashboard."&gt;&lt;/p&gt;
&lt;p&gt;OpenID Connect applications use a client ID and client secret in
place of traditional usernames and passwords. The client ID and
client secret will tell your authorization server to recognize your 
application. Press the "Add Application" button.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181031-okta-exist-flask/add-application.jpg" width="100%" class="shot rnd outl" alt="Click the Add Application button."&gt;&lt;/p&gt;
&lt;p&gt;On the new application screen choose "Web" and then press "Next".&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181031-okta-exist-flask/web-application.jpg" width="100%" class="shot rnd outl" alt="Choose a web application."&gt;&lt;/p&gt;
&lt;p&gt;On the next page there are numerous configuration options but only a 
few values we need to fill in before we can get our credentials. Set
the following values to the &lt;code&gt;Name&lt;/code&gt;, &lt;code&gt;Base URIs&lt;/code&gt; and &lt;code&gt;Login redirect URIs&lt;/code&gt;
properties:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;FlaskApp&lt;/strong&gt; for &lt;code&gt;Name&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;http://localhost:5000&lt;/strong&gt; for &lt;code&gt;Base URIs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;http://localhost:5000/oidc/callback&lt;/strong&gt; for &lt;code&gt;Login redirect URIs&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="/img/181031-okta-exist-flask/set-app-configuration.jpg" width="100%" class="shot rnd outl" alt="Set application configuration values."&gt;&lt;/p&gt;
&lt;p&gt;Those are the three values you need to fill in for now so save the 
application to create it.&lt;/p&gt;
&lt;p&gt;On the next page scroll down to find your client and secret keys.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181031-okta-exist-flask/client-credentials.jpg" width="100%" class="shot rnd outl" alt="Save the client credentials for later use."&gt;&lt;/p&gt;
&lt;p&gt;Copy and paste the client ID and client secret into the following 
highlighted lines to replace the &lt;code&gt;{{ OKTA_CLIENT_ID }}&lt;/code&gt; and 
&lt;code&gt;{{ OKTA_CLIENT_SECRET }}&lt;/code&gt; placeholders.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;quot;web&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;~~&lt;/span&gt;    &lt;span class="nt"&gt;&amp;quot;client_id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;{{ OKTA_CLIENT_ID }}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt;~~&lt;/span&gt;    &lt;span class="nt"&gt;&amp;quot;client_secret&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;{{ OKTA_CLIENT_SECRET }}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;auth_uri&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://dev-860408.oktapreview.com/oauth2/default/v1/authorize&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;token_uri&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://dev-860408.oktapreview.com/oauth2/default/v1/token&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;issuer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://dev-860408.oktapreview.com/oauth2/default&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;userinfo_uri&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://dev-860408.oktapreview.com/oauth2/default/userinfo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;redirect_uris&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s2"&gt;&amp;quot;http://localhost:5000/oidc/callback&amp;quot;&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;/pre&gt;&lt;/div&gt;


&lt;p&gt;Save the file and make sure to keep it out of version control as those
secret values need to stay secret.&lt;/p&gt;
&lt;p&gt;We have one more step in the Okta developer dashboard before we upgrade 
our Flask application with the authentication code: creating an 
&lt;a href="https://developer.okta.com/use_cases/api_access_management/"&gt;API authentication token&lt;/a&gt;.
Go to the API tab.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181008-flask-okta/api-tab.jpg" width="100%" class="shot rnd outl" alt="Click the API tab in the dashboard."&gt;&lt;/p&gt;
&lt;p&gt;Click the "Create Token" button.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181008-flask-okta/create-token.png" width="100%" class="shot rnd outl" alt="Create an authentication token to access Okta."&gt;&lt;/p&gt;
&lt;p&gt;Name the token &lt;code&gt;FlaskToken&lt;/code&gt; and copy it. Save the token somewhere
safe as we will not be able to access it through the dashboard again. We
are going to use this token when setting the &lt;code&gt;OKTA_AUTH_TOKEN&lt;/code&gt; environment
variable in the next section of this tutorial.&lt;/p&gt;
&lt;p&gt;Okay, we finally have all the Okta service configuration and tokens in
our &lt;code&gt;openidconnect_secret.json&lt;/code&gt; file that we need to finish our application.&lt;/p&gt;
&lt;p&gt;Update &lt;code&gt;app/__init__.py&lt;/code&gt; with these highlighted lines:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;redis&lt;/span&gt;
&lt;span class="o"&gt;~~&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;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;app.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;make_celery&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="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask_sqlalchemy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SQLAlchemy&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask_migrate&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Migrate&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask_oidc&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenIDConnect&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;okta&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UsersClient&lt;/span&gt;


&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;static_url_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/static&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&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;from_object&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;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SQLAlchemy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;migrate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Migrate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 

&lt;span class="c1"&gt;# connect to Redis instance&lt;/span&gt;
&lt;span class="n"&gt;redis_db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StrictRedis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;app&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="s1"&gt;&amp;#39;REDIS_SERVER&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                             &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;app&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="s1"&gt;&amp;#39;REDIS_PORT&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                             &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;app&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="s1"&gt;&amp;#39;REDIS_DB&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;celery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;make_celery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="c1"&gt;# instantiate OpenID client to handle user session&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="n"&gt;oidc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpenIDConnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="c1"&gt;# Okta client will determine if a user has an appropriate account&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="n"&gt;okta_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UsersClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;OKTA_ORG_URL&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;                          &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;OKTA_AUTH_TOKEN&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;app&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;routes&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We can now access the &lt;code&gt;okta_client&lt;/code&gt; in our routes. Open &lt;code&gt;app/routes.py&lt;/code&gt;
and update the following lines:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;send_from_directory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;render_template&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;app&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;oidc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;okta_client&lt;/span&gt;


&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;before_request&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;before_request&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;oidc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_loggedin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;        &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&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;okta_client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oidc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_getfield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sub&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;        &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/js/&amp;lt;path:path&amp;gt;&amp;#39;&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;send_js&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&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;send_from_directory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;js&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/css/&amp;lt;path:path&amp;gt;&amp;#39;&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;send_css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&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;send_from_directory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;css&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&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;dashboard&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;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;dashboard.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/repositories&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="nd"&gt;@oidc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;require_login&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;repositories&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;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;repositories.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/login&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="nd"&gt;@oidc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;require_login&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.repositories&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/logout&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;logout&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="n"&gt;oidc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.landing_page&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The above new highlighted lines check whether or not a user is logged in
before each request. If a route requires a logged in user due to the 
&lt;code&gt;@oidc.require_login&lt;/code&gt; decorator then the user will be redirect to the
sign in page. We also added routes under &lt;code&gt;/login&lt;/code&gt; and &lt;code&gt;/logout&lt;/code&gt; to make
it possible to log in and out of the application.&lt;/p&gt;
&lt;p&gt;Set three environment variables so our application can use them when we
run it. Make sure the placeholders &lt;code&gt;ORG_URL&lt;/code&gt; and &lt;code&gt;AUTH_TOKEN&lt;/code&gt; are set with 
your actual Org URL value and auth token from the Okta developer dashboard.&lt;/p&gt;
&lt;p&gt;On the command line run the following commands, making sure to replace
any placeholder values with your own tokens and URLs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# this tells Flask we want to run the built-in server in dev mode&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="n"&gt;FLASK_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;development&lt;/span&gt;
&lt;span class="c1"&gt;# make sure to use a very long random string here that cannot be guessed&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="n"&gt;SECRET_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a very long string with lots of numbers and letters&amp;#39;&lt;/span&gt;
&lt;span class="c1"&gt;# this is the same Org URL found on your developer dashboard&lt;/span&gt;
&lt;span class="c1"&gt;# for example, https://dev-860408.oktapreview.com&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="n"&gt;OKTA_ORG_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ORG_URL&amp;#39;&lt;/span&gt;
&lt;span class="c1"&gt;# this is the API authentication token we created&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="n"&gt;OKTA_AUTH_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;AUTH_TOKEN&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now re-run the Flask application:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;set FLASK_APP=app.py
flask run
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;You should be in good shape if the development server starts up with output
like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(flaskauth)$ flask run
 * Environment: development
 * Debug mode: on
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 415-920-546
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Head to localhost:5000 in a browser where you are not already logged into
your Okta account (an incognito window of your web browser works great).&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181031-okta-exist-flask/dashboard-incognito.jpg" width="100%" class="shot rnd outl" alt="Dashboard while in incognito mode."&gt;&lt;/p&gt;
&lt;p&gt;Let's test the redirect functionality when we try to go to the &lt;code&gt;/dashboard&lt;/code&gt;
route by going to localhost:5000/repositories. We get redirected to the Okta
login page.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181031-okta-exist-flask/okta-redirect.jpg" width="100%" class="shot rnd outl" alt="Getting redirected while in incognito mode."&gt;&lt;/p&gt;
&lt;p&gt;Enter your Okta developer username and password to log into your application.
For development purposes this will work fine for testing but obviously in a
production application you will create other accounts for users to log into.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181031-okta-exist-flask/repositories-enter.jpg" width="100%" class="shot rnd outl" alt="Got into the repositories page after logging in."&gt;&lt;/p&gt;
&lt;p&gt;To unauthenticate your user go to localhost:5000/logout. When you go back 
to localhost:5000/repositories again you will now have to re-authenticate. &lt;/p&gt;
&lt;h2&gt;What Now?&lt;/h2&gt;
&lt;p&gt;We configured an existing &lt;a href="/flask.html"&gt;Flask&lt;/a&gt; application to use Okta for
user authentication and identity management via the
&lt;a href="https://developer.okta.com/use_cases/api_access_management/"&gt;Okta API&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Next you can try one of the following tutorials to add other features to 
the Flask application:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/blog/hosted-monitoring-flask-web-apps.html"&gt;How to Add Hosted Monitoring to Flask Web Applications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/blog/develop-flask-web-apps-docker-containers-macos.html"&gt;Develop and Run Flask Apps within Docker Containers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/blog/respond-sms-text-messages-python-flask.html"&gt;Responding to SMS Text Messages with Python &amp;amp; Flask&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can also determine what to code next in your Python project by reading 
the &lt;a href="/table-of-contents.html"&gt;Full Stack Python table of contents page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Questions? Contact me via Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;. I am also on GitHub with
the username &lt;a href="https://github.com/mattmakai"&gt;mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Something wrong with this post? Fork 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/181031-auth-existing-flask-app.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Fri, 02 Nov 2018 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2018-10-31:blog/okta-user-auth-existing-flask-web-app.html</guid></item><item><title>Fresh Tutorials on Full Stack Python</title><link>https://www.fullstackpython.com/blog/fresh-tutorials-october-2018.html</link><description>&lt;p&gt;There are a bunch of 
&lt;a href="https://www.fullstackpython.com/blog.html"&gt;new tutorials&lt;/a&gt;
on &lt;a href="https://www.fullstackpython.com/"&gt;Full Stack Python&lt;/a&gt; that were written
since the last time I sent out an email newsletter. These range from getting 
started with some popular open source projects to integrating third party 
APIs to build authentication into Flask applications:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.fullstackpython.com/blog/python-3-flask-gunicorn-ubuntu-1804-bionic-beaver.html"&gt;Configure Python 3, Flask and Gunicorn on Ubuntu 18.04 LTS&lt;/a&gt;
  shows you how to set up your Python and 
  &lt;a href="https://www.fullstackpython.com/flask.html"&gt;Flask&lt;/a&gt; 
  &lt;a href="https://www.fullstackpython.com/development-environments.html"&gt;development environment&lt;/a&gt;
  on the latest &lt;a href="https://www.fullstackpython.com/ubuntu.html"&gt;Ubuntu&lt;/a&gt;
  Long-Term Support (LTS) release.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.fullstackpython.com/blog/add-user-authentication-flask-apps-okta.html"&gt;How to Add User Authentication to Flask Apps with Okta&lt;/a&gt;
  covers using OpenID Connect and the Okta API in Flask applications
  to handle user authentication.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.fullstackpython.com/blog/provision-ubuntu-1804-linux-servers-digitalocean.html"&gt;How to Provision Ubuntu 18.04 LTS Linux Servers on DigitalOcean&lt;/a&gt;
  is a quick tutorial for developers who have not seen how easy it is
  to spin up a virtual private server on DigitalOcean for hosting
  their Python applications.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.fullstackpython.com/blog/first-steps-bottle-web-apps-docker-containers.html"&gt;Running Bottle Apps in Docker Containers on macOS&lt;/a&gt;
  provides just the basics to start using 
  &lt;a href="https://www.fullstackpython.com/docker.html"&gt;Docker&lt;/a&gt; on macOS
  to run an example Flask web app.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.fullstackpython.com/blog/explain-products-developers.html"&gt;How to Explain Your Products to Developers&lt;/a&gt;
  is based on a talk I gave to a group of technical founders and investors
  in Silicon Valley. It's a bit different from my usual step-by-step 
  tutorial in that it gives strong advice based on my experience rather
  than show how to use an open source project or integrate a third-party
  API.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Got questions or comments about 
&lt;a href="https://www.fullstackpython.com/"&gt;Full Stack Python&lt;/a&gt;? Send me an email or 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/issues"&gt;submit an issue ticket on GitHub&lt;/a&gt; 
to let me know how to improve the site as I continue to fill in the 
&lt;a href="https://www.fullstackpython.com/table-of-contents.html"&gt;table of contents&lt;/a&gt; 
with &lt;a href="https://www.fullstackpython.com/change-log.html"&gt;new pages&lt;/a&gt; and 
&lt;a href="https://www.fullstackpython.com/blog.html"&gt;new tutorials&lt;/a&gt;.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Mon, 22 Oct 2018 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2018-10-22:blog/fresh-tutorials-october-2018.html</guid></item><item><title>How to Provision Ubuntu 18.04 LTS Linux Servers on DigitalOcean</title><link>https://www.fullstackpython.com/blog/provision-ubuntu-1804-linux-servers-digitalocean.html</link><description>&lt;p&gt;&lt;a href="/web-development.html"&gt;Python web applications&lt;/a&gt; need to be 
&lt;a href="/deployment.html"&gt;deployed&lt;/a&gt; to a production &lt;a href="/servers.html"&gt;server&lt;/a&gt; or 
&lt;a href="/platform-as-a-service.html"&gt;service&lt;/a&gt; so your users have access to
the application.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://do.co/fullstackpython"&gt;DigitalOcean&lt;/a&gt; is one such service
that makes it easy to immediately get access to initially free servers 
which are low cost (~$5 per month depending on the resources) to continue 
using after the first few months.&lt;/p&gt;
&lt;p&gt;In this tutorial we'll learn how to quickly sign up and spin up an
&lt;a href="/ubuntu.html"&gt;Ubuntu&lt;/a&gt;-based Linux server that only you will have
access to based on a private SSH key.&lt;/p&gt;
&lt;h2&gt;Obtain Your Virtual Server&lt;/h2&gt;
&lt;p&gt;These steps sign you up for a DigitalOcean account and guide you through 
provisioning a virtual private server called a "Droplet" for $5/month which 
we configure throughout the rest of the book.&lt;/p&gt;
&lt;p&gt;Point your web browser to 
&lt;a href="https://do.co/fullstackpython"&gt;Digitalocean.com's registration page&lt;/a&gt;.
Note that this link uses a referral code which gives you $100 in free
credit. Feel free to just go to 
&lt;a href="https://www.digitalocean.com/"&gt;digitalocean.com&lt;/a&gt; if you 
do not want to use the referral link (you will not get the $100 in credit 
though). Their landing page will look something like the following image.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181014-digitalocean-ubuntu/do-landing-page.jpg" class="shot rnd outl" alt="DigitalOcean landing page." width="100%"&gt;&lt;/p&gt;
&lt;p&gt;Register for a new DigitalOcean account. Fill out the appropriate 
information. When your account is registered and active you can create 
a new DigitalOcean server, which they call "droplets".&lt;/p&gt;
&lt;p&gt;After you finish the registration process you will be able to start
creating DigitalOcean servers. Select the "Create" button which 
opens a drop-down menu. Choose "Droplets" to go to the "Create Droplets"
page.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181014-digitalocean-ubuntu/create-droplet.png" class="shot rnd outl" width="100%"&gt;&lt;/p&gt;
&lt;p&gt;The new droplet configuration screen will appear and look like 
the following image. The default Ubuntu instance is 16.04, but
we will use the newer LTS release 18.04 in this book.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181014-digitalocean-ubuntu/create-droplets-page.jpg" class="shot" width="100%"&gt;&lt;/p&gt;
&lt;p&gt;Select the 1 GB memory-sized server for $5 per month. This instance
size should be perfect for prototypes, side projects and minimum
viable products. Feel free to choose a larger instance size if you
want more memory and resources for running your application.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181014-digitalocean-ubuntu/create-droplet-size.jpg" class="shot" width="100%"&gt;&lt;/p&gt;
&lt;p&gt;Scroll down and choose the data center region where you want your
instance to be located. I typically choose New York because I am
on the East Coast of the United Statest in Washington, D.C., and you will 
want the server to be closest to your users' location.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181014-digitalocean-ubuntu/choose-region.png" class="shot" alt="Choose the data center region closest to your customers." width="100%"&gt;&lt;/p&gt;
&lt;p&gt;Next, scroll down and click "New SSH Key". Copy and paste in the contents 
of your &lt;strong&gt;public&lt;/strong&gt; SSH key. If you do not yet have an SSH key here are a 
couple of guides that will walk you through creating one:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/blog/ssh-keys-macos-sierra.html"&gt;Creating SSH keys on macOS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/blog/ssh-keys-ubuntu-linux.html"&gt;Creating SSH keys on Ubuntu Linux&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can see the contents of a public key using the &lt;code&gt;cat&lt;/code&gt; command. For 
example on my system the command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;cat root.pub
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Outputs the contents of my public key:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCqAY/Le17HZpa4+eSoh2L9FMYaQ7EnLOGkYbcbsiQNpnF4FTAemF7tbvMvjpVLU6P9AVGs6qEeJdgTE2gH8fq881AUsQ8it1gla2oAlc+vOZmqWPYaLIl5g9DkGwvbITXayobDcw9wTN5tOITOxp3BV5jqanqoqDAPH1RGfT6A5vkJFsmu4w7cPsn9tiqfZZdge3WkpMNT1M3ou+ogrAwE6Ra531s3zYVG9y1688BGdYzbQFfU0+Pou6Z43Do6xbh2hAfQ5hUuTG0OrE3b/yhGcxEWz0Y9+wPGmxm3/0ioTfMWUG3LOQn+oMtKX/PXX/qOJuUjszbqYBvSYS3kv2IVFGV2KEIKC1xgUDfw+HOV4HlIosIbc97zY83m0Ft+tFavPaiQYrar3wCsVfRUltSR4EwNnLmvNYeMVSS8jSP2ZSPwbL8GO7xxAAS9Oy12set1f4OxdPhEUB9rEfAssU1mE6J5eq+Drs8KX04OasLSLt7kP7wWA27I9pU/y9NRHxEsO0YbLG7DzfHGl4QVXwDjIA5GpwjQMwZLt+lyGc4hpnuXg+IUR6MXI90Hh64ch32nSC8j/hjnWCWgj8Cyuc4Rd/2OtO5dHpbjSyU5Yza2lzIqFbFRo7aQNaIkBIioJnc1d6mrg9mLxfd5Ef2ez9bUjqcq4K7uH/JAm0H2Vk1VFQ&lt;span class="o"&gt;==&lt;/span&gt; matthew.makai@gmail.com
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Copy and paste this key into the DigitalOcean modal window and give it 
a memorable name for future reference:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181014-digitalocean-ubuntu/root-key.png" class="shot rnd outl" alt="Paste in the public root key into the modal window." width="100%"&gt;&lt;/p&gt;
&lt;p&gt;Optionally, give your server a nickname such as &lt;code&gt;flask-deploy-manual&lt;/code&gt;.
Then click the big green "Create" button at the bottom of the screen.&lt;/p&gt;
&lt;p&gt;The server provisioning process will begin and our Ubuntu Linux 18.04 
LTS-powered will soon be ready to go. &lt;/p&gt;
&lt;p&gt;Ubuntu 18.04 is the current Long Term Support (LTS) release and has a 
5 year support lifecycle. This version will receive security updates until 
April 2023 as shown on the 
&lt;a href="https://www.ubuntu.com/info/release-end-of-life"&gt;Ubuntu release end-of-life&lt;/a&gt;
page.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181014-digitalocean-ubuntu/ubuntu-lts-releases.png" class="shot rnd outl" alt="Ubuntu end of life schedule for all releases." width="100%"&gt;&lt;/p&gt;
&lt;p&gt;You should now be back on the DigitalOcean dashboard.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181014-digitalocean-ubuntu/ready-to-deploy.png" class="shot rnd outl" alt="New Ubuntu server ready for access." width="100%"&gt;&lt;/p&gt;
&lt;p&gt;Our server is now up and ready for SSH access.&lt;/p&gt;
&lt;p&gt;Connect to the server using the IP address associated with it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# make sure to replace 192.168.1.1 with your server&amp;#39;s IP address
# and the &amp;quot;private_key&amp;quot; name with the name of your private key
ssh -i ./private_key 192.168.1.1
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;You should now be connected to your new server and can proceed
with development or deployment.&lt;/p&gt;
&lt;h2&gt;What's Next?&lt;/h2&gt;
&lt;p&gt;We just stood up a new virtual private server on DigitalOcean that can be
used as a production or development environment.&lt;/p&gt;
&lt;p&gt;Next up I recommend either configuring the development environment or
deploying your application with one of the following tutorials:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/blog/python-3-flask-gunicorn-ubuntu-1804-bionic-beaver.html"&gt;Configure Python 3, Flask and Gunicorn on Ubuntu 18.04 LTS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/blog/make-phone-calls-python.html"&gt;How to Make Phone Calls in Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=vGphzPLemZE"&gt;5 ways to deploy your Python web app from PyCon US 2017&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can also figure out what to code next in your Python project by reading 
the &lt;a href="/table-of-contents.html"&gt;Full Stack Python table of contents page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Questions? Contact me via Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;. I'm also on GitHub with
the username &lt;a href="https://github.com/mattmakai"&gt;mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Something wrong with this post? Fork 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/181014-digitalocean-ubuntu-1804.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Sun, 14 Oct 2018 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2018-10-14:blog/provision-ubuntu-1804-linux-servers-digitalocean.html</guid></item><item><title>How to Add User Authentication to Flask Apps with Okta</title><link>https://www.fullstackpython.com/blog/add-user-authentication-flask-apps-okta.html</link><description>&lt;p&gt;User authentication is a basic feature in 
&lt;a href="/web-development.html"&gt;web applications&lt;/a&gt; so people can create and access 
their own accounts. Unfortunately, authentication is not always easy to
set up and there are many ways to incorrectly implement login and logout
features. &lt;/p&gt;
&lt;p&gt;This tutorial walks through how to use the 
&lt;a href="https://developer.okta.com/use_cases/authentication/"&gt;secure identity authentication service&lt;/a&gt;
called &lt;a href="https://developer.okta.com/"&gt;Okta&lt;/a&gt;, which is free for up to 1,000
active user accounts, to easily handle user data in &lt;a href="/flask.html"&gt;Flask&lt;/a&gt; 
applications.&lt;/p&gt;
&lt;h2&gt;Our Tools&lt;/h2&gt;
&lt;p&gt;Python 3 is strongly recommended for building applications and this
tutorial was built with Python 3.7 although earlier versions of Python 3 
should also work fine. In addition to Python 3.x we will also use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/flask.html"&gt;Flask&lt;/a&gt; web framework &lt;a href="https://pypi.org/project/Flask/1.0.2/"&gt;version 1.0.2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flask-oidc.readthedocs.io/en/latest/"&gt;Flask-OIDC&lt;/a&gt; where
  OIDC stands for "OpenID Connect". It provides support to use OpenID 
  Connect in Flask applications.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pypi.org/project/okta/"&gt;Okta Python helper library&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A free &lt;a href="https://developer.okta.com"&gt;Okta developer account&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of the code in this blog post is provided as open source under the 
MIT license on GitHub under the 
&lt;a href="https://github.com/fullstackpython/blog-code-examples"&gt;flask-auth-okta directory of the blog-code-examples&lt;/a&gt; 
repository. Use and abuse the source code for applications you want to 
build.&lt;/p&gt;
&lt;h2&gt;Installing Dependencies&lt;/h2&gt;
&lt;p&gt;Create a new Python virtualenv for this project:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python3 -m venv flaskauth
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Activate the virtual environment with the &lt;code&gt;activate&lt;/code&gt; script:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;. ./flaskauth/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The command prompt should change after activation:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181008-flask-okta/activate-virtualenv.jpg" width="100%" class="shot rnd outl" alt="Activating the flaskauth virtualenv."&gt;&lt;/p&gt;
&lt;p&gt;Remember that you will have to activate the virtualenv in every terminal 
window where you want to use the dependencies contained in this virtualenv.&lt;/p&gt;
&lt;p&gt;Now we can install &lt;a href="/flask.html"&gt;Flask&lt;/a&gt; and the Okta dependencies.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install flask&amp;gt;=1.0.2 flask-oidc&amp;gt;=1.4.0 okta==0.0.4
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Look for output similar to the following to confirm that the dependencies
successfully installed:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;idna&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mf"&gt;2.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;2.5&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;okta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Downloading&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;0276479&lt;/span&gt;&lt;span class="n"&gt;a4b3caeb8a8c1af2f8e4355746a97fab05a372e4a2c6a6b876165&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;idna&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.7&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;58&lt;/span&gt;&lt;span class="n"&gt;kB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;████████████████████████████████&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;61&lt;/span&gt;&lt;span class="n"&gt;kB&lt;/span&gt; &lt;span class="mf"&gt;16.6&lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; 
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;urllib3&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mf"&gt;1.24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.21&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;okta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Downloading&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bd&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;c9&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="n"&gt;fdd990019071a4a32a5e7cb78a1d92c53851ef4f56f62a3486e6a7d8ffb&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;urllib3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.23&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;133&lt;/span&gt;&lt;span class="n"&gt;kB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;████████████████████████████████&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;143&lt;/span&gt;&lt;span class="n"&gt;kB&lt;/span&gt; &lt;span class="mf"&gt;14.0&lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; 
&lt;span class="n"&gt;Installing&lt;/span&gt; &lt;span class="n"&gt;collected&lt;/span&gt; &lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MarkupSafe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Jinja2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;itsdangerous&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Werkzeug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pyasn1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pyasn1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rsa&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;httplib2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;six&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;oauth2client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;oidc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chardet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;certifi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idna&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urllib3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;dateutil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;okta&lt;/span&gt;
  &lt;span class="n"&gt;Running&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;MarkupSafe&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt;
  &lt;span class="n"&gt;Running&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;itsdangerous&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt;
  &lt;span class="n"&gt;Running&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;httplib2&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt;
  &lt;span class="n"&gt;Running&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;oidc&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt;
  &lt;span class="n"&gt;Running&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;okta&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt;
&lt;span class="n"&gt;Successfully&lt;/span&gt; &lt;span class="n"&gt;installed&lt;/span&gt; &lt;span class="n"&gt;Jinja2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.10&lt;/span&gt; &lt;span class="n"&gt;MarkupSafe&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="n"&gt;Werkzeug&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.14&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;certifi&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2018.8&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="n"&gt;chardet&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;6.7&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;oidc&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.4&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;httplib2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.11&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="n"&gt;idna&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.7&lt;/span&gt; &lt;span class="n"&gt;itsdangerous&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.24&lt;/span&gt; &lt;span class="n"&gt;oauth2client&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;4.1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="n"&gt;okta&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="n"&gt;pyasn1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="n"&gt;pyasn1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;modules&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;dateutil&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.7&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.19&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;rsa&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;4.0&lt;/span&gt; &lt;span class="n"&gt;six&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.11&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;urllib3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.23&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We installed our required Flask and the Okta dependencies so let's get to building
the Flask application.&lt;/p&gt;
&lt;h2&gt;Creating A Basic Flask App&lt;/h2&gt;
&lt;p&gt;The first step before adding authentication to our Flask application is
to write some scaffolding functions. The authentication will hook into
these functions, such as &lt;code&gt;signin&lt;/code&gt; and &lt;code&gt;signout&lt;/code&gt;, to ensure the auth
process works properly.&lt;/p&gt;
&lt;p&gt;Create a directory for your project named &lt;code&gt;thundercats&lt;/code&gt;. Why &lt;code&gt;thundercats&lt;/code&gt;?
Why &lt;em&gt;not&lt;/em&gt; Thundercats?&lt;/p&gt;
&lt;p&gt;Within the &lt;code&gt;thundercats&lt;/code&gt; directly create a file named &lt;code&gt;app.py&lt;/code&gt; with the 
following initial contents:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# imports for Flask&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;


&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/lair&amp;quot;&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;lair&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Thundercats (supposed to be hidden) lair.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&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;landing_page&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Thundercats, Thundercats, hoooooooooooo!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We can run our Flask app using the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;set FLASK_APP=app.py
flask run
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Go to localhost:5000 in your web browser and you should see:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181008-flask-okta/flask-app-running.png" width="100%" class="shot rnd outl" alt="Simple version of Flask application running."&gt;&lt;/p&gt;
&lt;p&gt;Now go to our "hidden lair" at localhost:5000/lair/. Eventually this
page should require authentication to access, but for now it appears
without any login challenge:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181008-flask-okta/flask-app-lair.png" width="100%" class="shot rnd outl" alt="Part of Flask app that should be hidden behind a login page."&gt;&lt;/p&gt;
&lt;p&gt;Awesome, our basic app is up and running, let's get to the authentication
functionality.&lt;/p&gt;
&lt;h2&gt;Auth-as-a-Service&lt;/h2&gt;
&lt;p&gt;Head to the &lt;a href="https://developer.okta.com/signup"&gt;Okta developers sign up page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181008-flask-okta/okta-sign-up.jpg" width="100%" class="shot rnd outl" alt="Okta developers landing page for signing up."&gt;&lt;/p&gt;
&lt;p&gt;Sign up for a new account or log into your existing account.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181008-flask-okta/okta-dev.jpg" width="100%" class="shot rnd outl" alt="Okta developer sign up flow."&gt;&lt;/p&gt;
&lt;p&gt;The interesting bit about the Okta developer sign up flow is that now you 
should check your email to finish creating your account. Look for an email 
like this one:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181008-flask-okta/okta-email.jpg" width="100%" class="shot rnd outl" alt="Okta sign up email."&gt;&lt;/p&gt;
&lt;p&gt;Click the "Sign In" button and log into developer account using 
the temporary password found in the email. Set a new password and challenge
question. Then pick an image to match your account login process.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181008-flask-okta/okta-create-account.png" width="100%" class="shot rnd outl" alt="Okta finish creating an account."&gt;&lt;/p&gt;
&lt;p&gt;Click the "Create Account" button and you will be wisked away to the
Okta developer dashboard.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181008-flask-okta/dev-dashboard.png" width="100%" class="shot rnd outl" alt="Okta developer dashboard."&gt;&lt;/p&gt;
&lt;p&gt;Find the "Org URL" as shown in the following image.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181008-flask-okta/okta-dev-dashboard-url.jpg" width="100%" class="shot rnd outl" alt="Okta Org URL value."&gt;&lt;/p&gt;
&lt;p&gt;We are going to use that URL in our secret credentials file so that
our Flask web app can properly connect to the Okta service.&lt;/p&gt;
&lt;p&gt;Create a new file in your project directory named 
&lt;code&gt;openidconnect_secrets.json&lt;/code&gt; with the following contents:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;quot;web&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;client_id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;{{ OKTA_CLIENT_ID }}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;client_secret&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;{{ OKTA_CLIENT_SECRET }}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;auth_uri&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;{{ OKTA_ORG_URL }}/oauth2/default/v1/authorize&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;token_uri&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;{{ OKTA_ORG_URL }}/oauth2/default/v1/token&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;issuer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;{{ OKTA_ORG_URL }}/oauth2/default&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;userinfo_uri&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;{{ OKTA_ORG_URL }}/oauth2/default/userinfo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;redirect_uris&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s2"&gt;&amp;quot;http://localhost:5000/oidc/callback&amp;quot;&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;/pre&gt;&lt;/div&gt;


&lt;p&gt;Replace the four &lt;code&gt;{{ OKTA_ORG_URL }}&lt;/code&gt; placeholders with the Org URL value
found in your dashboard. We will fill in the rest of the placeholders with 
actual values as we proceed through the tutorial. My 
&lt;code&gt;openidconnect_secret.json&lt;/code&gt; file would currently have the following
values based on my developer dashboard Org URL. 
&lt;strong&gt;Remember that your URL values will be different!&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;quot;web&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;client_id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;{{ OKTA_CLIENT_ID }}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;client_secret&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;{{ OKTA_CLIENT_SECRET }}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt;~~&lt;/span&gt;    &lt;span class="nt"&gt;&amp;quot;auth_uri&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://dev-860408.oktapreview.com/oauth2/default/v1/authorize&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt;~~&lt;/span&gt;    &lt;span class="nt"&gt;&amp;quot;token_uri&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://dev-860408.oktapreview.com/oauth2/default/v1/token&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt;~~&lt;/span&gt;    &lt;span class="nt"&gt;&amp;quot;issuer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://dev-860408.oktapreview.com/oauth2/default&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt;~~&lt;/span&gt;    &lt;span class="nt"&gt;&amp;quot;userinfo_uri&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://dev-860408.oktapreview.com/oauth2/default/userinfo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;redirect_uris&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s2"&gt;&amp;quot;http://localhost:5000/oidc/callback&amp;quot;&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;/pre&gt;&lt;/div&gt;


&lt;p&gt;Okay awesome, we have our Okta account set up so we can add the 
authentication code to our Flask application.&lt;/p&gt;
&lt;h2&gt;Connecting Flask to Okta&lt;/h2&gt;
&lt;p&gt;We need to connect our Flask code to our new Okta account. The
recommended way of including variables such as account credentials
in a Flask application is through
&lt;a href="http://flask.pocoo.org/docs/1.0/config/"&gt;configuration handling&lt;/a&gt; 
so we will use that in our account.&lt;/p&gt;
&lt;p&gt;Update the Flask code with the following highlighted lines.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# imports for both Flask and Okta connection&lt;/span&gt;
&lt;span class="o"&gt;~~&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;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask_oidc&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenIDConnect&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;okta&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UsersClient&lt;/span&gt;


&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="c1"&gt;# secret credentials for Okta connection&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="n"&gt;app&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="s2"&gt;&amp;quot;OIDC_CLIENT_SECRETS&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;openidconnect_secrets.json&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="n"&gt;app&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="s2"&gt;&amp;quot;OIDC_COOKIE_SECURE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="n"&gt;app&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="s2"&gt;&amp;quot;OIDC_CALLBACK_ROUTE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/oidc/callback&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="n"&gt;app&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="s2"&gt;&amp;quot;OIDC_SCOPES&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;openid&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;email&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;profile&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="n"&gt;app&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="s2"&gt;&amp;quot;SECRET_KEY&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SECRET_KEY&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="n"&gt;app&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="s2"&gt;&amp;quot;OIDC_ID_TOKEN_COOKIE_NAME&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;oidc_token&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="c1"&gt;# instantiate OpenID client to handle user session&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="n"&gt;oidc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpenIDConnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="c1"&gt;# Okta client will determine if a user has an appropriate account&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="n"&gt;okta_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UsersClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;OKTA_ORG_URL&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;                          &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;OKTA_AUTH_TOKEN&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/lair&amp;quot;&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;lair&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Thundercats (supposed to be hidden) lair.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&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;landing_page&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Thundercats, Thundercats, hoooooooooooo!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We first add three import lines, one to pull values from environment
variables, and the next two imports to make it possible to use OpenID
Connect and Okta in our application.&lt;/p&gt;
&lt;p&gt;The rest of the new code sets Flask application configuration
values that can be used to instantiate the OpenID Connect and
Okta clients.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;OIDC_CLIENT_SECRETS&lt;/code&gt;: the location of the OpenID Connect secrets file&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OIDC_COOKIE_SECURE&lt;/code&gt;: allows development mode for testing user login and
  registration without SSL. Your application must set this to &lt;code&gt;True&lt;/code&gt; in a
  production application.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OIDC_CALLBACK_ROUTE&lt;/code&gt;: URL in the web app for handling user logins&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OIDC_SCOPES&lt;/code&gt;: what data to request about the user when they log in. Our
  application requests the basic email, name and profile information&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SECRET_KEY&lt;/code&gt;: this is a Flask setting to keep sessions secure. The key 
  must never be made public or your web application user sessions will be
  compromised. &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Where do we get those application configuration values though? We
need to obtain them from our Okta account so go back to the
dashboard to create a new OpenID Connect application.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181008-flask-okta/select-applications.jpg" width="100%" class="shot rnd outl" alt="Select applications on the Okta developer dashboard."&gt;&lt;/p&gt;
&lt;p&gt;OpenID Connect applications use a client ID and client secret in
place of traditional usernames and passwords. The client ID and
client secret will tell your authorization server to recognize your 
application. Press the "Add Application" button.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181008-flask-okta/add-application.jpg" width="100%" class="shot rnd outl" alt="Click the Add Application button."&gt;&lt;/p&gt;
&lt;p&gt;On the new application screen choose "Web" and then press "Next".&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181008-flask-okta/web-application.jpg" width="100%" class="shot rnd outl" alt="Choose a web application."&gt;&lt;/p&gt;
&lt;p&gt;On the next page there are numerous configuration options but only a 
few values we need to fill in before we can get our credentials. Set
the following values to the &lt;code&gt;Name&lt;/code&gt;, &lt;code&gt;Base URIs&lt;/code&gt; and &lt;code&gt;Login redirect URIs&lt;/code&gt;
properties:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ThunderFlaskCats&lt;/strong&gt; for &lt;code&gt;Name&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;http://localhost:5000&lt;/strong&gt; for &lt;code&gt;Base URIs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;http://localhost:5000/oidc/callback&lt;/strong&gt; for &lt;code&gt;Login redirect URIs&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="/img/181008-flask-okta/set-app-configuration.jpg" width="100%" class="shot rnd outl" alt="Set application configuration values."&gt;&lt;/p&gt;
&lt;p&gt;Those are the three values you need to fill in for now so save the 
application to create it.&lt;/p&gt;
&lt;p&gt;On the next page scroll down to find your client and secret keys.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181008-flask-okta/client-credentials.jpg" width="100%" class="shot rnd outl" alt="Save the client credentials for later use."&gt;&lt;/p&gt;
&lt;p&gt;Copy and paste the client ID and client secret into the following 
highlighted lines to replace the &lt;code&gt;{{ OKTA_CLIENT_ID }}&lt;/code&gt; and 
&lt;code&gt;{{ OKTA_CLIENT_SECRET }}&lt;/code&gt; placeholders.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;quot;web&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;~~&lt;/span&gt;    &lt;span class="nt"&gt;&amp;quot;client_id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;{{ OKTA_CLIENT_ID }}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt;~~&lt;/span&gt;    &lt;span class="nt"&gt;&amp;quot;client_secret&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;{{ OKTA_CLIENT_SECRET }}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;auth_uri&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://dev-860408.oktapreview.com/oauth2/default/v1/authorize&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;token_uri&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://dev-860408.oktapreview.com/oauth2/default/v1/token&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;issuer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://dev-860408.oktapreview.com/oauth2/default&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;userinfo_uri&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://dev-860408.oktapreview.com/oauth2/default/userinfo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;redirect_uris&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s2"&gt;&amp;quot;http://localhost:5000/oidc/callback&amp;quot;&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;/pre&gt;&lt;/div&gt;


&lt;p&gt;Save the file and make sure to keep it out of version control as those
secret values need to stay secret.&lt;/p&gt;
&lt;p&gt;We have one more step in the Okta developer dashboard before we upgrade 
our Flask application with the authentication code: creating an 
&lt;a href="https://developer.okta.com/use_cases/api_access_management/"&gt;API authentication token&lt;/a&gt;.
Go to the API tab.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181008-flask-okta/api-tab.jpg" width="100%" class="shot rnd outl" alt="Click the API tab in the dashboard."&gt;&lt;/p&gt;
&lt;p&gt;Click the "Create Token" button.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181008-flask-okta/create-token.png" width="100%" class="shot rnd outl" alt="Create an authentication token to access Okta."&gt;&lt;/p&gt;
&lt;p&gt;Name the token &lt;code&gt;ThunderFlaskCatsToken&lt;/code&gt; and copy it. Save the token somewhere
safe as we will not be able to access it through the dashboard again. We
are going to use this token when setting the &lt;code&gt;OKTA_AUTH_TOKEN&lt;/code&gt; environment
variable in the next section of this tutorial.&lt;/p&gt;
&lt;p&gt;Okay, we finally have all the Okta service configuration and tokens in
our &lt;code&gt;openidconnect_secret.json&lt;/code&gt; file that we need to finish our application.&lt;/p&gt;
&lt;h2&gt;Protecting the Lair&lt;/h2&gt;
&lt;p&gt;Our configuration is set so update the &lt;code&gt;app.py&lt;/code&gt; file with the following 
highlighted lines:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# imports for both Flask and Okta connection&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="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&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;redirect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url_for&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask_oidc&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenIDConnect&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;okta&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UsersClient&lt;/span&gt;


&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# secret credentials for Okta connection&lt;/span&gt;
&lt;span class="n"&gt;app&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="s2"&gt;&amp;quot;OIDC_CLIENT_SECRETS&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;openidconnect_secrets.json&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;app&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="s2"&gt;&amp;quot;OIDC_COOKIE_SECURE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;span class="n"&gt;app&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="s2"&gt;&amp;quot;OIDC_CALLBACK_ROUTE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/oidc/callback&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;app&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="s2"&gt;&amp;quot;OIDC_SCOPES&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;openid&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;email&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;profile&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;app&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="s2"&gt;&amp;quot;SECRET_KEY&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SECRET_KEY&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;app&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="s2"&gt;&amp;quot;OIDC_ID_TOKEN_COOKIE_NAME&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;oidc_token&amp;quot;&lt;/span&gt;
&lt;span class="c1"&gt;# instantiate OpenID client to handle user session&lt;/span&gt;
&lt;span class="n"&gt;oidc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpenIDConnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Okta client will determine if a user has an appropriate account&lt;/span&gt;
&lt;span class="n"&gt;okta_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UsersClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;OKTA_ORG_URL&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                          &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;OKTA_AUTH_TOKEN&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;before_request&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;before_request&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;oidc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_loggedin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;        &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&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;okta_client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oidc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_getfield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sub&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;        &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/lair&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="nd"&gt;@oidc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;require_login&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lair&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Thundercats (supposed to be hidden) lair.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&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;landing_page&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Thundercats, Thundercats, hoooooooooooo!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/login&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="nd"&gt;@oidc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;require_login&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.lair&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/logout&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;logout&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="n"&gt;oidc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.landing_page&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The above new highlighted lines check whether or not a user is logged in
before each request. If a route requires a logged in user due to the 
&lt;code&gt;@oidc.require_login&lt;/code&gt; decorator then the user will be redirect to the
sign in page. We also added routes under &lt;code&gt;/login&lt;/code&gt; and &lt;code&gt;/logout&lt;/code&gt; to make
it possible to log in and out of the application.&lt;/p&gt;
&lt;p&gt;Set three environment variables so our application can use them when we
run it. Make sure the placeholders &lt;code&gt;ORG_URL&lt;/code&gt; and &lt;code&gt;AUTH_TOKEN&lt;/code&gt; are set with 
your actual Org URL value and auth token from the Okta developer dashboard.&lt;/p&gt;
&lt;p&gt;On the command line run the following commands, making sure to replace
any placeholder values with your own tokens and URLs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# this tells Flask we want to run the built-in server in dev mode&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="n"&gt;FLASK_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;development&lt;/span&gt;
&lt;span class="c1"&gt;# make sure to use a very long random string here that cannot be guessed&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="n"&gt;SECRET_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a very long string with lots of numbers and letters&amp;#39;&lt;/span&gt;
&lt;span class="c1"&gt;# this is the same Org URL found on your developer dashboard&lt;/span&gt;
&lt;span class="c1"&gt;# for example, https://dev-860408.oktapreview.com&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="n"&gt;OKTA_ORG_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ORG_URL&amp;#39;&lt;/span&gt;
&lt;span class="c1"&gt;# this is the API authentication token we created&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="n"&gt;OKTA_AUTH_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;AUTH_TOKEN&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now re-run the Flask application:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;set FLASK_APP=app.py
flask run
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;You should be in good shape if the development server starts up with output
like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(flaskauth)$ flask run
 * Environment: development
 * Debug mode: on
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 415-920-546
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Head to localhost:5000 in a browser where you are not already logged into
your Okta account (an incognito window of your web browser works great).&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181008-flask-okta/landing-page-incognito.png" width="100%" class="shot rnd outl" alt="Landing page while in incognito mode."&gt;&lt;/p&gt;
&lt;p&gt;Let's test the redirect functionality when we try to go to the &lt;code&gt;/lair&lt;/code&gt;
route by going to localhost:5000/lair. We get redirected to the Okta
login page.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181008-flask-okta/lair-redirect.jpg" width="100%" class="shot rnd outl" alt="Getting redirected while in incognito mode."&gt;&lt;/p&gt;
&lt;p&gt;Enter your Okta developer username and password to log into your application.
For development purposes this will work fine for testing but obviously in a
production application you will create other accounts for users to log into.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181008-flask-okta/enter-lair.jpg" width="100%" class="shot rnd outl" alt="Got into the lair URL after logging in."&gt;&lt;/p&gt;
&lt;p&gt;Let's tweak one more bit in our application to fix the glaring lack of
excitement in successfully completing the authentication code for this 
tutorial. Update the two highlighted lines to match what is in the code
block below:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# imports for both Flask and Okta connection&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;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&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;redirect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url_for&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask_oidc&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenIDConnect&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;okta&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UsersClient&lt;/span&gt;


&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# secret credentials for Okta connection&lt;/span&gt;
&lt;span class="n"&gt;app&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="s2"&gt;&amp;quot;OIDC_CLIENT_SECRETS&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;openidconnect_secrets.json&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;app&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="s2"&gt;&amp;quot;OIDC_COOKIE_SECURE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;span class="n"&gt;app&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="s2"&gt;&amp;quot;OIDC_CALLBACK_ROUTE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/oidc/callback&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;app&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="s2"&gt;&amp;quot;OIDC_SCOPES&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;openid&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;email&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;profile&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;app&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="s2"&gt;&amp;quot;SECRET_KEY&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SECRET_KEY&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;app&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="s2"&gt;&amp;quot;OIDC_ID_TOKEN_COOKIE_NAME&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;oidc_token&amp;quot;&lt;/span&gt;
&lt;span class="c1"&gt;# instantiate OpenID client to handle user session&lt;/span&gt;
&lt;span class="n"&gt;oidc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpenIDConnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Okta client will determine if a user has an appropriate account&lt;/span&gt;
&lt;span class="n"&gt;okta_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UsersClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;OKTA_ORG_URL&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                          &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;OKTA_AUTH_TOKEN&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;before_request&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;before_request&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;oidc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_loggedin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&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;okta_client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oidc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_getfield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sub&amp;quot;&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;g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/lair&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@oidc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;require_login&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lair&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="n"&gt;thundercats_lair&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;lt;html&amp;gt;&amp;lt;head&amp;gt;&amp;lt;title&amp;gt;Thundercats, hoooo!&amp;lt;/title&amp;gt;&amp;lt;/head&amp;gt;&amp;lt;body&amp;gt;&amp;lt;h1&amp;gt;Thundercats now hidden lair.&amp;lt;/h1&amp;gt;&amp;lt;iframe src=&amp;quot;https://giphy.com/embed/ahXtBEbHiraxO&amp;quot; width=&amp;quot;480&amp;quot; height=&amp;quot;273&amp;quot; frameBorder=&amp;quot;0&amp;quot; class=&amp;quot;giphy-embed&amp;quot; allowFullScreen&amp;gt;&amp;lt;/iframe&amp;gt;&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://giphy.com/gifs/retro-cartoons-thundercats-ahXtBEbHiraxO&amp;quot;&amp;gt;via GIPHY&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="k"&gt;return&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;thundercats_lair&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&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;landing_page&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Thundercats, Thundercats, hoooooooooooo!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/login&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@oidc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;require_login&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Force user to login and then redirect them to the lair.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.lair&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/logout&amp;quot;&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;logout&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;oidc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logout&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;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.landing_page&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Refresh the lair page.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/181008-flask-okta/refreshed-lair.jpg" width="100%" class="shot rnd outl" alt="Lair page with new GIF."&gt;&lt;/p&gt;
&lt;p&gt;Alright that's just a little bit better! Go to localhost:5000/logout to 
unauthenticate your user. When you go to localhost:5000/lair again you 
will now have to re-authenticate. &lt;/p&gt;
&lt;h2&gt;What Now?&lt;/h2&gt;
&lt;p&gt;We just built an example Flask application with user authentication via 
the &lt;a href="https://developer.okta.com/use_cases/api_access_management/"&gt;Okta API&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Next up try the following tutorials to add other features to your
Flask application:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/blog/respond-sms-text-messages-python-flask.html"&gt;Responding to SMS Text Messages with Python &amp;amp; Flask&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/blog/hosted-monitoring-flask-web-apps.html"&gt;How to Add Hosted Monitoring to Flask Web Applications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/blog/develop-flask-web-apps-docker-containers-macos.html"&gt;Develop and Run Flask Apps within Docker Containers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can also determine what to code next in your Python project by reading 
the &lt;a href="/table-of-contents.html"&gt;Full Stack Python table of contents page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Questions? Contact me via Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;. I'm also on GitHub with
the username &lt;a href="https://github.com/mattmakai"&gt;mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Something wrong with this post? Fork 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/181008-add-user-auth-flask-okta.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Wed, 10 Oct 2018 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2018-10-08:blog/add-user-authentication-flask-apps-okta.html</guid></item><item><title>Configure Python 3, Flask and Gunicorn on Ubuntu 18.04 LTS</title><link>https://www.fullstackpython.com/blog/python-3-flask-gunicorn-ubuntu-1804-bionic-beaver.html</link><description>&lt;p&gt;&lt;a href="/ubuntu.html"&gt;Ubuntu Linux's&lt;/a&gt; latest Long Term Support (LTS)
&lt;a href="/operating-systems.html"&gt;operating system&lt;/a&gt; version is 
&lt;a href="http://releases.ubuntu.com/18.04/"&gt;18.04&lt;/a&gt; and was released in April 2018.
The 18.04 update is code named "Bionic Beaver" and it includes
&lt;a href="/python-2-or-3.html"&gt;Python 3&lt;/a&gt; by default. However, there are bunch of
dependencies you will need to install to get this release set up as a 
&lt;a href="/development-environments.html"&gt;development environment&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In this tutorial we will get Python 3.6 configured with development system
packages to start a new &lt;a href="/flask.html"&gt;Flask&lt;/a&gt; web application project and 
run it with &lt;a href="/green-unicorn-gunicorn.html"&gt;Green Unicorn (Gunicorn)&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Our Tools&lt;/h2&gt;
&lt;p&gt;Our project will use the Ubuntu 18.04 release along with a few other 
libraries. Note that if you are using the older 16.04 LTS release, there
is also 
&lt;a href="/blog/python-3-flask-green-unicorn-ubuntu-1604-xenial-xerus.html"&gt;a guide that will walk you through setting up that version&lt;/a&gt;
as your development environment.&lt;/p&gt;
&lt;p&gt;We will install the following tools as we step through the rest of 
the sections in this tutorial:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://releases.ubuntu.com/18.04/"&gt;Ubuntu 18.04 LTS (Bionic Beaver)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/why-use-python.html"&gt;Python&lt;/a&gt; version 
  &lt;a href="https://docs.python.org/3/whatsnew/3.6.html"&gt;3.6.5&lt;/a&gt; 
  (default in Ubuntu 18.04)&lt;/li&gt;
&lt;li&gt;&lt;a href="/flask.html"&gt;Flask&lt;/a&gt; web framework version 
  &lt;a href="http://flask.pocoo.org/docs/1.0/changelog/#version-1-0-2"&gt;1.0.2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/green-unicorn-gunicorn.html"&gt;Green Unicorn (Gunicorn)&lt;/a&gt; version 
  &lt;a href="http://docs.gunicorn.org/en/stable/news.html"&gt;19.8.1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you're running on Mac OS X or Windows, use virtualization software such
as &lt;a href="https://www.parallels.com/products/desktop/"&gt;Parallels&lt;/a&gt; or
&lt;a href="https://www.virtualbox.org/wiki/Downloads"&gt;VirtualBox&lt;/a&gt; with the 
&lt;a href="http://releases.ubuntu.com/18.04/"&gt;Ubuntu .iso file&lt;/a&gt;. Either the amd64 or
i386 version for 18.04 will work. I am using amd64 for development and testing
in this tutorial.&lt;/p&gt;
&lt;p&gt;When you boot up to the Ubuntu desktop you should see a screen like this one.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180614-ubuntu-flask-gunicorn/ubuntu-desktop.jpg" width="100%" class="shot rnd outl"&gt;&lt;/p&gt;
&lt;p&gt;We're ready to get our development environment configured.&lt;/p&gt;
&lt;h2&gt;System Packages&lt;/h2&gt;
&lt;p&gt;Open up a terminal window to proceed with the setup.&lt;/p&gt;
&lt;p&gt;Use the following two commands to check which version of Python 3 is installed&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python3 --version
which python3
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The Python version should be 3.6.5 and the location &lt;code&gt;/usr/bin/python3&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Our Ubuntu installation requires a few system packages to do development
rather than just run Python scripts. Run the following &lt;code&gt;apt-get&lt;/code&gt; command
and enter your &lt;code&gt;sudo&lt;/code&gt; password to allow restricted system access.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo apt-get install python3-dev python3-pip python3-virtualenv
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We should see the following prompt requesting &lt;code&gt;sudo&lt;/code&gt; access. Enter &lt;code&gt;y&lt;/code&gt; to 
let the system package manager complete the installation.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following packages were automatically installed and are no longer required:
  linux-headers-4.15.0-20 linux-headers-4.15.0-20-generic
  linux-image-4.15.0-20-generic linux-modules-4.15.0-20-generic
  linux-modules-extra-4.15.0-20-generic
Use &lt;span class="s1"&gt;&amp;#39;sudo apt autoremove&amp;#39;&lt;/span&gt; to remove them.
The following additional packages will be installed:
  dh-python libexpat1-dev libpython3-dev libpython3.6-dev python3-setuptools
  python3-wheel python3.6-dev
Suggested packages:
  python-setuptools-doc
The following NEW packages will be installed:
  dh-python libexpat1-dev libpython3-dev libpython3.6-dev python3-dev
  python3-pip python3-setuptools python3-virtualenv python3-wheel
  python3.6-dev
&lt;span class="m"&gt;0&lt;/span&gt; upgraded, &lt;span class="m"&gt;10&lt;/span&gt; newly installed, &lt;span class="m"&gt;0&lt;/span&gt; to remove and &lt;span class="m"&gt;11&lt;/span&gt; not upgraded.
Need to get &lt;span class="m"&gt;3&lt;/span&gt;,617 kB/3,661 kB of archives.
After this operation, &lt;span class="m"&gt;20&lt;/span&gt;.2 MB of additional disk space will be used.
Do you want to &lt;span class="k"&gt;continue&lt;/span&gt;? &lt;span class="o"&gt;[&lt;/span&gt;Y/n&lt;span class="o"&gt;]&lt;/span&gt; 
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The package manager will do the dirty work and should report when the
installation finishes successfully.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;...clipped a bunch of installation lines &lt;span class="k"&gt;for&lt;/span&gt; brevity...&lt;span class="o"&gt;)&lt;/span&gt;
Unpacking python3-wheel &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.30.0-0.2&lt;span class="o"&gt;)&lt;/span&gt; ...
Setting up python3-wheel &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.30.0-0.2&lt;span class="o"&gt;)&lt;/span&gt; ...
Setting up python3-virtualenv &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;.1.0+ds-1.1&lt;span class="o"&gt;)&lt;/span&gt; ...
Setting up python3-pip &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;.0.1-2.3~ubuntu1&lt;span class="o"&gt;)&lt;/span&gt; ...
Setting up libexpat1-dev:amd64 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.2.5-3&lt;span class="o"&gt;)&lt;/span&gt; ...
Processing triggers &lt;span class="k"&gt;for&lt;/span&gt; man-db &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.8.3-2&lt;span class="o"&gt;)&lt;/span&gt; ...
Setting up python3-setuptools &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;39&lt;/span&gt;.0.1-2&lt;span class="o"&gt;)&lt;/span&gt; ...
Setting up dh-python &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.20180325ubuntu2&lt;span class="o"&gt;)&lt;/span&gt; ...
Setting up libpython3.6-dev:amd64 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.6.5-3&lt;span class="o"&gt;)&lt;/span&gt; ...
Setting up python3.6-dev &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.6.5-3&lt;span class="o"&gt;)&lt;/span&gt; ...
Setting up libpython3-dev:amd64 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.6.5-3&lt;span class="o"&gt;)&lt;/span&gt; ...
Setting up python3-dev &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.6.5-3&lt;span class="o"&gt;)&lt;/span&gt; ...
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The packages we need are now installed. We can continue on to install our 
Python-specific dependencies.&lt;/p&gt;
&lt;h2&gt;Virtual environment&lt;/h2&gt;
&lt;p&gt;We installed &lt;a href="https://virtualenv.pypa.io/en/latest/"&gt;virtualenv&lt;/a&gt; 
and &lt;a href="https://pypi.org/project/pip"&gt;pip&lt;/a&gt; to handle our 
&lt;a href="/application-dependencies.html"&gt;application dependencies&lt;/a&gt;.
We can now use them to download and install Flask and Gunicorn.&lt;/p&gt;
&lt;p&gt;Create a directory to store your virtualenvs. Then create a new virtualenv
within that directory.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# make sure pip and setuptools are the latest version&lt;/span&gt;
pip3 install --upgrade pip setuptools
&lt;span class="c1"&gt;# the tilde (&amp;quot;~&amp;quot;) specifies the user&amp;#39;s home directory, such as &amp;quot;/home/matt&amp;quot;&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ~
mkdir venvs
&lt;span class="c1"&gt;# specify the system python3 installation&lt;/span&gt;
python3 -m venv venvs/flask1804
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Activate the virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt; ~/venvs/flask1804/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Our prompt will change when the virutalenv is activated.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180614-ubuntu-flask-gunicorn/venv-activated.jpg" width="100%" class="shot rnd outl"&gt;&lt;/p&gt;
&lt;p&gt;Our virtualenv is now activated with Python 3. We can install any
dependencies we need such as Flask and Gunicorn.&lt;/p&gt;
&lt;h2&gt;Flask and Gunicorn&lt;/h2&gt;
&lt;p&gt;We're going to use &lt;code&gt;pip&lt;/code&gt; within our new virtualenv but it's a good
idea to update it to the latest version. We should also install the
&lt;code&gt;wheel&lt;/code&gt; package to remove installation warnings when &lt;code&gt;pip&lt;/code&gt; tries to
use &lt;a href="https://pythonwheels.com/"&gt;Python wheels&lt;/a&gt;, which are the newest 
standard in an admittedly long line of Python distribution package
models.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install --upgrade pip
pip install wheel
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We can now install Flask and Green Unicorn via the &lt;code&gt;pip&lt;/code&gt; command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install flask gunicorn
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Look for output similar to the following to ensure the libraries installed
without an issue.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;flask1804&lt;span class="o"&gt;)&lt;/span&gt; matt@ubuntu:~$ pip install flask gunicorn
Collecting flask
  Using cached https://files.pythonhosted.org/packages/7f/e7/08578774ed4536d3242b14dacb4696386634607af824ea997202cd0edb4b/Flask-1.0.2-py2.py3-none-any.whl
Collecting gunicorn
  Using cached https://files.pythonhosted.org/packages/55/cb/09fe80bddf30be86abfc06ccb1154f97d6c64bb87111de066a5fc9ccb937/gunicorn-19.8.1-py2.py3-none-any.whl
Collecting click&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;.1 &lt;span class="o"&gt;(&lt;/span&gt;from flask&lt;span class="o"&gt;)&lt;/span&gt;
  Using cached https://files.pythonhosted.org/packages/34/c1/8806f99713ddb993c5366c362b2f908f18269f8d792aff1abfd700775a77/click-6.7-py2.py3-none-any.whl
Collecting Werkzeug&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.14 &lt;span class="o"&gt;(&lt;/span&gt;from flask&lt;span class="o"&gt;)&lt;/span&gt;
  Using cached https://files.pythonhosted.org/packages/20/c4/12e3e56473e52375aa29c4764e70d1b8f3efa6682bef8d0aae04fe335243/Werkzeug-0.14.1-py2.py3-none-any.whl
Collecting itsdangerous&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.24 &lt;span class="o"&gt;(&lt;/span&gt;from flask&lt;span class="o"&gt;)&lt;/span&gt;
  Using cached https://files.pythonhosted.org/packages/dc/b4/a60bcdba945c00f6d608d8975131ab3f25b22f2bcfe1dab221165194b2d4/itsdangerous-0.24.tar.gz
Collecting Jinja2&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.10 &lt;span class="o"&gt;(&lt;/span&gt;from flask&lt;span class="o"&gt;)&lt;/span&gt;
  Using cached https://files.pythonhosted.org/packages/7f/ff/ae64bacdfc95f27a016a7bed8e8686763ba4d277a78ca76f32659220a731/Jinja2-2.10-py2.py3-none-any.whl
Collecting MarkupSafe&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.23 &lt;span class="o"&gt;(&lt;/span&gt;from Jinja2&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.10-&amp;gt;flask&lt;span class="o"&gt;)&lt;/span&gt;
  Using cached https://files.pythonhosted.org/packages/4d/de/32d741db316d8fdb7680822dd37001ef7a448255de9699ab4bfcbdf4172b/MarkupSafe-1.0.tar.gz
Building wheels &lt;span class="k"&gt;for&lt;/span&gt; collected packages: itsdangerous, MarkupSafe
  Running setup.py bdist_wheel &lt;span class="k"&gt;for&lt;/span&gt; itsdangerous ... &lt;span class="k"&gt;done&lt;/span&gt;
  Stored &lt;span class="k"&gt;in&lt;/span&gt; directory: /home/matt/.cache/pip/wheels/2c/4a/61/5599631c1554768c6290b08c02c72d7317910374ca602ff1e5
  Running setup.py bdist_wheel &lt;span class="k"&gt;for&lt;/span&gt; MarkupSafe ... &lt;span class="k"&gt;done&lt;/span&gt;
  Stored &lt;span class="k"&gt;in&lt;/span&gt; directory: /home/matt/.cache/pip/wheels/33/56/20/ebe49a5c612fffe1c5a632146b16596f9e64676768661e4e46
Successfully built itsdangerous MarkupSafe
Installing collected packages: click, Werkzeug, itsdangerous, MarkupSafe, Jinja2, flask, gunicorn
Successfully installed Jinja2-2.10 MarkupSafe-1.0 Werkzeug-0.14.1 click-6.7 flask-1.0.2 gunicorn-19.8.1 itsdangerous-0.24
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Create a new directory named &lt;code&gt;flask1804&lt;/code&gt; under your home directory (not
within the &lt;code&gt;venvs&lt;/code&gt; subdirectory) that will store our Flask test project. 
Change directory into the new folder.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;mkdir ~/flask1804
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/flask1804
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Create a new file named &lt;code&gt;__init__.py&lt;/code&gt; within our &lt;code&gt;flaskproj&lt;/code&gt; directory so
we can test to make sure Flask is working properly. I usually use
&lt;a href="/vim.html"&gt;Vim&lt;/a&gt; but &lt;a href="/emacs.html"&gt;Emacs&lt;/a&gt; and other 
&lt;a href="/development-environments.html"&gt;development environments&lt;/a&gt; work great as
well.&lt;/p&gt;
&lt;p&gt;Within &lt;code&gt;__init__.py&lt;/code&gt; write the following code.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;


&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&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;index&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;It works!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We could run our app with the Flask development server using the 
&lt;code&gt;python __init__.py&lt;/code&gt; command. Instead run the Flask app with
Gunicorn. Go to the directory above the &lt;code&gt;flask1804&lt;/code&gt; folder, in our
case we can enter &lt;code&gt;cd ~&lt;/code&gt; then use the &lt;code&gt;gunicorn&lt;/code&gt; command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;gunicorn flask1804.app:app
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We should see:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2018&lt;/span&gt;-06-15 &lt;span class="m"&gt;15&lt;/span&gt;:54:31 -0400&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;5174&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;INFO&lt;span class="o"&gt;]&lt;/span&gt; Starting gunicorn &lt;span class="m"&gt;19&lt;/span&gt;.8.1
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2018&lt;/span&gt;-06-15 &lt;span class="m"&gt;15&lt;/span&gt;:54:31 -0400&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;5174&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;INFO&lt;span class="o"&gt;]&lt;/span&gt; Listening at: http://127.0.0.1:8000 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5174&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2018&lt;/span&gt;-06-15 &lt;span class="m"&gt;15&lt;/span&gt;:54:31 -0400&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;5174&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;INFO&lt;span class="o"&gt;]&lt;/span&gt; Using worker: sync
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2018&lt;/span&gt;-06-15 &lt;span class="m"&gt;15&lt;/span&gt;:54:31 -0400&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;5177&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;INFO&lt;span class="o"&gt;]&lt;/span&gt; Booting worker with pid: &lt;span class="m"&gt;5177&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Great now we can bring up our shell Flask app in the web browser at
the &lt;code&gt;localhost:8000&lt;/code&gt; or &lt;code&gt;127.0.0.1:8000&lt;/code&gt; address.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180614-ubuntu-flask-gunicorn/it-works.jpg" width="100%" class="shot rnd outl"&gt;&lt;/p&gt;
&lt;p&gt;Now you're ready for some real &lt;a href="/flask.html"&gt;Flask&lt;/a&gt; development!&lt;/p&gt;
&lt;h2&gt;Ready to Code&lt;/h2&gt;
&lt;p&gt;That provides a quick configuration for getting started on 18.04 LTS 
developing &lt;a href="/flask.html"&gt;Flask&lt;/a&gt; applications with the 
&lt;a href="/green-unicorn-gunicorn.html"&gt;Gunicorn&lt;/a&gt; &lt;a href="/wsgi-servers.html"&gt;WSGI server&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Next up you should check out the following tutorials that use this 
Flask configuration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/blog/respond-sms-text-messages-python-flask.html"&gt;Responding to SMS Text Messages with Python &amp;amp; Flask&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/blog/hosted-monitoring-flask-web-apps.html"&gt;How to Add Hosted Monitoring to Flask Web Applications&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Alternatively you can also determine what to code next in your Python 
project by reading the 
&lt;a href="/table-of-contents.html"&gt;Full Stack Python table of contents page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Questions? Contact me via Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;. I'm also on GitHub with
the username &lt;a href="https://github.com/mattmakai"&gt;mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Something wrong with this post? Fork 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/180614-flask-gunicorn-ubuntu-1804.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Fri, 15 Jun 2018 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2018-06-14:blog/python-3-flask-gunicorn-ubuntu-1804-bionic-beaver.html</guid></item><item><title>Running Bottle Apps in Docker Containers on macOS</title><link>https://www.fullstackpython.com/blog/first-steps-bottle-web-apps-docker-containers.html</link><description>&lt;p&gt;It can be confusing to figure out how to use &lt;a href="/docker.html"&gt;Docker&lt;/a&gt; 
containers in your &lt;a href="/why-use-python.html"&gt;Python&lt;/a&gt; and 
&lt;a href="/flask.html"&gt;Bottle&lt;/a&gt; 
&lt;a href="/development-environments.html"&gt;development environment&lt;/a&gt; workflow.
This tutorial will quickly show you the exact steps to get Docker
up and running on macOS with a working Bottle 
&lt;a href="/web-development.html"&gt;web application&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Our Tools&lt;/h2&gt;
&lt;p&gt;This tutorial is written for &lt;a href="/python-2-or-3.html"&gt;Python 3&lt;/a&gt;. It may work with
Python 2 but it has not been testing with that soon-to-be deprecated
&lt;a href="https://pythonclock.org/"&gt;2.7 version&lt;/a&gt;. You should really be using Python 3,
preferrably the latest release which is currently 
&lt;a href="https://www.python.org/downloads/release/python-365/"&gt;3.6.5&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://docs.docker.com/docker-for-mac/install/"&gt;Docker for Mac&lt;/a&gt; is necessary
to run Docker containers. I recommend that you use the stable release unless 
you have an explicit purpose for the 
&lt;a href="https://docs.docker.com/docker-for-mac/edge-release-notes/"&gt;edge channel&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Within the Docker container we will use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python 3, specifically the
  &lt;a href="https://hub.docker.com/r/library/python/tags/"&gt;slim-3.6.5 version&lt;/a&gt;
  from &lt;a href="https://hub.docker.com/"&gt;Docker Hub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/bottle.html"&gt;Bottle&lt;/a&gt; version 0.12.13&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All for the Dockerfile and the Bottle project are available open source
under the MIT license on GitHub under the 
&lt;a href="https://github.com/fullstackpython/blog-code-examples/tree/master/docker-bottle-mac"&gt;docker-bottle-mac directory&lt;/a&gt;
of the
&lt;a href="https://github.com/fullstackpython/blog-code-examples"&gt;blog-code-examples&lt;/a&gt;
repository.&lt;/p&gt;
&lt;h2&gt;Installing Docker on macOS&lt;/h2&gt;
&lt;p&gt;We must install Docker before we can spin up our containers. Jump to
the next section if you already have Docker for Mac installed and working
on your computer.&lt;/p&gt;
&lt;p&gt;On your Mac, 
&lt;a href="https://www.docker.com/community-edition#/download"&gt;download the Docker Community Edition (CE) for Mac&lt;/a&gt;
installer.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180604-bottle-docker/docker-ce.jpg" width="100%" 
 class="shot rnd" alt="Download the Docker Community Edition for Mac."&gt;&lt;/p&gt;
&lt;p&gt;Open Finder and go to the downloads folder where the installation file is located.
Follow the installation steps and open Terminal when the installer finishes. &lt;/p&gt;
&lt;p&gt;Test your Docker installation by running the &lt;code&gt;docker&lt;/code&gt; command along with the 
&lt;code&gt;--version&lt;/code&gt; flag:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;docker --version
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If Docker is installed correctly you should see the following output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Docker version 18.03.1-ce, build 9ee9f40
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Note that Docker runs through a system agent you can find in the menu bar.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180604-bottle-docker/docker-agent.png" width="100%" 
     class="shot rnd" alt="Docker agent in the menu bar."&gt;&lt;/p&gt;
&lt;p&gt;Docker is now installed so we can run a container and write a simple
Bottle application to test running an app within the container. &lt;/p&gt;
&lt;h2&gt;Dockerfile&lt;/h2&gt;
&lt;p&gt;Docker needs to know what we want in our container so we specify an
image using a &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# &lt;span class="nv"&gt;this&lt;/span&gt; &lt;span class="nv"&gt;is&lt;/span&gt; &lt;span class="nv"&gt;an&lt;/span&gt; &lt;span class="nv"&gt;official&lt;/span&gt; &lt;span class="nv"&gt;Python&lt;/span&gt; &lt;span class="nv"&gt;runtime&lt;/span&gt;, &lt;span class="nv"&gt;used&lt;/span&gt; &lt;span class="nv"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;parent&lt;/span&gt; &lt;span class="nv"&gt;image&lt;/span&gt;
&lt;span class="nv"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;python&lt;/span&gt;:&lt;span class="mi"&gt;3&lt;/span&gt;.&lt;span class="mi"&gt;6&lt;/span&gt;.&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;slim&lt;/span&gt;

# &lt;span class="nv"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;working&lt;/span&gt; &lt;span class="nv"&gt;directory&lt;/span&gt; &lt;span class="nv"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;container&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;app&lt;/span&gt;
&lt;span class="nv"&gt;WORKDIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;app&lt;/span&gt;

# &lt;span class="nv"&gt;add&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;current&lt;/span&gt; &lt;span class="nv"&gt;directory&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;container&lt;/span&gt; &lt;span class="nv"&gt;as&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;app&lt;/span&gt;
&lt;span class="nv"&gt;ADD&lt;/span&gt; . &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;app&lt;/span&gt;

# &lt;span class="nv"&gt;execute&lt;/span&gt; &lt;span class="nv"&gt;everyone&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;s favorite pip command, pip install -r&lt;/span&gt;
&lt;span class="nv"&gt;RUN&lt;/span&gt; &lt;span class="nv"&gt;pip&lt;/span&gt; &lt;span class="nv"&gt;install&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nv"&gt;trusted&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;host&lt;/span&gt; &lt;span class="nv"&gt;pypi&lt;/span&gt;.&lt;span class="nv"&gt;python&lt;/span&gt;.&lt;span class="nv"&gt;org&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;r&lt;/span&gt; &lt;span class="nv"&gt;requirements&lt;/span&gt;.&lt;span class="nv"&gt;txt&lt;/span&gt;

# &lt;span class="nv"&gt;unblock&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;Bottle&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;run&lt;/span&gt; &lt;span class="nv"&gt;on&lt;/span&gt;
&lt;span class="nv"&gt;EXPOSE&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;

# &lt;span class="nv"&gt;execute&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;Flask&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;
&lt;span class="nv"&gt;CMD&lt;/span&gt; [&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;python&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;, &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;app.py&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;]
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Save the Dockerfile and then on the commandline run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;docker build -t bottledock .
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The above &lt;code&gt;docker build&lt;/code&gt; file uses the &lt;code&gt;-t&lt;/code&gt; flag to tag the image with
the name of &lt;code&gt;bottledock&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If the build worked successfully the &lt;a href="/shells.html"&gt;shell&lt;/a&gt; will show 
some completed output like the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt; &lt;span class="n"&gt;docker&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;bottledock&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;Sending&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;Docker&lt;/span&gt; &lt;span class="n"&gt;daemon&lt;/span&gt;  &lt;span class="mf"&gt;16.38&lt;/span&gt;&lt;span class="n"&gt;kB&lt;/span&gt;
&lt;span class="n"&gt;Step&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;3.6&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;slim&lt;/span&gt;
&lt;span class="mf"&gt;3.6&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;slim&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Pulling&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;library&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="n"&gt;f2aa67a397c4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Pull&lt;/span&gt; &lt;span class="n"&gt;complete&lt;/span&gt; 
&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="n"&gt;cc085bc22b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Pull&lt;/span&gt; &lt;span class="n"&gt;complete&lt;/span&gt; 
&lt;span class="mi"&gt;83&lt;/span&gt;&lt;span class="n"&gt;bd7790bc68&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Pull&lt;/span&gt; &lt;span class="n"&gt;complete&lt;/span&gt; 
&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="n"&gt;b3329adba1b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Pull&lt;/span&gt; &lt;span class="n"&gt;complete&lt;/span&gt; 
&lt;span class="n"&gt;d0a8fd6eb5d0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Pull&lt;/span&gt; &lt;span class="n"&gt;complete&lt;/span&gt; 
&lt;span class="n"&gt;Digest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;56100&lt;/span&gt;&lt;span class="n"&gt;f5b5e299f4488f51ea81cc1a67b5ff13ee2f926280eaf8e527a881afa61&lt;/span&gt;
&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Downloaded&lt;/span&gt; &lt;span class="n"&gt;newer&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;3.6&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;slim&lt;/span&gt;
 &lt;span class="o"&gt;---&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="n"&gt;ea9c0b39c6&lt;/span&gt;
&lt;span class="n"&gt;Step&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;WORKDIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;
&lt;span class="n"&gt;Removing&lt;/span&gt; &lt;span class="n"&gt;intermediate&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt; &lt;span class="mi"&gt;627538&lt;/span&gt;&lt;span class="n"&gt;eb0d39&lt;/span&gt;
 &lt;span class="o"&gt;---&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;26360255&lt;/span&gt;&lt;span class="n"&gt;c163&lt;/span&gt;
&lt;span class="n"&gt;Step&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ADD&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;
 &lt;span class="o"&gt;---&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;9658&lt;/span&gt;&lt;span class="n"&gt;b91b29db&lt;/span&gt;
&lt;span class="n"&gt;Step&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RUN&lt;/span&gt; &lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;trusted&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="n"&gt;pypi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="n"&gt;requirements&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt;
 &lt;span class="o"&gt;---&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Running&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;f0d0969f3066&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;bottle&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mf"&gt;0.12&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="n"&gt;requirements&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;Downloading&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bd&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;04&lt;/span&gt;&lt;span class="n"&gt;dc59ced52a8261ee0f965a8968717a255ea84a36013e527944dbf3468c&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bottle&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.12&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;13.&lt;/span&gt;&lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gz&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="n"&gt;kB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Building&lt;/span&gt; &lt;span class="n"&gt;wheels&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;collected&lt;/span&gt; &lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bottle&lt;/span&gt;
  &lt;span class="n"&gt;Running&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="n"&gt;bdist_wheel&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;bottle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;started&lt;/span&gt;
  &lt;span class="n"&gt;Running&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="n"&gt;bdist_wheel&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;bottle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;finished&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;done&amp;#39;&lt;/span&gt;
  &lt;span class="n"&gt;Stored&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;/.&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pip&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;wheels&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;76&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;a0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;b4&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;a3ee1a32d0506931e558530258de1cc04b628eff1b2f008e0&lt;/span&gt;
&lt;span class="n"&gt;Successfully&lt;/span&gt; &lt;span class="n"&gt;built&lt;/span&gt; &lt;span class="n"&gt;bottle&lt;/span&gt;
&lt;span class="n"&gt;Installing&lt;/span&gt; &lt;span class="n"&gt;collected&lt;/span&gt; &lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bottle&lt;/span&gt;
&lt;span class="n"&gt;Successfully&lt;/span&gt; &lt;span class="n"&gt;installed&lt;/span&gt; &lt;span class="n"&gt;bottle&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.12&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;
&lt;span class="n"&gt;Removing&lt;/span&gt; &lt;span class="n"&gt;intermediate&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt; &lt;span class="n"&gt;f0d0969f3066&lt;/span&gt;
 &lt;span class="o"&gt;---&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0534575&lt;/span&gt;&lt;span class="n"&gt;c8067&lt;/span&gt;
&lt;span class="n"&gt;Step&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EXPOSE&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
 &lt;span class="o"&gt;---&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Running&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="mf"&gt;14e49938&lt;/span&gt;&lt;span class="n"&gt;d3be&lt;/span&gt;
&lt;span class="n"&gt;Removing&lt;/span&gt; &lt;span class="n"&gt;intermediate&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt; &lt;span class="mf"&gt;14e49938&lt;/span&gt;&lt;span class="n"&gt;d3be&lt;/span&gt;
 &lt;span class="o"&gt;---&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;05e087&lt;/span&gt;&lt;span class="n"&gt;d2471d&lt;/span&gt;
&lt;span class="n"&gt;Step&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CMD&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;python&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;app.py&amp;quot;&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;Running&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ca9738bfd06a&lt;/span&gt;
&lt;span class="n"&gt;Removing&lt;/span&gt; &lt;span class="n"&gt;intermediate&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt; &lt;span class="n"&gt;ca9738bfd06a&lt;/span&gt;
 &lt;span class="o"&gt;---&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="n"&gt;afb4f01e0d3&lt;/span&gt;
&lt;span class="n"&gt;Successfully&lt;/span&gt; &lt;span class="n"&gt;built&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="n"&gt;afb4f01e0d3&lt;/span&gt;
&lt;span class="n"&gt;Successfully&lt;/span&gt; &lt;span class="n"&gt;tagged&lt;/span&gt; &lt;span class="n"&gt;bottledock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;latest&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We can also see the image with the &lt;code&gt;docker image ls&lt;/code&gt; command. Give that 
a try now:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;docker image ls
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Our tag name should appear in the images list:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
bottledock          latest              9afb4f01e0d3        About a minute ago   145MB
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Our image is ready to load as a container so we can code a short
Bottle web app for testing and then further development.&lt;/p&gt;
&lt;h2&gt;Coding A Bottle Web App&lt;/h2&gt;
&lt;p&gt;It is time to code a simple "Hello, World!"-style Bottle app to test
running Python code within our Docker container. Within the current
project directory, create a file named &lt;code&gt;app.py&lt;/code&gt; with the following contents:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;bottle&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;bottle&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;


&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bottle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_app&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="nd"&gt;@route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&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;hello_world&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Hello, world! (From Full Stack Python)&amp;quot;&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0.0.0.0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reloader&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The above code returns a simple "Hello, world!" message when
executed by the Bottle development server and contacted by a client.&lt;/p&gt;
&lt;p&gt;We need just one more file to specify our &lt;code&gt;bottle&lt;/code&gt; dependency. Create 
a &lt;code&gt;requirements.txt&lt;/code&gt; file within the same directory as &lt;code&gt;app.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;bottle==0.12.13
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Make sure both the &lt;code&gt;app.py&lt;/code&gt; and &lt;code&gt;requirements.txt&lt;/code&gt; file are saved then
we can give the code a try.&lt;/p&gt;
&lt;h2&gt;Running the Container&lt;/h2&gt;
&lt;p&gt;Now that we have our image in hand along with the Python code in a file 
we can run the image as a container with the &lt;code&gt;docker run&lt;/code&gt; command. Execute 
the following command, making sure to replace the absolute path for the 
volume to your own directory.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;docker run -p 5000:8080 --volume=/Users/matt/devel/py/blog-code-examples/docker-bottle-macapp bottledock
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If you receive the error 
&lt;code&gt;python: can't open file 'app.py': [Errno 2] No such file or directory&lt;/code&gt; then
you likely did not change &lt;code&gt;/Users/matt/devel/py/bottledocker&lt;/code&gt; to the 
directory where your project files, especially &lt;code&gt;app.py&lt;/code&gt;, are located.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180604-bottle-docker/bottle-app-response.png" width="100%" 
 class="shot rnd" alt="Bottle web app responding to requests from within a Docker container."&gt;&lt;/p&gt;
&lt;p&gt;Everything worked when you see a simple text-based HTTP response like what
is shown above in the screenshot of my Chrome browser.&lt;/p&gt;
&lt;h2&gt;What's Next?&lt;/h2&gt;
&lt;p&gt;We just installed Docker and wrote a Bottle web app to run inside a 
container. That is just the beginning of how you can integrate Docker into 
your workflow.&lt;/p&gt;
&lt;p&gt;Next up take a look at the &lt;a href="/bottle.html"&gt;Bottle&lt;/a&gt;, &lt;a href="/docker.html"&gt;Docker&lt;/a&gt; 
and &lt;a href="/deployment.html"&gt;deployment&lt;/a&gt; pages for more tutorials.&lt;/p&gt;
&lt;p&gt;Questions? Let me know via a GitHub
&lt;a href="https://github.com/mattmakai/fullstackpython.com/issues"&gt;issue ticket on the Full Stack Python repository&lt;/a&gt;, 
on Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Do you see a typo, syntax issue or just something that's confusing in this 
blog post? Fork
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/180604-bottle-docker-macos.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request with a fix or 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/issues"&gt;file an issue ticket on GitHub&lt;/a&gt;.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Tue, 05 Jun 2018 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2018-06-04:blog/first-steps-bottle-web-apps-docker-containers.html</guid></item><item><title>How to Explain Your Products to Developers</title><link>https://www.fullstackpython.com/blog/explain-products-developers.html</link><description>&lt;p&gt;This blog post contains the slides along with a loose transcript
from my talk on appropriately marketing products to 
software developers that I gave at
&lt;a href="https://www.svb.com/"&gt;Silicon Valley Bank&lt;/a&gt; during 
&lt;a href="http://www.ubiquity.vc/"&gt;Ubiquity.VC&lt;/a&gt;'s summit for founders, investors and 
technical advisors on May 24, 2018.&lt;/p&gt;
&lt;hr /&gt;
&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/180526-explain-product/01-explain-products.jpg" width="100%" class="shot rnd outl" alt="Title slide for this talk on Explaining Products to Developers."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
Hey folks, my name is &lt;a href="/about-author.html"&gt;Matt Makai&lt;/a&gt;. I serve the
&lt;a href="https://www.youtube.com/watch?v=TF129ioe8kc"&gt;Developer Network&lt;/a&gt; at
&lt;a href="https://www.twilio.com/"&gt;Twilio&lt;/a&gt;. We've talked a lot today about 
making the real, physical world programmable. We ask "what if we could modify 
the world using GitHub and Jira?" When we succeed in creating programmatic 
access to the physical world, what then? Is that the end goal?
&lt;/p&gt;&lt;p&gt;
No, that's only the beginning. We need developers to use those new 
capabilities and code with them. 
&lt;/p&gt;&lt;p&gt;
How do you get developers to adopt what you are creating? That is a broad 
question so I am going to zoom in on just one small slice of developer 
relations that kicks off the whole adoption process. Unfortunately I 
see upwards of 90% of companies completely screw up explaining their 
products to developers.
&lt;/p&gt;&lt;p&gt;
Today we are going to look at how to appropriately explain and demo your 
product to developers to maximize developer adoption. This is the first
step towards getting a developer to care enough to try out what you have 
built.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/180526-explain-product/02-matt-makai-bio.jpg" width="100%" class="shot rnd outl" alt="Bio information slide for Matt Makai."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
In addition to serving the Developer Network at Twilio, I am also a 
&lt;a href="https://www.python.org/"&gt;Python&lt;/a&gt; and 
&lt;a href="https://swift.org/"&gt;Swift&lt;/a&gt; developer as well as the creator of 
&lt;a href="https://www.fullstackpython.com/"&gt;Full Stack Python&lt;/a&gt;. My 
background provides me an opportunity to give insight on this topic because 
I am a software developer, I market to fellow software developers and I write a 
community-driven site that is widely read and trusted by software developers.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/180526-explain-product/03-show-it-live.jpg" width="100%" class="shot rnd outl" alt="Fred Wilson quote on showing rather than telling."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
How do you explain your product? &lt;a href="https://avc.com/"&gt;Fred Wilson&lt;/a&gt; 
of &lt;a href="https://www.usv.com/"&gt;Union Square Ventures&lt;/a&gt; said it best in 
this quote, which we will roughly summarize as: 
&lt;em&gt;show, don't just tell&lt;/em&gt;.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/180526-explain-product/04-demo.jpg" width="100%" class="shot rnd outl" alt="Demo slide for transitioning into a live-coded demo."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
With Fred Wilson's quote in mind, it's demo time! 
&lt;/p&gt;&lt;p&gt;
(This is where I do a condensed, approximately two minute version of my 
Twilio five minute live-coded demo. For a rough approximation of what I 
showed, check out the 
&lt;a href="https://www.youtube.com/watch?v=-VuXIgp9S7o"&gt;NY Tech Meetup Twilio demo&lt;/a&gt;
from 2010.)
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/180526-explain-product/05-phone-api.jpg" width="100%" class="shot rnd outl" alt="Twilio 2008-2011 had only the phone calling voice API."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
That demo represented the Twilio 5 minute demo from 2008 through part of 
2011, when the 
&lt;a href="https://www.twilio.com/docs/voice/api"&gt;phone calling voice API&lt;/a&gt; 
was the company's main product.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/180526-explain-product/06-story-arc.jpg" width="100%" class="shot rnd outl" alt="Story arc visual."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
Let's break down the demo into its component pieces so we can learn from
it. The demo narrative fits into a story arc. Yes, a story arc like from a
novel. You may not have thought about explaining and showing your product
in a couple of minutes to be similiar to a novel, but you should follow the 
same narrative structure because it is easier for the audience to understand.
&lt;p&gt;&lt;/p&gt;
The demo we just saw follows the story arc in the beginning when I introduce 
myself and Twilio. A clear, concise set of intentional words are used to 
explain what Twilio *can do for a developer*. "Twilio makes it easy for 
software developers to add phone calling to applications using the 
programming languages that you already know." Breaking that down further:
&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;software developers&lt;/strong&gt;: a clear call out to who we are 
talking to&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;phone calling&lt;/strong&gt;: what problem we solve by adding this 
feature to applications&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;programming languages that you already know&lt;/strong&gt;: emphasizing
that you do not have to learn some complicated proprietary syntax from the 
telecommunications world&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;
Next in the exposition, we explain how it works 
&lt;a href="https://s3.amazonaws.com/com.twilio.prod.twilio-docs/images/incoming-voice.width-800.png"&gt;using a diagram&lt;/a&gt;
that shows inbound and outbound phone calls and how they interact with 
Twilio's service as well as your &lt;a href="/web-servers.html"&gt;web server&lt;/a&gt;.
&lt;/p&gt;&lt;p&gt;
The inciting incident during the demo  happens when I finish the 
explanation of how Twilio works and say "rather than just show you a
little diagram, let's build an application together right now".
&lt;/p&gt;&lt;p&gt;
We move into the demo phase where I buy and configure a phone number then
we all test it by calling the number on our own cell phones. The audience 
learns that to configure the phone number to do something useful in this
case only requires two XML elements that can be stored in a static file or 
generated by an endpoint in their application.
&lt;/p&gt;&lt;p&gt;
The climax hits when we see outbound phone calling, everyone's
phones in the room start ringing and we are all on speaker phone together.
Finally, there is a short resolution where I re-explain what Twilio can
do for developers and outro with my name and where you can find me.
&lt;/p&gt;&lt;p&gt;
The whole two minute demo, or however long we need it to be, has a narrative
with a clear story arc.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/180526-explain-product/07-sms-2011.jpg" width="100%" class="shot rnd outl" alt="Twilio added an SMS API in 2011."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
In 2011, Twilio added SMS. This changed the 5 minute demo's explanation
to "Twilio makes it easy for developers to send and receive text messages
and make and receive phone calls using the programming languages that they
already know". The overall structure otherwise remained the same because we 
used SMS for inbound action and kept phone calling for the outbound action. 
&lt;/p&gt;&lt;p&gt;
Eventually your product line or features within a product line will reach
a point where you need to determine if it changes your explanation and
demo. In some cases there will be modifications that fit within the existing
framework and do not substantially change the narrative.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/180526-explain-product/08-more-products.jpg" width="100%" class="shot rnd outl" alt="Eventually you expand your product lines or features within a product."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
As you continue to grow you will eventually reach an inflection point where
you have too many products or features to explain, regardless of how
much time you have for your demo. You reach a situation where if you try to 
tell the audience everything that your product does, they will zone out
and ignore your laundry list of features.
&lt;/p&gt;&lt;p&gt;
If you are not intentional in the words you say and specific
in the products and features you choose to show, then your pitch becomes 
spread too thin and no developer will care to listen.
&lt;/p&gt;&lt;p&gt;
Twilio now has dozens of products under the communications umbrella. I talk
about specific products and tailor my explanation based on the audience. You
should too! For example, if I am talking to a group of web developers, I will
still use the classic Twilio 5 minute demo that shows off SMS and phone 
calling capability. On the other hand, if I am demoing to iOS and Android 
mobile developers then I will show off 
&lt;a href="https://www.twilio.com/docs/chat"&gt;Programmable Chat&lt;/a&gt; or 
&lt;a href="https://www.twilio.com/docs/video"&gt;Programmable Video&lt;/a&gt;.
&lt;/p&gt;&lt;p&gt;
The explanation is tuned to "Twilio makes it easy for developers to add 
communications, such as phone calling, messaging and video, to their 
applications using the programming languages that they already know." I 
draw a broad theme by saying the word "communications" then give three
specific examples of products that are the most widely used by developers
because they are incredibly useful for implementing common application
features.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/180526-explain-product/09-devangelism.jpg" width="100%" class="shot rnd outl" alt="You as a founder, or as an investor who work with founders must be the chief evangelist for your product."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
It's time to reinforce why it is so important for you, as a founder, or as an
investor that works with founders, to be the chief evangelist for your
product. You cannot ever outsource this role. You cannot hire someone to
lead an evangelism team and expect them to figure it all out for you. 
&lt;/p&gt;&lt;p&gt;
If you are not excited about the product you are building or are unable 
to transfer that excitement to developers with a clear explanation and demo, 
then all of the other priorities for your company become useless. If 
developers are your customers and they do not adopt your product then you
will not sell anything, you won't be able to set a great company culture
and you won't need to worry about what snacks are stocked in your office's
kitchen. If developers are the lifeblood of your company then you need to
be the chief evangelist, period.
&lt;/p&gt;&lt;p&gt;
Here are a few more important points for how to perform this role 
effectively.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/180526-explain-product/10-be-specific.jpg" width="100%" class="shot rnd outl" alt="The earlier you are, the more specific you need to be about what problem you solve."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
When you are early stage, be as specific as possible about what problem
that you are solving. You are &lt;strong&gt;not&lt;/strong&gt; "disrupting 
transportation by blah blah blah". No developer gives a shit. They want to 
know what problem you will solve for them right now.
&lt;/p&gt;&lt;p&gt;
Be specific, like the "add phone calling to your applications" line so that
it is absolutely clear what you do.
&lt;/p&gt;&lt;p&gt;
When your company grows and your brand expands, then you may expand to 
include the general industry your company works in, such as "communications".
Do not jump the gun in trying to become too grandiose with your ambitions 
because your developer audience cares about what problem you are solving for
them, not who you imagine yourself to be in your future vision.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/180526-explain-product/11-refine-growth.jpg" width="100%" class="shot rnd outl" alt="Refine the message as you grow, or your industry grows."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
Refine the explanation you use and the demo under two situations. 
&lt;/p&gt;&lt;p&gt;
First, when your products and features expand. Think critically if a new 
feature should be part of your explanation or it can be left as an answer 
to follow up questions that a developer asks you. 
&lt;/p&gt;&lt;p&gt;
Second, developer ecosystems are constantly changing. If you tried to talk
to me about containers ten years ago and I was not a Solaris sysadmin then
I would not have any clue what you are talking about. Today, it's generally
safe to assume most Bay Area developers have a working knowledge of what
containers are and what they are useful for accomplishing. Use that type of
context in your pitch to reinforce your technical credibility with your
developer audience.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/180526-explain-product/12-rehearsal.jpg" width="100%" class="shot rnd outl" alt="No demo fails because you rehearse constantly."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
I get asked a lot about live coding because everyone is worried about demo
fails. You should not demo fail, ever. To steal a line from 
&lt;a href="https://github.com/RobSpectre"&gt;Rob Spectre&lt;/a&gt;, former head of the 
Twilio Developer Network:&lt;/p&gt;

&lt;blockquote&gt;There is only one demo God, and her name is rehearsal.&lt;/blockquote&gt;

&lt;p&gt;
You do not just rehearse and practice the happy path, you also practice
what can go wrong. What happens when you mistype a character in your code?
Find out and get used to it. If and when it happens during your live demo 
then you can incorporate that mistake into your narrative as a learning
opportunity for the audience.
&lt;/p&gt;&lt;p&gt;
Magicians always have "outs" in their acts, essentially plan B, plan C, 
plan Z. You should too because something will always go wrong but if you
are ready for it and know how to handle it then you will never demo fail.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/180526-explain-product/13-your-message-to-devs.jpg" width="100%" class="shot rnd outl" alt="Your message to developers."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
That sums up my strong recommendations for this one small slice of the
field of developer product adoption. To re-iterate, create a narrative 
for your explanation and demo that follows the classic story arc. The 
earlier you are as a product, the more specific your explanation should
be in what problem you solve. Refine the message as your features and
product lines grow, as well as when the industry around you changes. 
Rehearse your demo including what can and will go wrong.
&lt;/p&gt;&lt;p&gt;
There is a lot more to developer adoption than a good explanation and
demo, but I see greater than 90% of companies never even get to this
point so you will be way ahead of the pack if you heed the advice from
this talk.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class="row talk"&gt;&lt;div class="c6"&gt;
&lt;img src="/img/180526-explain-product/14-thank-you.jpg" width="100%" class="shot rnd outl" alt="Thank you slide."&gt;
&lt;/div&gt;&lt;div class="c6"&gt;&lt;p&gt;
That's all for today. My name is &lt;a href="/about-author.html"&gt;Matt Makai&lt;/a&gt;,
I am a software developer at &lt;a href="/twilio.html"&gt;Twilio&lt;/a&gt; and the
author of &lt;a href="https://www.fullstackpython.com/"&gt;Full Stack Python&lt;/a&gt;.
Thank you very much.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Fri, 25 May 2018 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2018-05-25:blog/explain-products-developers.html</guid></item><item><title>How to Add Maps to Django Web App Projects with Mapbox</title><link>https://www.fullstackpython.com/blog/maps-django-web-applications-projects-mapbox.html</link><description>&lt;p&gt;Building interactive maps into a &lt;a href="/django.html"&gt;Django&lt;/a&gt; web application
can seem daunting if you do not know where to begin, but it is easier
than you think if you use a developer tool such as 
&lt;a href="https://www.mapbox.com/"&gt;Mapbox&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In this post we will build a simple Django project with a single app
and add an interactive map like the one you see below to the webpage that
Django renders with the &lt;a href="https://www.mapbox.com/maps/"&gt;Mapbox Maps&lt;/a&gt; 
&lt;a href="/application-programming-interfaces.html"&gt;API&lt;/a&gt;.&lt;/p&gt;
&lt;div id='map' width="100%" style='height:280px;margin-bottom:16px' class="shot rnd outl"&gt;&lt;/div&gt;

&lt;script&gt;
mapboxgl.accessToken = 'pk.eyJ1IjoibWF0dG1ha2FpIiwiYSI6ImNqZzU0OXNtYjIzdmIyeHA5OG1sNnhid2YifQ.kzJityumPUk9f9i1vkmWAg';
var map = new mapboxgl.Map({
 container: 'map',
 style: 'mapbox://styles/mapbox/streets-v10',
 center: [-77.03, 38.91],
 zoom: 9
});
&lt;/script&gt;

&lt;h2&gt;Our Tools&lt;/h2&gt;
&lt;p&gt;&lt;a href="/python-2-or-3.html"&gt;Python 3&lt;/a&gt; is strongly recommended for this tutorial
because Python 2 will no longer be supported starting January 1, 2020.
&lt;a href="https://www.python.org/downloads/release/python-365/"&gt;Python 3.6.5&lt;/a&gt; to 
was used to build this tutorial. We will also use the following 
&lt;a href="/application-dependencies.html"&gt;application dependencies&lt;/a&gt; to build
our application:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/django.html"&gt;Django&lt;/a&gt; web framework, 
  &lt;a href="https://docs.djangoproject.com/en/2.0/"&gt;version 2.0.5&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pip.pypa.io/en/stable/"&gt;pip&lt;/a&gt; and 
  &lt;a href="https://virtualenv.pypa.io/en/latest/"&gt;virtualenv&lt;/a&gt;, which come installed 
  with Python 3, to install and isolate the Django library 
  from your other applications&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://www.mapbox.com/"&gt;free Mapbox account&lt;/a&gt; to interact with their
  &lt;a href="/application-programming-interfaces.html"&gt;web API&lt;/a&gt; using 
  &lt;a href="/javascript.html"&gt;JavaScript&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you need help getting your 
&lt;a href="/development-environments.html"&gt;development environment&lt;/a&gt; configured
before running this code, take a look at
&lt;a href="/blog/python-3-django-gunicorn-ubuntu-1604-xenial-xerus.html"&gt;this guide for setting up Python 3 and Django on Ubuntu 16.04 LTS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This blog post's code is also available on GitHub within the
&lt;a href="https://github.com/fullstackpython/blog-code-examples"&gt;maps-django-mapbox directory of the blog-code-examples repository&lt;/a&gt;. 
Take the code and use it for your own purposes because it is all
provided under the MIT open source license.&lt;/p&gt;
&lt;h2&gt;Installing Dependencies&lt;/h2&gt;
&lt;p&gt;Start the Django project by creating a new 
&lt;a href="/virtual-environments-virtualenvs-venvs.html"&gt;virtual environment&lt;/a&gt; 
using the following command. I recommend using a separate directory 
such as &lt;code&gt;~/venvs/&lt;/code&gt; (the tilde is a shortcut for your user's &lt;code&gt;home&lt;/code&gt; 
directory) so that you always know where all your virtualenvs are 
located.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python3 -m venv djangomaps
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Activate the virtualenv with the &lt;code&gt;activate&lt;/code&gt; shell script:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt; djangomaps/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The command prompt will change after activating the virtualenv:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180519-django-maps/virtualenv.jpg" width="100%" class="shot rnd outl" alt="Activate your djangomaps virtualenv."&gt;&lt;/p&gt;
&lt;p&gt;Remember that you have to activate your virtualenv in every new terminal 
window where you want to use dependencies in the virtualenv.&lt;/p&gt;
&lt;p&gt;We can now install the &lt;a href="https://pypi.org/project/Django/2.0.5"&gt;Django&lt;/a&gt; 
package into the activated but otherwise empty virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install django==2.0.5
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Look for the following output to confirm Django installed
correctly from PyPI.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;  &lt;span class="n"&gt;Downloading&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;91&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;2245462e57798&lt;/span&gt;&lt;span class="n"&gt;e9251de87c88b2b8f996d10ddcb68206a8a020561ef7bd3&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Django&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;7.1&lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;████████████████████████████████&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mf"&gt;7.1&lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt; &lt;span class="mi"&gt;231&lt;/span&gt;&lt;span class="n"&gt;kB&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; 
      &lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;pytz&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pythonhosted&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dc&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;83&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="n"&gt;f7833b70d3e067ca91467ca245bae0f6fe56ddc7451aa0dc5606b120f2&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pytz&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2018.4&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
        &lt;span class="n"&gt;Installing&lt;/span&gt; &lt;span class="n"&gt;collected&lt;/span&gt; &lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pytz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;
        &lt;span class="n"&gt;Successfully&lt;/span&gt; &lt;span class="n"&gt;installed&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="n"&gt;pytz&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2018.4&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The Django dependency is ready to go so now we can create our project
and add some awesome maps to the application.&lt;/p&gt;
&lt;h2&gt;Building Our Django Project&lt;/h2&gt;
&lt;p&gt;We can use the &lt;a href="/django.html"&gt;Django&lt;/a&gt; &lt;code&gt;django-admin.py&lt;/code&gt; tool to create
the boilerplate code structure to get our project started.
Change into the directory where you develop your applications. For 
example, I typically use &lt;code&gt;/Users/matt/devel/py/&lt;/code&gt;. Then run the following 
command to start a Django project named &lt;code&gt;djmaps&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;django-admin.py startproject djmaps
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;django-admin.py&lt;/code&gt; command will create a directory named &lt;code&gt;djmaps&lt;/code&gt; along
with several subdirectories that you should be familiar with if you have 
previously worked with Django.&lt;/p&gt;
&lt;p&gt;Change directories into the new project.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;cd djmaps
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Create a new Django app within &lt;code&gt;djmaps&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python manage.py startapp maps
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Django will generate a new folder named &lt;code&gt;maps&lt;/code&gt; for the project.
We should update the URLs so the app is accessible before we write
our &lt;code&gt;views.py&lt;/code&gt; code.&lt;/p&gt;
&lt;p&gt;Open &lt;code&gt;djmaps/djmaps/urls.py&lt;/code&gt;. Add the highlighted lines so that URLs
will check the &lt;code&gt;maps&lt;/code&gt; app for appropriate URL matching.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; (comments)&lt;/span&gt;
&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;include&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;


&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;maps.urls&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;admin/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Save &lt;code&gt;djmaps/djmaps/urls.py&lt;/code&gt; and open &lt;code&gt;djmaps/djmaps/settings.py&lt;/code&gt;.
Add the &lt;code&gt;maps&lt;/code&gt; app to &lt;code&gt;settings.py&lt;/code&gt; by inserting the highlighted line:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Application definition&lt;/span&gt;

&lt;span class="n"&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; 
    &lt;span class="s1"&gt;&amp;#39;django.contrib.admin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.auth&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.contenttypes&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.sessions&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.messages&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.staticfiles&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="s1"&gt;&amp;#39;maps&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Make sure you change the default &lt;code&gt;DEBUG&lt;/code&gt; and &lt;code&gt;SECRET_KEY&lt;/code&gt; 
values in &lt;code&gt;settings.py&lt;/code&gt; before you deploy any code to production. Secure 
your app properly with the information from the Django
&lt;a href="https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/"&gt;production deployment checklist&lt;/a&gt; 
so that you do not add your project to the list of hacked applications
on the web.&lt;/p&gt;
&lt;p&gt;Save and close &lt;code&gt;settings.py&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next change into the &lt;code&gt;djmaps/maps&lt;/code&gt; directory. Create a new file named 
&lt;code&gt;urls.py&lt;/code&gt; to contain routes for the &lt;code&gt;maps&lt;/code&gt; app.&lt;/p&gt;
&lt;p&gt;Add these lines to the empty &lt;code&gt;djmaps/maps/urls.py&lt;/code&gt; file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;                                                                                                                              
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&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="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_map&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="s2"&gt;&amp;quot;default&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Save &lt;code&gt;djmaps/maps/urls.py&lt;/code&gt; and open &lt;code&gt;djmaps/maps/views.py&lt;/code&gt; add the
following two highlighted lines. You can keep the boilerplate comment or 
delete it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.shortcuts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;


&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;default_map&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;~~&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;render&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="s1"&gt;&amp;#39;default.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Next, create a directory for your template files named &lt;code&gt;templates&lt;/code&gt; under
the &lt;code&gt;djmaps/maps&lt;/code&gt; app directory.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;mkdir templates
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Create a new file named &lt;code&gt;default.html&lt;/code&gt; within &lt;code&gt;djmaps/maps/templates&lt;/code&gt; 
that contains the following &lt;a href="/django-templates.html"&gt;Django template&lt;/a&gt; markup.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Interactive maps for Django web apps&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
   &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Map time!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We can test out this static page to make sure all of our code is
correct, then we'll use Mapbox to embed a customizable map within
the page. Change into the base directory of your Django project
where the &lt;code&gt;manage.py&lt;/code&gt; file is located. Execute the development
server with the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python manage.py runserver
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The Django development server will start up with no issues other than an 
unapplied migrations warning.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;Performing&lt;/span&gt; &lt;span class="nv"&gt;system&lt;/span&gt; &lt;span class="nv"&gt;checks&lt;/span&gt;...

&lt;span class="nv"&gt;System&lt;/span&gt; &lt;span class="nv"&gt;check&lt;/span&gt; &lt;span class="nv"&gt;identified&lt;/span&gt; &lt;span class="nv"&gt;no&lt;/span&gt; &lt;span class="nv"&gt;issues&lt;/span&gt; &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="nv"&gt;silenced&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;.

&lt;span class="nv"&gt;You&lt;/span&gt; &lt;span class="nv"&gt;have&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt; &lt;span class="nv"&gt;unapplied&lt;/span&gt; &lt;span class="nv"&gt;migration&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;s&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;. &lt;span class="nv"&gt;Your&lt;/span&gt; &lt;span class="nv"&gt;project&lt;/span&gt; &lt;span class="nv"&gt;may&lt;/span&gt; &lt;span class="nv"&gt;not&lt;/span&gt; &lt;span class="nv"&gt;work&lt;/span&gt; &lt;span class="nv"&gt;properly&lt;/span&gt; &lt;span class="k"&gt;until&lt;/span&gt; &lt;span class="nv"&gt;you&lt;/span&gt; &lt;span class="nv"&gt;apply&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;migrations&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;s&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;: &lt;span class="nv"&gt;admin&lt;/span&gt;, &lt;span class="nv"&gt;auth&lt;/span&gt;, &lt;span class="nv"&gt;contenttypes&lt;/span&gt;, &lt;span class="nv"&gt;sessions&lt;/span&gt;.
&lt;span class="nv"&gt;Run&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;python manage.py migrate&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;apply&lt;/span&gt; &lt;span class="nv"&gt;them&lt;/span&gt;.

&lt;span class="nv"&gt;May&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;, &lt;span class="mi"&gt;2018&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;:&lt;span class="mi"&gt;47&lt;/span&gt;:&lt;span class="mi"&gt;54&lt;/span&gt;
&lt;span class="nv"&gt;Django&lt;/span&gt; &lt;span class="nv"&gt;version&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;.&lt;span class="mi"&gt;0&lt;/span&gt;.&lt;span class="mi"&gt;5&lt;/span&gt;, &lt;span class="nv"&gt;using&lt;/span&gt; &lt;span class="nv"&gt;settings&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;djmaps.settings&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;
&lt;span class="nv"&gt;Starting&lt;/span&gt; &lt;span class="nv"&gt;development&lt;/span&gt; &lt;span class="nv"&gt;server&lt;/span&gt; &lt;span class="nv"&gt;at&lt;/span&gt; &lt;span class="nv"&gt;http&lt;/span&gt;:&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="mi"&gt;127&lt;/span&gt;.&lt;span class="mi"&gt;0&lt;/span&gt;.&lt;span class="mi"&gt;0&lt;/span&gt;.&lt;span class="mi"&gt;1&lt;/span&gt;:&lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="nv"&gt;Quit&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;server&lt;/span&gt; &lt;span class="nv"&gt;with&lt;/span&gt; &lt;span class="nv"&gt;CONTROL&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;C&lt;/span&gt;.
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Open a web browser and go to &lt;code&gt;localhost:8000&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180519-django-maps/map-time.png" width="100%" class="shot rnd outl" alt="Plain old HTML page."&gt;&lt;/p&gt;
&lt;p&gt;Our code works, but boy is that a plain-looking HTML page. Let's make the
magic happen by adding JavaScript to the template to generate maps.&lt;/p&gt;
&lt;h2&gt;Adding Maps with Mapbox&lt;/h2&gt;
&lt;p&gt;Head to &lt;a href="https://www.mapbox.com/"&gt;mapbox.com&lt;/a&gt; in your web browser to
access the Mapbox homepage.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180519-django-maps/mapbox-homepage.jpg" width="100%" class="shot rnd outl" alt="Mapbox homepage."&gt;&lt;/p&gt;
&lt;p&gt;Click on "Get Started" or "Get Started for free" (the text depends on whether
or not you already have a Mapbox account).&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180519-django-maps/sign-up.jpg" width="100%" class="shot rnd outl" alt="Sign up for a Mapbox account."&gt;&lt;/p&gt;
&lt;p&gt;Sign up for a new free developer account or sign in to your existing 
account.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180519-django-maps/add-mapbox.png" width="100%" class="shot rnd outl" alt="Add Mapbox to your application."&gt;&lt;/p&gt;
&lt;p&gt;Click the "JS Web" option.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180519-django-maps/method-installation.png" width="100%" class="shot rnd outl" alt="Choose the method of installation."&gt;&lt;/p&gt;
&lt;p&gt;Choose "Use the Mapbox CDN" for the installation method. The next two screens
show some code that you should add to your &lt;code&gt;djmaps/maps/templates/default.html&lt;/code&gt; 
template file. The code will look like the following but you will need to 
replace the &lt;code&gt;mapboxgl.accessToken&lt;/code&gt; line with your own access token.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Interactive maps for Django web apps&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
~~    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;https://api.mapbox.com/mapbox-gl-js/v0.44.2/mapbox-gl.js&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
~~    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;https://api.mapbox.com/mapbox-gl-js/v0.44.2/mapbox-gl.css&amp;#39;&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;stylesheet&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
   &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Map time!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
~~   &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;map&amp;#39;&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;100%&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;height:400px&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
~~   &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;mapbox_access_token&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ow"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;     &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;map&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;     &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;mapbox://styles/mapbox/streets-v10&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;   &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Re-open &lt;code&gt;djmaps/maps/views.py&lt;/code&gt; to update the parameters passed into the
Django template. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.shortcuts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;default_map&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;~~&lt;/span&gt;    &lt;span class="c1"&gt;# TODO: move this token to Django settings from an environment variable&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="c1"&gt;# found in the Mapbox account settings and getting started instructions&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="c1"&gt;# see https://www.mapbox.com/account/ under the &amp;quot;Access tokens&amp;quot; section&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="n"&gt;mapbox_access_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pk.my_mapbox_access_token&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;render&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="s1"&gt;&amp;#39;default.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;span class="o"&gt;~~&lt;/span&gt;                  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;mapbox_access_token&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mapbox_access_token&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The Mapbox access token should really be stored in the Django settings
file, so we left a "TODO" note to handle that as a future step.&lt;/p&gt;
&lt;p&gt;Now we can try our webpage again. Refresh &lt;code&gt;localhost:8000&lt;/code&gt; in your
web browser.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180519-django-maps/map-time-with-map.jpg" width="100%" class="shot rnd outl" alt="Screenshot of the Mapbox map showing up in our Django front end."&gt;&lt;/p&gt;
&lt;p&gt;Sweet, we've got a live, interactive map! It's kind of weird thought how it
is zoomed out to view the entire world. Time to customize the map using
a few JavaScript parameters.&lt;/p&gt;
&lt;h2&gt;Customizing the Map&lt;/h2&gt;
&lt;p&gt;We can modify the map by changing parameters for the style, zoom level,
location and many other attributes.&lt;/p&gt;
&lt;p&gt;We'll start by changing the location that the initial map centers in
on as well as the zoom level.&lt;/p&gt;
&lt;p&gt;Re-open &lt;code&gt;djmaps/maps/templates/default.html&lt;/code&gt; and modify the first 
highlighted lines so it ends with a commas and add the two new 
highlighted lines shown below.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Interactive maps for Django web apps&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;https://api.mapbox.com/mapbox-gl-js/v0.44.2/mapbox-gl.js&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;https://api.mapbox.com/mapbox-gl-js/v0.44.2/mapbox-gl.css&amp;#39;&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;stylesheet&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
   &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Map time!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
   &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;map&amp;#39;&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;100%&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;height:400px&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
   &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;mapbox_access_token&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ow"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
     &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;map&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;     &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;mapbox://styles/mapbox/streets-v10&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;     &lt;span class="nx"&gt;center&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;77.03&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;38.91&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;     &lt;span class="nx"&gt;zoom&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;9&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
   &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The first number, -77.03, for the &lt;code&gt;center&lt;/code&gt; array is the longitude
and the second number, 38.91, is the latitude. Zoom level 9 is much
closer to the city than the default which was the entire world at
level 0. All of the customization values are listed in the 
&lt;a href="https://www.mapbox.com/mapbox-gl-js/api/"&gt;Mapbox GL JS API documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now refresh the page at &lt;code&gt;localhost:8000&lt;/code&gt; to reload our map.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180519-django-maps/map-updated-style-1.jpg" width="100%" class="shot rnd outl" alt="Updated map centered and zoomed in on Washington, D.C."&gt;&lt;/p&gt;
&lt;p&gt;Awesome, now we are zoomed in on Washington, D.C. and can still move
around to see more of the map. Let's make a couple other changes to
our map before wrapping up.&lt;/p&gt;
&lt;p&gt;Again back in &lt;code&gt;djmaps/maps/templates/default.html&lt;/code&gt; change the highlighted
line for the &lt;code&gt;style&lt;/code&gt; key to the &lt;code&gt;mapbox://styles/mapbox/satellite-streets-v10&lt;/code&gt;
value. That will change the look from an abstract map style to satellite
image data. Update &lt;code&gt;zoom: 9&lt;/code&gt; so that it has a comma at the end of the line
and add &lt;code&gt;bearing: 180&lt;/code&gt; as the last key-value pair in the configuration.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Interactive maps for Django web apps&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;https://api.mapbox.com/mapbox-gl-js/v0.44.2/mapbox-gl.js&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;https://api.mapbox.com/mapbox-gl-js/v0.44.2/mapbox-gl.css&amp;#39;&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;stylesheet&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
   &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Map time!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
   &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;map&amp;#39;&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;100%&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;height:400px&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
   &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;mapbox_access_token&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ow"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
     &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;map&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;     &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;mapbox://styles/mapbox/satellite-streets-v10&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;     &lt;span class="nx"&gt;center&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;77.03&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;38.91&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;     &lt;span class="nx"&gt;zoom&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;     &lt;span class="nx"&gt;bearing&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;180&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
   &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Save the template and refresh &lt;code&gt;localhost:8000&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180519-django-maps/map-updated-style-2.jpg" width="100%" class="shot rnd outl" alt="Updated map with satellite imagery and street map overlay."&gt;&lt;/p&gt;
&lt;p&gt;The map now provides a satellite view with streets overlay but it is
also... "upside down"! At least the map is upside down compared to how 
most maps are drawn, due to the &lt;code&gt;bearing: 180&lt;/code&gt; value, which modified
this map's rotation.&lt;/p&gt;
&lt;p&gt;Not bad for a few lines of JavaScript in our Django application.
Remember to check the 
&lt;a href="https://www.mapbox.com/mapbox-gl-js/api/"&gt;Mapbox GL JS API documentation&lt;/a&gt;
for the exhaustive list of parameters that you can adjust.&lt;/p&gt;
&lt;h2&gt;What's Next?&lt;/h2&gt;
&lt;p&gt;We just learned how to add interactive JavaScript-based maps to our 
&lt;a href="/django.html"&gt;Django&lt;/a&gt; web applications, as well as modify the look
and feel of the maps. Next try out some of the other APIs Mapbox
provides including: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.mapbox.com/api-documentation/#directions"&gt;directions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mapbox.com/api-documentation/#map-matching"&gt;map matching&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mapbox.com/api-documentation/#geocoding"&gt;geocoding&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Questions? Let me know via 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/issues"&gt;a GitHub issue ticket on the Full Stack Python repository&lt;/a&gt;, 
on Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Do you see a typo, syntax issue or wording that's confusing in this blog 
post? Fork
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/180519-django-maps-mapbox.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request with a fix or 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/issues"&gt;file an issue ticket on GitHub&lt;/a&gt;.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Sun, 06 Oct 2019 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2018-05-19:blog/maps-django-web-applications-projects-mapbox.html</guid></item><item><title>Full Stack Python at PyCon US 2018</title><link>https://www.fullstackpython.com/blog/full-stack-python-pycon-us-2018.html</link><description>&lt;p&gt;&lt;a href="https://us.pycon.org/2018/about/"&gt;PyCon US 2018&lt;/a&gt; kicked off today with the 
&lt;a href="https://us.pycon.org/2018/schedule/tutorials/"&gt;first day of tutorials&lt;/a&gt;. I am
flying in tomorrow and will be there through the end of the 
weekend. If you're around, come by either the 
&lt;a href="https://www.twilio.com/"&gt;Twilio booth&lt;/a&gt; or the community booth where the 
gang from &lt;a href="https://talkpython.fm/"&gt;Talk Python to Me&lt;/a&gt;, 
&lt;a href="https://realpython.com/"&gt;Real Python&lt;/a&gt;, &lt;a href="https://pybit.es/"&gt;PyBites&lt;/a&gt; and 
&lt;a href="http://testandcode.com/"&gt;Test &amp;amp; Code&lt;/a&gt; will be hanging out. I will be 
at one of those two spots when I am not watching talks! I'd love your 
feedback on what I can improve on 
&lt;a href="https://www.fullstackpython.com/"&gt;Full Stack Python&lt;/a&gt;. It's also great 
hearing your stories about how the site has helped you improve your 
development skills.&lt;/p&gt;
&lt;p&gt;For those folks who can't make it to PyCon, I'll be tweeting the best stuff 
that I see throughout the conference via 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;. Likewise, if I miss 
something let me know on Twitter or via email so we can highlight it.&lt;/p&gt;
&lt;p&gt;One quick update on the
&lt;a href="https://www.deploypython.com/"&gt;Full Stack Python Guide to Deployments book&lt;/a&gt;. 
I have a great update in the works that bumps to the latest versions of 
Ubuntu (now 18.04 LTS), Ansible 2.5.1 and Flask 1.0.2. It has been a long 
time coming and will be a free update to all existing purchasers. If you have
not bought the book yet, I recommend waiting until the update is out 
because the existing book's software versions are getting way too out of 
date to be useful to most projects.&lt;/p&gt;
&lt;p&gt;Got questions or comments about 
&lt;a href="https://www.fullstackpython.com/"&gt;Full Stack Python&lt;/a&gt;? Send me an email or 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/issues"&gt;submit an issue ticket on GitHub&lt;/a&gt; 
to let me know how to improve the site as I continue to fill in the 
&lt;a href="https://www.fullstackpython.com/table-of-contents.html"&gt;table of contents&lt;/a&gt; 
with &lt;a href="https://www.fullstackpython.com/change-log.html"&gt;new pages&lt;/a&gt; and 
&lt;a href="https://www.fullstackpython.com/blog.html"&gt;new tutorials&lt;/a&gt;.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Wed, 09 May 2018 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2018-05-09:blog/full-stack-python-pycon-us-2018.html</guid></item><item><title>Monitoring Python 3.6 Functions on AWS Lambda</title><link>https://www.fullstackpython.com/blog/monitor-python-3-6-example-code-aws-lambda-rollbar.html</link><description>&lt;p&gt;&lt;a href="/aws-lambda.html"&gt;Amazon Web Services (AWS) Lambda&lt;/a&gt; is a usage-based
execution environment that can run Python 3.6 code. If you have never
previously used AWS Lambda then you can read&lt;br /&gt;
&lt;a href="/blog/aws-lambda-python-3-6.html"&gt;How to Create Your First Python 3.6 AWS Lambda Function&lt;/a&gt;.
However, this tutorial will give you every step to follow even if you
are completely new to AWS.&lt;/p&gt;
&lt;p&gt;In this post we are going to monitor Python code that is running on AWS 
Lambda by using a hosted &lt;a href="/monitoring.html"&gt;monitoring&lt;/a&gt; service, 
&lt;a href="/rollbar.html"&gt;Rollbar&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Required Tools and Code&lt;/h2&gt;
&lt;p&gt;A local &lt;a href="/development-environments.html"&gt;development environment&lt;/a&gt; is not
required to follow this tutorial. All the work will happen in a web
browser through the &lt;a href="https://console.aws.amazon.com/console/"&gt;AWS Console&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The example code can be copy and pasted from this blog post or you
can access it on GitHub under the
&lt;a href="https://github.com/fullstackpython/blog-code-examples"&gt;Full Stack Python blog-post-examples&lt;/a&gt;
repository within the 
&lt;a href="https://github.com/fullstackpython/blog-code-examples/tree/master/aws-lambda-python-3-6"&gt;monitor-aws-lambda-python-3-6 directory&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Accessing the AWS Lambda Service&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://aws.amazon.com/console"&gt;Sign into your existing AWS account&lt;/a&gt; 
or sign up for a &lt;a href="https://aws.amazon.com/"&gt;new account&lt;/a&gt;. AWS Lambda
comes with a free tier so you can test code and execute basic 
applications without cost.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180420-monitor-aws-lambda/aws-amazon-com.jpg" width="100%" class="shot rnd outl" alt="AWS Lambda landing page."&gt;&lt;/p&gt;
&lt;p&gt;AWS has a boatload of services so use the search box to enter
"lambda" and select "Lambda" when it appears to get to the appropriate
starting page.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180420-monitor-aws-lambda/search-for-lambda.jpg" width="100%" class="shot rnd outl" alt="Search for lambda in the dashboard text box."&gt;&lt;/p&gt;
&lt;p&gt;Click the "Create function" button.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180420-monitor-aws-lambda/create-function.png" width="100%" class="shot rnd outl" alt="The create Lambda function screen."&gt;&lt;/p&gt;
&lt;p&gt;Select "Author from Scratch". Fill in a name so you can easily recognize this
function for future reference. I chose "monitorPython3". Select "Python 3.6"
for Runtime.&lt;/p&gt;
&lt;p&gt;Select "Create new role from template(s)", input a Role name, for example
"basicEdgeLambdaRole". For Policy templates choose "Basic Edge Lambda 
Permissions".&lt;/p&gt;
&lt;p&gt;Then click "Create function."&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180420-monitor-aws-lambda/monitorpython3.png" width="100%" class="shot rnd outl" alt="Blank AWS Lambda function named monitorPython3."&gt;&lt;/p&gt;
&lt;p&gt;Ok, finally we have arrived at the configuration screen where we can write
our code.&lt;/p&gt;
&lt;h2&gt;Coding a Python Function&lt;/h2&gt;
&lt;p&gt;Scroll down to the "Function code" user interface section.&lt;/p&gt;
&lt;p&gt;Paste or type in the following code, replacing what is already in the 
text box.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;rollbar&lt;/span&gt;


&lt;span class="n"&gt;ROLLBAR_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ROLLBAR_SECRET_KEY&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;missing Rollbar secret key&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;rollbar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ROLLBAR_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;production&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@rollbar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lambda_function&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;message&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;print_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;print_count&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c1"&gt;# check if message exists and how many times to print it&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;print_count&lt;/span&gt; &lt;span class="o"&gt;&amp;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;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&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="n"&gt;print_count&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="c1"&gt;# formatted string literals are new in Python 3.6&lt;/span&gt;
            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;message: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&amp;quot;&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;print_count&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The code contains the required &lt;code&gt;lambda_handler&lt;/code&gt; function. &lt;code&gt;lambda_handler&lt;/code&gt; 
is Lambda's hook for where to start execution the code.&lt;/p&gt;
&lt;p&gt;The Python code expects two environment variables that are read by the
&lt;code&gt;os&lt;/code&gt; module with the &lt;code&gt;getenv&lt;/code&gt; function. The &lt;code&gt;message&lt;/code&gt; and
&lt;code&gt;print_count&lt;/code&gt; variables are set by the environment variables.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180420-monitor-aws-lambda/lambda-coded.png" width="100%" class="shot rnd outl" alt="Python 3.6 code within a Lambda function."&gt;&lt;/p&gt;
&lt;p&gt;Below the code input text box on this function configuration screen there 
is a section to set environment variable key-value pairs. We need to input 
two environment variables and then we can run our code.&lt;/p&gt;
&lt;p&gt;Enter the keys named &lt;code&gt;message&lt;/code&gt; with a value of &lt;code&gt;Hello World!&lt;/code&gt;. Then
enter &lt;code&gt;print_count&lt;/code&gt; as a second key with the value of &lt;code&gt;5&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Our Python code's error handling is not robust. A value other than a 
number in the &lt;code&gt;print_count&lt;/code&gt; variable will cause the script to throw 
an exception when it is executed due to the forced casting of &lt;code&gt;print_count&lt;/code&gt; 
via the &lt;code&gt;int()&lt;/code&gt; function. We will use the exception that can occur during 
this forced casting as a trivial example that shows what happens when 
errors in our code happen during Lambda function execution.&lt;/p&gt;
&lt;p&gt;Hit the "Save" button at the top right. Use the
default "Hello World" test template values and name it "testHelloWorld".
We do not need any of those values for our function. &lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180420-monitor-aws-lambda/configure-test-event.png" width="100%" class="shot rnd outl" alt="Configure an empty test event for your Lambda function."&gt;&lt;/p&gt;
&lt;p&gt;Click "Create" and your test template will be created. Now click
"Test" to run the function. You should see "Execution result: succeeded" 
with the &lt;code&gt;message&lt;/code&gt; variable printed five times.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180420-monitor-aws-lambda/success-execution.png" width="100%" class="shot rnd outl" alt="Execution succeeds when there is an integer value for the print_count variable."&gt;&lt;/p&gt;
&lt;p&gt;Now change the value of &lt;code&gt;print_count&lt;/code&gt; to &lt;code&gt;i dunno&lt;/code&gt;. Save the function
and click "Test" again. The function will fail.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180420-monitor-aws-lambda/failed-execution-result.png" width="100%" class="shot rnd outl" alt="Execution fails when we do not have an integer value for print_count variable."&gt;&lt;/p&gt;
&lt;p&gt;It is obvious when we are working in the Console that an error just
occurred. However, in most cases an error will happen sporadically 
which is why we need a monitoring system in place to catch and report
on those exceptions.&lt;/p&gt;
&lt;h2&gt;Monitoring our Lambda Function&lt;/h2&gt;
&lt;p&gt;Head over to the &lt;a href="https://rollbar.com/"&gt;Rollbar homepage&lt;/a&gt; 
to obtain a free account and grab the necessary information to add their 
hosted monitoring service into our Lambda application.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180420-monitor-aws-lambda/rollbar-home.jpg" width="100%" class="shot rnd outl" alt="Rollbar homepage."&gt;&lt;/p&gt;
&lt;p&gt;Click "Sign Up" in the upper right-hand corner. Enter your 
email address, username and desired password.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180420-monitor-aws-lambda/sign-up-rollbar.jpg" width="100%" class="shot rnd outl" alt="Signing up for a Rollbar account in your browser."&gt;&lt;/p&gt;
&lt;p&gt;After the sign up page you will see the onboarding flow where you can
enter a project name and select a programming language. For the project
name type in "Full Stack Python" and then select that you are monitoring 
a Python-based application.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180420-monitor-aws-lambda/create-project.jpg" width="100%" class="shot rnd outl" alt="Name your project 'Full Stack Python' and select Python as your language."&gt;&lt;/p&gt;
&lt;p&gt;Press "Continue" at the bottom of the screen. The next
page shows us a few instructions on how to add monitoring.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180420-monitor-aws-lambda/configure-project.jpg" width="100%" class="shot rnd outl" alt="Configure project using your server-side access token."&gt;&lt;/p&gt;
&lt;p&gt;Take note of that server-side access token as we will need to set it
as an environment variable on AWS Lambda.&lt;/p&gt;
&lt;p&gt;We can now update our Python function to collect and aggregate
the errors that occur in our application. Add the following highlighted
lines to your Lambda code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;rollbar&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="n"&gt;ROLLBAR_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ROLLBAR_SECRET_KEY&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;missing Rollbar secret key&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="n"&gt;rollbar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ROLLBAR_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;production&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="nd"&gt;@rollbar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lambda_function&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;message&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;print_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;print_count&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c1"&gt;# check if message exists and how many times to print it&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;print_count&lt;/span&gt; &lt;span class="o"&gt;&amp;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;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&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="n"&gt;print_count&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="c1"&gt;# formatted string literals are new in Python 3.6&lt;/span&gt;
            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;message: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&amp;quot;&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;print_count&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The above highlighted new code lines incorporate the &lt;code&gt;rollbar&lt;/code&gt; library
into our application, set the &lt;code&gt;ROLLBAR_KEY&lt;/code&gt; with our environment variable
and use the &lt;code&gt;rollbar.lambda_function&lt;/code&gt; decorator to catch all errors in
our &lt;code&gt;lambda_handler&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;Add the following third environment variable named &lt;code&gt;ROLLBAR_SECRET_KEY&lt;/code&gt;
that is the server-side token from your new Rollbar project.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180420-monitor-aws-lambda/rollbar-key-env-var.png" width="100%" class="shot rnd outl" alt="Add your Rollbar server-side key into a Lambda environment variable."&gt;&lt;/p&gt;
&lt;p&gt;There is just one issue with this function on Lambda as it stands: there is 
no way for Lambda to know about the Rollbar package code. The external Rollbar
dependency needs to be included. There are a couple of ways to handle the
issue:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Download 
   &lt;a href="https://github.com/fullstackpython/blog-code-examples/raw/master/monitor-aws-lambda-python-3-6/hello-rollbar.zip"&gt;this pre-made zip file&lt;/a&gt;
   from the GitHub repository which includes all of the Rollbar package
   code and our code in the &lt;code&gt;lambda_function.py&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;Re-create the above code on your local system and 
   &lt;a href="https://haythamsalhi.wordpress.com/2017/10/04/creating-lambda-deployment-package-of-python/"&gt;use pip to obtain the dependencies and create a zip file locally&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I provided the pre-made zip file to save time in this tutorial so try
that one now so we can see the final results. Under "Function code", change
the "Code entry type" from "Edit code inline" to "Upload a .ZIP file".
Hit the "Upload" button under "Function package".&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180420-monitor-aws-lambda/upload-zip-package.png" width="100%" class="shot rnd outl" alt="Upload the ZIP file with Rollbar dependency."&gt;&lt;/p&gt;
&lt;p&gt;Hit the "Save" button at the top. With our new code we can now see if 
Rollbar will capture and report the exceptions. Hit the "Save" button and 
then "Test".&lt;/p&gt;
&lt;p&gt;The function will fail as expected. If we move over to our Rollbar
dashboard and refresh the page, we see the exceptions.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180420-monitor-aws-lambda/rollbar-exceptions.png" width="100%" class="shot rnd outl" alt="Rollbar user interface with exceptions."&gt;&lt;/p&gt;
&lt;p&gt;Now we can track Lambda exceptions across many functions regardless
of how frequently they are running.&lt;/p&gt;
&lt;h2&gt;What's Next?&lt;/h2&gt;
&lt;p&gt;We just wrote and executed a Python 3.6 function on AWS Lambda then
captured the exception message into our Rollbar logs. Now you can
continue building out your Python code knowing that when something
goes wrong you will have full visibility on what happened.&lt;/p&gt;
&lt;p&gt;Check out the &lt;a href="/aws-lambda.html"&gt;AWS Lambda section&lt;/a&gt; for 
more tutorials by other developers.&lt;/p&gt;
&lt;p&gt;Further questions? Contact me on Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;. I am also on GitHub with
the username &lt;a href="https://github.com/mattmakai"&gt;mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Something wrong with this post? Fork 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/180420-monitor-aws-lambda-python-3-6.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Wed, 25 Apr 2018 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2018-04-20:blog/monitor-python-3-6-example-code-aws-lambda-rollbar.html</guid></item><item><title>Developing Flask Apps in Docker Containers on macOS</title><link>https://www.fullstackpython.com/blog/develop-flask-web-apps-docker-containers-macos.html</link><description>&lt;p&gt;Adding &lt;a href="/docker.html"&gt;Docker&lt;/a&gt; to your &lt;a href="/why-use-python.html"&gt;Python&lt;/a&gt; and 
&lt;a href="/flask.html"&gt;Flask&lt;/a&gt; &lt;a href="/development-environments.html"&gt;development environment&lt;/a&gt; 
can be confusing when you are just getting started with containers. Let's 
quickly get Docker installed and configured for developing Flask web 
applications on your local system.&lt;/p&gt;
&lt;h2&gt;Our Tools&lt;/h2&gt;
&lt;p&gt;This tutorial is written for &lt;a href="/python-2-or-3.html"&gt;Python 3&lt;/a&gt;. It will work with
Python 2 but I have not tested it with the 
&lt;a href="https://pythonclock.org/"&gt;soon-to-be deprecated 2.7 version&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;&lt;a href="https://docs.docker.com/docker-for-mac/install/"&gt;Docker for Mac&lt;/a&gt; is necessary.
I recommend the stable release unless you have an explicit purpose for the edge 
channel.&lt;/p&gt;
&lt;p&gt;Within the Docker container we will use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python 3, specifically the
  &lt;a href="https://hub.docker.com/r/library/python/tags/"&gt;slim-3.6.5 version&lt;/a&gt;
  from Docker Hub&lt;/li&gt;
&lt;li&gt;&lt;a href="/flask.html"&gt;Flask&lt;/a&gt; version 1.0.2&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of the code for the Dockerfile and the Flask app are available open source
under the MIT license on GitHub under the 
&lt;a href="https://github.com/fullstackpython/blog-code-examples/tree/master/docker-flask-mac"&gt;docker-flask-mac directory&lt;/a&gt;
of the
&lt;a href="https://github.com/fullstackpython/blog-code-examples"&gt;blog-code-examples&lt;/a&gt;
repository. Use the code for your own purposes as much as you like.&lt;/p&gt;
&lt;h2&gt;Installing Docker on macOS&lt;/h2&gt;
&lt;p&gt;We need to install Docker before we can spin up our Docker containers. If you
already have Docker for Mac installed and working, feel free to jump to the
next section.&lt;/p&gt;
&lt;p&gt;On your Mac, 
&lt;a href="https://www.docker.com/community-edition#/download"&gt;download the Docker Community Edition (CE) for Mac&lt;/a&gt;
installer.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180309-flask-docker/docker-ce.jpg" width="100%" 
 class="shot rnd" alt="Download the Docker Community Edition for Mac."&gt;&lt;/p&gt;
&lt;p&gt;Find the newly-downloaded install within Finder and double click on the file.
Follow the installation process, which includes granting administrative privileges
to the installer.&lt;/p&gt;
&lt;p&gt;Open Terminal when the installer is done. Test your Docker installation with the 
&lt;code&gt;--version&lt;/code&gt; flag:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;docker --version
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If Docker is installed correctly you should see the following output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Docker version 18.03.1-ce, build 9ee9f40
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Note that Docker runs through a system agent you can find in the menu bar.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180309-flask-docker/docker-agent.png" width="100%" 
 class="shot rnd" alt="Docker agent in the menu bar."&gt;&lt;/p&gt;
&lt;p&gt;I have found the Docker agent to take up some precious battery life
on my Macbook Pro. If I am not developing and need to max battery time I will
close down the agent and start it back up again when I am ready to code. &lt;/p&gt;
&lt;p&gt;Now that Docker is installed let's get to running a container and writing
our Flask application.&lt;/p&gt;
&lt;h2&gt;Dockerfile&lt;/h2&gt;
&lt;p&gt;Docker needs to know what we want in a container, which is where the 
&lt;code&gt;Dockerfile&lt;/code&gt; comes in. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# &lt;span class="nv"&gt;this&lt;/span&gt; &lt;span class="nv"&gt;is&lt;/span&gt; &lt;span class="nv"&gt;an&lt;/span&gt; &lt;span class="nv"&gt;official&lt;/span&gt; &lt;span class="nv"&gt;Python&lt;/span&gt; &lt;span class="nv"&gt;runtime&lt;/span&gt;, &lt;span class="nv"&gt;used&lt;/span&gt; &lt;span class="nv"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;parent&lt;/span&gt; &lt;span class="nv"&gt;image&lt;/span&gt;
&lt;span class="nv"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;python&lt;/span&gt;:&lt;span class="mi"&gt;3&lt;/span&gt;.&lt;span class="mi"&gt;6&lt;/span&gt;.&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;slim&lt;/span&gt;

# &lt;span class="nv"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;working&lt;/span&gt; &lt;span class="nv"&gt;directory&lt;/span&gt; &lt;span class="nv"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;container&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;app&lt;/span&gt;
&lt;span class="nv"&gt;WORKDIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;app&lt;/span&gt;

# &lt;span class="nv"&gt;add&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;current&lt;/span&gt; &lt;span class="nv"&gt;directory&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;container&lt;/span&gt; &lt;span class="nv"&gt;as&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;app&lt;/span&gt;
&lt;span class="nv"&gt;ADD&lt;/span&gt; . &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;app&lt;/span&gt;

# &lt;span class="nv"&gt;execute&lt;/span&gt; &lt;span class="nv"&gt;everyone&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;s favorite pip command, pip install -r&lt;/span&gt;
&lt;span class="nv"&gt;RUN&lt;/span&gt; &lt;span class="nv"&gt;pip&lt;/span&gt; &lt;span class="nv"&gt;install&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nv"&gt;trusted&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;host&lt;/span&gt; &lt;span class="nv"&gt;pypi&lt;/span&gt;.&lt;span class="nv"&gt;python&lt;/span&gt;.&lt;span class="nv"&gt;org&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;r&lt;/span&gt; &lt;span class="nv"&gt;requirements&lt;/span&gt;.&lt;span class="nv"&gt;txt&lt;/span&gt;

# &lt;span class="nv"&gt;unblock&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;Flask&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;run&lt;/span&gt; &lt;span class="nv"&gt;on&lt;/span&gt;
&lt;span class="nv"&gt;EXPOSE&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;

# &lt;span class="nv"&gt;execute&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;Flask&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;
&lt;span class="nv"&gt;CMD&lt;/span&gt; [&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;python&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;, &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;app.py&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;]
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Save the Dockerfile so that we can run our next command with the completed
contents of the file. On the commandline run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;docker build -t flaskdock .
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The above &lt;code&gt;docker build&lt;/code&gt; file uses the &lt;code&gt;-t&lt;/code&gt; flag to tag the image with
the name of &lt;code&gt;flaskdock&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If the build worked successfully we can see the image in with the 
&lt;code&gt;docker image ls&lt;/code&gt; command. Give that a try now:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;docker image ls
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We should then see our tag name in the images list:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
flaskdock           latest              24045e0464af        2 minutes ago       165MB
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Our image is ready to load up as a container so we can write a quick
Flask app that we will use to test our environment by running it within
the container.&lt;/p&gt;
&lt;h2&gt;Coding A Simple Flask app&lt;/h2&gt;
&lt;p&gt;Time to put together a super simple "Hello, World!" Flask web app to test
running Python code within our Docker container. Within the current
project directory, create a file named &lt;code&gt;app.py&lt;/code&gt; with the following contents:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;


&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&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;hello&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hi from your Flask app running in your Docker container!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0.0.0.0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The above 7 lines of code (not counting blank PEP8-compliant lines) in 
&lt;a href="https://github.com/fullstackpython/blog-code-examples/blob/master/docker-flask-mac/app.py"&gt;app.py&lt;/a&gt; 
allow our application to return a simple message when run with the 
Flask development server.&lt;/p&gt;
&lt;p&gt;We need just one more file to specify our &lt;code&gt;Flask&lt;/code&gt; dependency. Create 
a &lt;code&gt;requirements.txt&lt;/code&gt; file within the same directory as &lt;code&gt;app.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;flask==1.0.2
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Make sure both the &lt;code&gt;app.py&lt;/code&gt; and &lt;code&gt;requirements.txt&lt;/code&gt; file are saved then
we can give the code a try.&lt;/p&gt;
&lt;h2&gt;Running the Container&lt;/h2&gt;
&lt;p&gt;Now that we have our image in hand along with the Python code in a file 
we can run the image as a container with the &lt;code&gt;docker run&lt;/code&gt; command. Execute 
the following command, making sure to replace the absolute path for the 
volume to your own directory.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;docker run -p 5000:80 --volume=/Users/matt/devel/py/flaskdocker:/app flaskdock
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If you receive the error 
&lt;code&gt;python: can't open file 'app.py': [Errno 2] No such file or directory&lt;/code&gt; then
you likely forgot to chance &lt;code&gt;/Users/matt/devel/py/flaskdocker&lt;/code&gt; to the 
directory where your project files, especially &lt;code&gt;app.py&lt;/code&gt;, are located.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180309-flask-docker/flask-app-response.png" width="100%" 
 class="shot rnd" alt="Flask app responding to requests from within a Docker container."&gt;&lt;/p&gt;
&lt;p&gt;Everything worked when you see a simple text-based HTTP response like what
is shown above in the screenshot of my Chrome browser.&lt;/p&gt;
&lt;h2&gt;What's Next?&lt;/h2&gt;
&lt;p&gt;We just installed Docker and configured a Flask application to run inside a 
container. That is just the beginning of how you can integrate Docker into 
your workflow. I strongly recommend reading the 
&lt;a href="https://docs.docker.com/compose/django/"&gt;Django with PostgreSQL quickstart&lt;/a&gt;
that will introduce you to Docker Swarm as well as the core Docker container
service.&lt;/p&gt;
&lt;p&gt;Next up take a look at the &lt;a href="/docker.html"&gt;Docker&lt;/a&gt; and 
&lt;a href="/deployment.html"&gt;deployment&lt;/a&gt; pages for more related tutorials.&lt;/p&gt;
&lt;p&gt;Questions? Let me know via a GitHub
&lt;a href="https://github.com/mattmakai/fullstackpython.com/issues"&gt;issue ticket on the Full Stack Python repository&lt;/a&gt;, 
on Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Do you see a typo, syntax issue or just something that's confusing in this 
blog post? Fork
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/180309-flask-docker-macos.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request with a fix or 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/issues"&gt;file an issue ticket on GitHub&lt;/a&gt;.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Tue, 05 Jun 2018 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2018-03-09:blog/develop-flask-web-apps-docker-containers-macos.html</guid></item><item><title>ReportLab and Future Community Project Launches</title><link>https://www.fullstackpython.com/blog/python-community-project-launches.html</link><description>&lt;p&gt;Congratulations to fellow Python developer 
&lt;a href="https://github.com/driscollis"&gt;Mike Driscoll&lt;/a&gt; for his successful 
&lt;strong&gt;&lt;a href="https://www.kickstarter.com/projects/34257246/reportlab-pdf-processing-with-python/"&gt;ReportLab: PDF Processing with Python Kickstarter&lt;/a&gt;&lt;/strong&gt; 
that just concluded with over double his funding goal.&lt;/p&gt;
&lt;p&gt;I was excited to back Mike's project for a couple of reasons. First, I've
used &lt;a href="https://www.reportlab.com/opensource/"&gt;ReportLab&lt;/a&gt; on past projects 
and it is a handy library for working with PDFs. Second, it is super useful 
to have entire books written on niche Python code libraries such as ReportLab.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.fullstackpython.com/"&gt;Full Stack Python&lt;/a&gt; will gladly back and 
spread the word about other awesome, legitimate Python community projects. Let 
me know via email (matthew.makai@gmail.com or mattmakai@fullstackguides.com) 
when you are getting ready to launch a Python project so I can help give a 
boost.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://talkpython.fm/"&gt;Michael Kennedy&lt;/a&gt; and I know from our 
&lt;a href="https://www.kickstarter.com/projects/mikeckennedy/python-for-entrepreneurs-video-course"&gt;own Kickstarter experience&lt;/a&gt; 
how much work goes into making these ideas come to fruition. It's a big 
confidence boost to have a community tailwind at your back and I am always 
happy to be part of that tailwind.&lt;/p&gt;
&lt;p&gt;Got questions or comments about 
&lt;a href="https://www.fullstackpython.com/"&gt;Full Stack Python&lt;/a&gt;? Send me an email or 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/issues"&gt;submit an issue ticket on GitHub&lt;/a&gt; 
to let me know how to improve the site
as I continue to 
&lt;a href="https://www.fullstackpython.com/table-of-contents.html"&gt;fill in the table of contents&lt;/a&gt; 
with &lt;a href="https://www.fullstackpython.com/change-log.html"&gt;new pages&lt;/a&gt;
and 
&lt;a href="https://www.fullstackpython.com/blog.html"&gt;new tutorials&lt;/a&gt;.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Sun, 04 Mar 2018 00:00:00 -0500</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2018-03-04:blog/python-community-project-launches.html</guid></item><item><title>Monitoring Django Projects with Rollbar</title><link>https://www.fullstackpython.com/blog/monitor-django-projects-web-apps-rollbar.html</link><description>&lt;p&gt;One fast way to scan for exceptions and errors in your
&lt;a href="/django.html"&gt;Django&lt;/a&gt; web application projects is to add a few lines of 
code to include a hosted &lt;a href="/monitoring.html"&gt;monitoring&lt;/a&gt; tool.&lt;/p&gt;
&lt;p&gt;In this tutorial we will learn to add the
&lt;a href="https://rollbar.com"&gt;Rollbar monitoring service&lt;/a&gt;
to a web app to visualize any issues produced by our web app.
This tutorial will use &lt;a href="/django.html"&gt;Django&lt;/a&gt; as the 
&lt;a href="/web-frameworks.html"&gt;web framework&lt;/a&gt; to build the web application but
there are also tutorials for 
the &lt;a href="/blog/hosted-monitoring-flask-web-apps.html"&gt;Flask&lt;/a&gt; and 
&lt;a href="/blog/monitor-python-web-applications.html"&gt;Bottle&lt;/a&gt; frameworks as well.
You can also check out a list of other hosted and open source tools on the 
&lt;a href="/monitoring.html"&gt;monitoring&lt;/a&gt; page.&lt;/p&gt;
&lt;h2&gt;Our Tools&lt;/h2&gt;
&lt;p&gt;&lt;a href="/python-2-or-3.html"&gt;Python 3&lt;/a&gt; is strongly recommended for this tutorial
because Python 2 will no longer be supported starting January 1, 2020.
&lt;a href="https://www.python.org/downloads/release/python-364/"&gt;Python 3.6.4&lt;/a&gt; to 
was used to build this tutorial. We will also use the following 
&lt;a href="/application-dependencies.html"&gt;application dependencies&lt;/a&gt; to build
our application:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/django.html"&gt;Django&lt;/a&gt; web framework, 
  &lt;a href="https://docs.djangoproject.com/en/2.0/"&gt;version 2.0.4&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rollbar.com/docs/notifier/pyrollbar/"&gt;rollbar&lt;/a&gt; monitoring 
  instrumentation library,
  &lt;a href="https://github.com/rollbar/pyrollbar/tree/v0.13.18"&gt;version 0.13.18&lt;/a&gt;,
  to report exceptions and errors&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pip.pypa.io/en/stable/"&gt;pip&lt;/a&gt; and 
  &lt;a href="https://virtualenv.pypa.io/en/latest/"&gt;virtualenv&lt;/a&gt;, which come installed 
  with Python 3, to install and isolate these Django and Rollbar libraries 
  from your other applications&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://rollbar.com/"&gt;free Rollbar account&lt;/a&gt; where we will send error
  data and view it when it is captured&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you need help getting your 
&lt;a href="/development-environments.html"&gt;development environment&lt;/a&gt; configured
before running this code, take a look at
&lt;a href="/blog/python-3-django-gunicorn-ubuntu-1604-xenial-xerus.html"&gt;this guide for setting up Python 3 and Django on Ubuntu 16.04 LTS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;All code in this blog post is available open source on GitHub under the 
MIT license within the
&lt;a href="https://github.com/fullstackpython/blog-code-examples"&gt;monitor-python-django-apps directory of the blog-code-examples repository&lt;/a&gt;. 
Use and modify the code however you like for your own applications.&lt;/p&gt;
&lt;h2&gt;Installing Dependencies&lt;/h2&gt;
&lt;p&gt;Start the project by creating a new 
&lt;a href="/virtual-environments-virtualenvs-venvs.html"&gt;virtual environment&lt;/a&gt; 
using the following command. I recommend keeping a separate directory 
such as &lt;code&gt;~/venvs/&lt;/code&gt; so that you always know where all your virtualenvs are 
located.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python3 -m venv monitordjango
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Activate the virtualenv with the &lt;code&gt;activate&lt;/code&gt; shell script:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt; monitordjango/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The command prompt will change after activating the virtualenv:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180202-monitor-django/activate-virtualenv.png" width="100%" class="shot rnd outl" alt="Activate the virtualenv on the command line."&gt;&lt;/p&gt;
&lt;p&gt;Remember that you need to activate your virtualenv in every new terminal 
window where you want to use the virtualenv to run the project.&lt;/p&gt;
&lt;p&gt;We can now install the &lt;a href="https://pypi.org/project/Django/2.0.4"&gt;Django&lt;/a&gt; 
and &lt;a href="https://pypi.org/project/rollbar"&gt;Rollbar&lt;/a&gt; packages into the 
activated, empty virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install django==2.0.4 rollbar==0.13.18
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Look for output like the following to confirm the 
dependencies installed correctly.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;certifi&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;2017.4&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.12&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rollbar&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mf"&gt;0.13&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Downloading&lt;/span&gt; &lt;span class="n"&gt;certifi&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2018.1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;151&lt;/span&gt;&lt;span class="n"&gt;kB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;████████████████████████████████&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;153&lt;/span&gt;&lt;span class="n"&gt;kB&lt;/span&gt; &lt;span class="mi"&gt;767&lt;/span&gt;&lt;span class="n"&gt;kB&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; 
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;urllib3&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mf"&gt;1.23&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.21&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.12&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rollbar&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mf"&gt;0.13&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;urllib3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.22&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;chardet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mf"&gt;3.1&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="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.12&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rollbar&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mf"&gt;0.13&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;chardet&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;span class="n"&gt;Collecting&lt;/span&gt; &lt;span class="n"&gt;idna&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mf"&gt;2.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;2.5&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.12&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rollbar&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mf"&gt;0.13&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Using&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="n"&gt;idna&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.6&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;py2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;whl&lt;/span&gt;
&lt;span class="n"&gt;Installing&lt;/span&gt; &lt;span class="n"&gt;collected&lt;/span&gt; &lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pytz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;certifi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urllib3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chardet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idna&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;six&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rollbar&lt;/span&gt;
  &lt;span class="n"&gt;Running&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rollbar&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt;
&lt;span class="n"&gt;Successfully&lt;/span&gt; &lt;span class="n"&gt;installed&lt;/span&gt; &lt;span class="n"&gt;certifi&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2018.1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt; &lt;span class="n"&gt;chardet&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="n"&gt;idna&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.6&lt;/span&gt; &lt;span class="n"&gt;pytz&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2018.3&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.18&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="n"&gt;rollbar&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.13&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt; &lt;span class="n"&gt;six&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.11&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;urllib3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.22&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We have our dependencies ready to go so now we can write the code for
our Django project.&lt;/p&gt;
&lt;h2&gt;Our Django Web App&lt;/h2&gt;
&lt;p&gt;&lt;a href="/django.html"&gt;Django&lt;/a&gt; makes it easy to generate the boilerplate code 
for new projects and apps using the &lt;code&gt;django-admin.py&lt;/code&gt; commands. Go to the 
directory where you typically store your coding projects. For example, on 
my Mac I use &lt;code&gt;/Users/matt/devel/py/&lt;/code&gt;. Then run the following command to 
start a Django project named &lt;code&gt;djmonitor&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;django-admin.py startproject djmonitor
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The command will create a directory named &lt;code&gt;djmonitor&lt;/code&gt; with several
subdirectories that you should be familiar with when you've previously 
worked with Django.&lt;/p&gt;
&lt;p&gt;Change directories into the new project.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;cd djmonitor
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Start a new Django app for our example code.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python manage.py startapp billions
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Django will create a new folder named &lt;code&gt;billions&lt;/code&gt; for our project.
Let's make sure our Django URLS work properly before before we write 
the code for the app.&lt;/p&gt;
&lt;p&gt;Now open &lt;code&gt;djmonitor/djmonitor/urls.py&lt;/code&gt; and add the highlighted lines so that URLs
with the path &lt;code&gt;/billions/&lt;/code&gt; will be routed to the app we are working on.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; (comments section)&lt;/span&gt;
&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;include&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;billions/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;billions.urls&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;admin/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Save &lt;code&gt;djmonitor/djmonitor/urls.py&lt;/code&gt; and open &lt;code&gt;djmonitor/djmonitor/settings.py&lt;/code&gt;.
Add the &lt;code&gt;billions&lt;/code&gt; app to &lt;code&gt;settings.py&lt;/code&gt; by inserting the highlighted line,
which will become line number 40 after insertion:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Application definition&lt;/span&gt;

&lt;span class="n"&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; 
    &lt;span class="s1"&gt;&amp;#39;django.contrib.admin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.auth&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.contenttypes&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.sessions&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.messages&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.staticfiles&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="s1"&gt;&amp;#39;billions&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Save and close &lt;code&gt;settings.py&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reminder&lt;/strong&gt;: make sure you change the default &lt;code&gt;DEBUG&lt;/code&gt; and &lt;code&gt;SECRET_KEY&lt;/code&gt; 
values in &lt;code&gt;settings.py&lt;/code&gt; before you deploy any code to production. Secure 
your app properly with the information from the
&lt;a href="https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/"&gt;Django production deployment checklist&lt;/a&gt; 
so that you do not add your project to the list of hacked applications
on the web.&lt;/p&gt;
&lt;p&gt;Next change into the &lt;code&gt;djmonitor/billions&lt;/code&gt; directory. Create a new file named 
&lt;code&gt;urls.py&lt;/code&gt; that will be specific to the routes for the &lt;code&gt;billions&lt;/code&gt; app within 
the &lt;code&gt;djmonitor&lt;/code&gt; project.&lt;/p&gt;
&lt;p&gt;Add the following lines to the currently-blank &lt;code&gt;djmonitor/billions/urls.py&lt;/code&gt; 
file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;                                                                                                                              
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&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="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;(?P&amp;lt;slug&amp;gt;[\wa-z-]+)&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;they&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="s2"&gt;&amp;quot;they&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Save &lt;code&gt;djmonitor/billions/urls.py&lt;/code&gt;. One more file before we can test that
our simple Django app works. Open &lt;code&gt;djmonitor/billions/views.py&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.exceptions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PermissionDenied&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.shortcuts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;they&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;slug&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;slug&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;are&amp;quot;&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;render&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="s1"&gt;&amp;#39;billions.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;PermissionDenied&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hmm, can&amp;#39;t find what you&amp;#39;re looking for.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Create a directory for your template files named &lt;code&gt;templates&lt;/code&gt; under
the &lt;code&gt;djmonitor/billions&lt;/code&gt; app directory.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;mkdir templates
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Within &lt;code&gt;templates&lt;/code&gt; create a new file named &lt;code&gt;billions.html&lt;/code&gt; that contains
the following &lt;a href="/django-templates.html"&gt;Django template&lt;/a&gt; markup.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;They... are BILLIONS!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://store.steampowered.com/app/644930/They_Are_Billions/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;They Are Billions&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://media.giphy.com/media/2jUHXTGhGo156/giphy.gif&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Alright, all of our files are in place so we can test the application.
Within the base directory of your project run the Django development
server:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python manage.py runserver
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The Django development server will start up with no issues other than an 
unapplied migrations warning.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;monitordjango&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; $ &lt;span class="nv"&gt;python&lt;/span&gt; &lt;span class="nv"&gt;manage&lt;/span&gt;.&lt;span class="nv"&gt;py&lt;/span&gt; &lt;span class="nv"&gt;runserver&lt;/span&gt;
&lt;span class="nv"&gt;Performing&lt;/span&gt; &lt;span class="nv"&gt;system&lt;/span&gt; &lt;span class="nv"&gt;checks&lt;/span&gt;...

&lt;span class="nv"&gt;System&lt;/span&gt; &lt;span class="nv"&gt;check&lt;/span&gt; &lt;span class="nv"&gt;identified&lt;/span&gt; &lt;span class="nv"&gt;no&lt;/span&gt; &lt;span class="nv"&gt;issues&lt;/span&gt; &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="nv"&gt;silenced&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;.

&lt;span class="nv"&gt;You&lt;/span&gt; &lt;span class="nv"&gt;have&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt; &lt;span class="nv"&gt;unapplied&lt;/span&gt; &lt;span class="nv"&gt;migration&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;s&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;. &lt;span class="nv"&gt;Your&lt;/span&gt; &lt;span class="nv"&gt;project&lt;/span&gt; &lt;span class="nv"&gt;may&lt;/span&gt; &lt;span class="nv"&gt;not&lt;/span&gt; &lt;span class="nv"&gt;work&lt;/span&gt; &lt;span class="nv"&gt;properly&lt;/span&gt; &lt;span class="k"&gt;until&lt;/span&gt; &lt;span class="nv"&gt;you&lt;/span&gt; &lt;span class="nv"&gt;apply&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;migrations&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;s&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;: &lt;span class="nv"&gt;admin&lt;/span&gt;, &lt;span class="nv"&gt;auth&lt;/span&gt;, &lt;span class="nv"&gt;contenttypes&lt;/span&gt;, &lt;span class="nv"&gt;sessions&lt;/span&gt;.
&lt;span class="nv"&gt;Run&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;python manage.py migrate&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;apply&lt;/span&gt; &lt;span class="nv"&gt;them&lt;/span&gt;.

&lt;span class="nv"&gt;April&lt;/span&gt; &lt;span class="mi"&gt;08&lt;/span&gt;, &lt;span class="mi"&gt;2018&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;:&lt;span class="mi"&gt;06&lt;/span&gt;:&lt;span class="mi"&gt;44&lt;/span&gt;
&lt;span class="nv"&gt;Django&lt;/span&gt; &lt;span class="nv"&gt;version&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;.&lt;span class="mi"&gt;0&lt;/span&gt;.&lt;span class="mi"&gt;4&lt;/span&gt;, &lt;span class="nv"&gt;using&lt;/span&gt; &lt;span class="nv"&gt;settings&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;djmonitor.settings&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;
&lt;span class="nv"&gt;Starting&lt;/span&gt; &lt;span class="nv"&gt;development&lt;/span&gt; &lt;span class="nv"&gt;server&lt;/span&gt; &lt;span class="nv"&gt;at&lt;/span&gt; &lt;span class="nv"&gt;http&lt;/span&gt;:&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="mi"&gt;127&lt;/span&gt;.&lt;span class="mi"&gt;0&lt;/span&gt;.&lt;span class="mi"&gt;0&lt;/span&gt;.&lt;span class="mi"&gt;1&lt;/span&gt;:&lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="nv"&gt;Quit&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;server&lt;/span&gt; &lt;span class="nv"&gt;with&lt;/span&gt; &lt;span class="nv"&gt;CONTROL&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;C&lt;/span&gt;.
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Only the &lt;code&gt;/billions/&lt;/code&gt; route will successfully hit our &lt;code&gt;billions&lt;/code&gt; app. Try
to access "http://localhost:8000/billions/are/". We should see our template
render with the gif:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180202-monitor-django/localhost-dev-server.jpg" width="100%" class="shot rnd outl" alt="Testing local development server at /billions/are/."&gt;&lt;/p&gt;
&lt;p&gt;Cool, our application successfully rendered a super-simple HTML page 
with a GIF of one of my favorite computer games. What if we try another
path under &lt;code&gt;/billions/&lt;/code&gt; such as "http://localhost:8000/billions/arenot/"?&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180202-monitor-django/arenot-403-forbidden.png" width="100%" class="shot rnd outl" alt="403 Forbidden error with any path under /billions/ other than /billions/are/."&gt;&lt;/p&gt;
&lt;p&gt;Our 403 Forbidden is raised, which is what we expected based on our code.
That is a somewhat contrived block of code but let's see how we can
catch and report this type of error without changing our &lt;code&gt;views.py&lt;/code&gt;
code at all. This approach will be much easier on us when modifying an
existing application than having to refactor the code to report on
these types of errors, if we even know where they exist.&lt;/p&gt;
&lt;h2&gt;Monitoring with Rollbar&lt;/h2&gt;
&lt;p&gt;Go to the &lt;a href="https://rollbar.com/"&gt;Rollbar homepage in your browser&lt;/a&gt; 
to add their tool to our Django app.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180202-monitor-django/rollbar-home.jpg" width="100%" class="shot rnd outl" alt="rollbar.com in Chrome."&gt;&lt;/p&gt;
&lt;p&gt;Click the "Sign Up" button in the upper right-hand corner. Enter your 
email address, a username and the password you want on the sign up page.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180202-monitor-django/sign-up-rollbar.jpg" width="100%" class="shot rnd outl" alt="Sign up for Rollbar."&gt;&lt;/p&gt;
&lt;p&gt;After the sign up page you will see the onboarding flow where you can
enter a project name and select a programming language. For the project
name type in "Full Stack Python" (or whatever project name you are
working on) then select that you are monitoring a Python-based application.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180202-monitor-django/create-project.jpg" width="100%" class="shot rnd outl" alt="Create a project named 'Full Stack Python' and select Python for programming language."&gt;&lt;/p&gt;
&lt;p&gt;Press the "Continue" button at the bottom to move along. The next
screen shows us a few instructions on how to add monitoring.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180202-monitor-django/configure-project.jpg" width="100%" class="shot rnd outl" alt="Configure project using your server-side access token."&gt;&lt;/p&gt;
&lt;p&gt;Let's change our Django project code to let Rollbar collect and aggregate the
errors that pop up in our application. &lt;/p&gt;
&lt;p&gt;Re-open &lt;code&gt;djmonitor/djmonitor/settings.py&lt;/code&gt; and look for the &lt;code&gt;MIDDLEWARE&lt;/code&gt;
list. Add &lt;code&gt;rollbar.contrib.django.middleware.RollbarNotifierMiddleware&lt;/code&gt;
as the last item:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;MIDDLEWARE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; 
    &lt;span class="s1"&gt;&amp;#39;django.middleware.security.SecurityMiddleware&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.sessions.middleware.SessionMiddleware&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.middleware.common.CommonMiddleware&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.middleware.csrf.CsrfViewMiddleware&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.auth.middleware.AuthenticationMiddleware&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.messages.middleware.MessageMiddleware&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.middleware.clickjacking.XFrameOptionsMiddleware&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="s1"&gt;&amp;#39;rollbar.contrib.django.middleware.RollbarNotifierMiddleware&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Do not close &lt;code&gt;settings.py&lt;/code&gt; just yet. Next add the following lines 
to the bottom of the file. Change the &lt;code&gt;access_token&lt;/code&gt; value to your
Rollbar server side access token and &lt;code&gt;root&lt;/code&gt; to the directory where
you are developing your project.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;ROLLBAR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; {
    &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;access_token&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;access token from dashboard&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;,
    &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;environment&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;development&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;DEBUG&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;production&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;,
    &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;branch&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;master&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;,
    &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;root&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;: &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;/Users/matt/devel/py/blog-code-examples/monitor-django-apps/djmonitor&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;,
    &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;patch_debugview&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;: &lt;span class="nv"&gt;False&lt;/span&gt;,
}
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If you are uncertain about what your secret token is, it can be found on 
the Rollbar onboarding screen or "Settings" -&amp;gt; "Access Tokens" within 
&lt;a href="https://rollbar.com"&gt;rollbar.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Note that I typically store all my environment variables in a &lt;code&gt;.env&lt;/code&gt; &lt;/p&gt;
&lt;p&gt;We can test that Rollbar is working as we run our application. Run it
now using the development server.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python manage.py runserver
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Back in your web browser press the "Done! Go to Dashboard" button.&lt;/p&gt;
&lt;p&gt;If an event hasn't been reported yet we'll see a waiting screen like this
one:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180202-monitor-django/waiting-for-events.jpg" width="100%" class="shot rnd outl" alt="Waiting for events data on the dashboard."&gt;&lt;/p&gt;
&lt;p&gt;Make sure your Django development still server is running and try to go to 
"http://localhost:8000/billions/arenot/". A 403 error is immediately reported
on the dashboard:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180202-monitor-django/forbidden-exception.jpg" width="100%" class="shot rnd outl" alt="403 Forbidden exceptions on the Rollbar dashboard screen."&gt;&lt;/p&gt;
&lt;p&gt;We even get an email with the error (which can also be turned off if you
don't want emails for every error):&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/180202-monitor-django/email-report.jpg" width="100%" class="shot rnd outl" alt="Email report on the errors in your Django application."&gt;&lt;/p&gt;
&lt;p&gt;Alright we now have monitoring and error reporting all configured for our
Django application!&lt;/p&gt;
&lt;h2&gt;What now?&lt;/h2&gt;
&lt;p&gt;We learned to catch issues in our Django project using Rollbar and view the
errors in Rollbar's interface. Next try out Rollbar's more advanced monitoring
features such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://rollbar.com/docs/person-tracking/"&gt;sorting errors by user&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rollbar.com/docs/custom-grouping/"&gt;configuring rules on group errors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rollbar.com/docs/deploy-tracking/"&gt;debugging deployment issues&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There is plenty more to learn about in the areas of 
&lt;a href="/web-development.html"&gt;web development&lt;/a&gt; and 
&lt;a href="/deployments.html"&gt;deployments&lt;/a&gt; so keep learning by reading 
about &lt;a href="/web-frameworks.html"&gt;web frameworks&lt;/a&gt;. You can also learn more 
about integrating Rollbar with Python applications via 
&lt;a href="https://rollbar.com/docs/notifier/pyrollbar/"&gt;their Python documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Questions? Let me know via 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/issues"&gt;a GitHub issue ticket on the Full Stack Python repository&lt;/a&gt;, 
on Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Do you see a typo, syntax issue or wording that's confusing in this blog 
post? Fork
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/180202-monitor-django-web-apps.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request with a fix or 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/issues"&gt;file an issue ticket on GitHub&lt;/a&gt;.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Sun, 20 May 2018 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2018-02-02:blog/monitor-django-projects-web-apps-rollbar.html</guid></item><item><title>5 Years of Full Stack Python</title><link>https://www.fullstackpython.com/blog/five-years-full-stack-python.html</link><description>&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.fullstackpython.com/"&gt;Full Stack Python&lt;/a&gt;&lt;/strong&gt; began five years 
ago today, on December 23, 2012, with 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/commit/69f5f466196f572aab187504d52bc368cde840cd"&gt;Git commit 69f5f46&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;I originally built the site to help out a group of junior developers that 
kept asking me similar Python web development questions via email. It seemed 
like the answers would be useful to more people if I put them in a
publicly-accessible location. One day over lunch with a friend before I 
started writing I sketched out some of my vague ideas on a napkin: &lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.fullstackpython.com/img/visuals/fsp-napkin.jpg" width="100%" class="rnd" alt="Original Full Stack Python concept drawing."&gt;&lt;/p&gt;
&lt;p&gt;The site started as a single page 
&lt;a href="https://www.fullstackpython.com/static-site-generator.html"&gt;static website&lt;/a&gt; 
but eventually was split into topic-specific pages such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.fullstackpython.com/deployments.html"&gt;deployments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fullstackpython.com/servers.html"&gt;servers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fullstackpython.com/web-frameworks.html"&gt;web frameworks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fullstackpython.com/wsgi-servers.html"&gt;WSGI servers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fullstackpython.com/source-control.html"&gt;source control&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fullstackpython.com/operating-systems.html"&gt;operating systems&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fullstackpython.com/web-servers.html"&gt;web servers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Most pages were on deployment and web framework topics. I have made a 
concerted effort to write more about 
&lt;a href="https://www.fullstackpython.com/data.html"&gt;data&lt;/a&gt; and 
&lt;a href="https://www.fullstackpython.com/development-environments.html"&gt;development environment&lt;/a&gt; 
subjects as I continue to learn and grow my own software development skills. 
In some ways Full Stack Python's evolution represents my own growth as a 
programmer.&lt;/p&gt;
&lt;p&gt;The site now has over 120,000 words and 150+ pages, split between topics
pages and &lt;a href="https://www.fullstackpython.com/blog.html"&gt;tutorial blog posts&lt;/a&gt;. 
I've also given a few technical talks on many of these topics, such as 
&lt;a href="https://www.youtube.com/watch?v=s6NaOKD40rY"&gt;Full Stack Python&lt;/a&gt; at 
EuroPython 2014 and 
&lt;a href="https://www.youtube.com/watch?v=L5YQbNrFfyw"&gt;WebSockets in Python&lt;/a&gt; at
the San Francisco Python meetup. With so much content on the site, it's time
to revamp many of the original pages to ensure they are still accurate and
contain solid resources that explain those subjects. It can be sad to see so
many awesome blog posts I used to reference that have succumbed to link rot.
Maintenance takes up an increasing amount of time spent working on the site
so please submit 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/issues"&gt;issue tickets&lt;/a&gt; 
whenever you see a 404 or a link that's not the original correct resource.&lt;/p&gt;
&lt;p&gt;Full Stack Python has now been read by over 2.5 million developers, but
it took a long time to get to that milestone. In fact there were only a few
hundred readers within the first year. Over time with daily updates I have
been fortunate to grow the readership to around 125,000 developers per month.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.fullstackpython.com/img/visuals/traffic.png" width="100%" class="rnd" alt="Full Stack Python traffic growth via Google Analytics."&gt;&lt;/p&gt;
&lt;p&gt;Watching the numbers go up has been fun but the best part is receiving 
"thank you" emails and tweets, as well as talking to readers in person at 
PyCon. Keep those emails coming as they keep me motivated to continue writing! 
If you'll be at PyCon in April, I'll definitely be there at the 
&lt;a href="https://www.twilio.com/"&gt;Twilio&lt;/a&gt; booth or around the community
booths where &lt;a href="https://talkpython.fm/"&gt;Michael Kennedy of Talk Python to Me&lt;/a&gt; 
and other Python community folks such as &lt;a href="https://dbader.org/"&gt;Dan Bader&lt;/a&gt;, 
&lt;a href="https://www.pyimagesearch.com/"&gt;Adrian Rosebrock of PyImageSearch&lt;/a&gt;,
&lt;a href="https://pybit.es/"&gt;Bob Belderbos of PyBites&lt;/a&gt; and the 
&lt;a href="https://realpython.com"&gt;Real Python&lt;/a&gt; guys will also be hanging out.&lt;/p&gt;
&lt;p&gt;It's been a real pleasure working on Full Stack Python over the past five
years and I'm really excited for what's coming for the site in the next 
five years. The 
&lt;a href="https://www.fullstackpython.com/change-log.html"&gt;change log page&lt;/a&gt; contains
a complete list of major modifications and 
&lt;a href="https://www.fullstackpython.com/future-directions.html"&gt;future directions&lt;/a&gt;
has some insight into my thought process for creating additional content.&lt;/p&gt;
&lt;p&gt;Got questions or comments about 
&lt;a href="https://www.fullstackpython.com/"&gt;Full Stack Python&lt;/a&gt;? Send me an email or 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/issues"&gt;submit an issue ticket on GitHub&lt;/a&gt; 
to let me know how to improve the site
as I continue to 
&lt;a href="https://www.fullstackpython.com/table-of-contents.html"&gt;fill in the table of contents&lt;/a&gt; 
with &lt;a href="https://www.fullstackpython.com/change-log.html"&gt;new pages&lt;/a&gt;
and 
&lt;a href="https://www.fullstackpython.com/blog.html"&gt;new tutorials&lt;/a&gt;.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Sat, 23 Dec 2017 00:00:00 -0500</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2017-12-23:blog/five-years-full-stack-python.html</guid></item><item><title>GitPython and New Git Tutorials</title><link>https://www.fullstackpython.com/blog/gitpython-git-tutorials.html</link><description>&lt;p&gt;&lt;a href="/blog/first-steps-gitpython.html"&gt;&lt;strong&gt;First Steps with GitPython&lt;/strong&gt;&lt;/a&gt;
is a quick tutorial that shows how to get started using the awesome
&lt;a href="https://gitpython.readthedocs.io/en/stable/"&gt;GitPython&lt;/a&gt; library for
programmatically interacting with Git repositories in your Python 
applications. In the spirit of the 
&lt;a href="https://github.com/jhund/filterrific/issues/147#issuecomment-341867147"&gt;thank you maintainers&lt;/a&gt; 
issue ticket I wrote about last newsletter, I opened a quick
&lt;a href="https://github.com/gitpython-developers/GitPython/issues/709"&gt;"non-issue" ticket for the GitPython developers&lt;/a&gt;
to thank them. Give them a thank you +1 if you've used the project and also 
found it useful.&lt;/p&gt;
&lt;p&gt;The &lt;a href="/git.html"&gt;&lt;strong&gt;Git&lt;/strong&gt;&lt;/a&gt; page on Full Stack
Python has also just been updated with new resources. A few of my favorite
new tutorials list on the &lt;a href="/git.html"&gt;Git page&lt;/a&gt; 
are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/k88hudson/git-flight-rules"&gt;Flight rules for Git&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philna.sh/blog/2017/01/04/git-back-to-the-future/"&gt;Git back to the future&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jqassistant.org/shadows-of-the-past-analysis-of-git-repositories/"&gt;Shadows Of The Past: Analysis Of Git Repositories&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.thoughtram.io/git/2014/11/18/the-anatomy-of-a-git-commit.html"&gt;The anatomy of a Git commit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also split out the Git page resources into beginner, more advanced, specific
use case and workflow sections so it's easier to parse based on whether you're
a Git veteran or still up-and-coming in that area of your development skills.&lt;/p&gt;
&lt;p&gt;Got questions or comments about 
&lt;a href="https://www.fullstackpython.com/"&gt;Full Stack Python&lt;/a&gt;? Send me an email or 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/issues"&gt;submit an issue ticket on GitHub&lt;/a&gt; 
to let me know how to improve the site
as I continue to 
&lt;a href="/table-of-contents.html"&gt;fill in the table of contents&lt;/a&gt; 
with &lt;a href="/change-log.html"&gt;new pages&lt;/a&gt;
and 
&lt;a href="/blog.html"&gt;new tutorials&lt;/a&gt;.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Mon, 11 Dec 2017 00:00:00 -0500</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2017-12-11:blog/gitpython-git-tutorials.html</guid></item><item><title>First Steps with GitPython</title><link>https://www.fullstackpython.com/blog/first-steps-gitpython.html</link><description>&lt;p&gt;&lt;a href="http://gitpython.readthedocs.io/"&gt;GitPython&lt;/a&gt; is a Python code library
for programmatically reading from and writing to &lt;a href="/git.html"&gt;Git&lt;/a&gt;
&lt;a href="/source-control.html"&gt;source control&lt;/a&gt; repositories.&lt;/p&gt;
&lt;p&gt;Let's learn how to use GitPython by quickly installing it and reading from
a local cloned Git repository.&lt;/p&gt;
&lt;h2&gt;Our Tools&lt;/h2&gt;
&lt;p&gt;This tutorial should work with either &lt;a href="/python-2-or-3.html"&gt;Python 2.7 or 3&lt;/a&gt;, 
but Python 3, especially 3.6+, is strongly recommended for all new 
applications. I used
&lt;a href="https://www.python.org/downloads/release/python-363/"&gt;Python 3.6.3&lt;/a&gt; to 
write this post. In addition to Python, throughout this tutorial we 
will also use the following 
&lt;a href="/application-dependencies.html"&gt;application dependencies&lt;/a&gt;: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/git.html"&gt;Git&lt;/a&gt;, 
  a &lt;a href="/static-site-generator.html"&gt;source (version) control&lt;/a&gt; implementation, 
  &lt;a href="https://github.com/git/git/tree/v2.15.1"&gt;version 2.15.1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gitpython-developers/GitPython/tree/2.1.7"&gt;GitPython&lt;/a&gt;
  version &lt;a href="https://github.com/gitpython-developers/GitPython/tree/2.1.7"&gt;2.1.7&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pip.pypa.io/en/stable/"&gt;pip&lt;/a&gt; and 
  &lt;a href="https://virtualenv.pypa.io/en/latest/"&gt;virtualenv&lt;/a&gt;, which come
  packaged with Python 3, to install and isolate the GitPython library
  from any of your other Python projects&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Take a look at
&lt;a href="/blog/python-3-flask-green-unicorn-ubuntu-1604-xenial-xerus.html"&gt;this guide for setting up Python 3 and Flask on Ubuntu 16.04 LTS&lt;/a&gt;
if you need specific instructions to get a base
&lt;a href="/development-environments.html"&gt;Python development environment&lt;/a&gt; set up.&lt;/p&gt;
&lt;p&gt;All code in this blog post is available open source under the MIT license 
on GitHub under the 
&lt;a href="https://github.com/fullstackpython/blog-code-examples/tree/master/first-steps-gitpython"&gt;first-steps-gitpython directory of the blog-code-examples repository&lt;/a&gt;.
Use and abuse the source code as you like for your own applications.&lt;/p&gt;
&lt;h2&gt;Install GitPython&lt;/h2&gt;
&lt;p&gt;Start by creating a new virtual environment for your project. My virtualenv
is named &lt;code&gt;testgit&lt;/code&gt; but you can name yours whatever matches the project 
you are creating.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python3 -m venv gitpy
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Activate the newly-created virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;source gitpy/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The virtualenv's name will be prepended to the command prompt after 
activation.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171129-gitpython/activate-virtualenv.png" width="100%" class="shot rnd outl" alt="Create and activate the Python virtual environment."&gt;&lt;/p&gt;
&lt;p&gt;Now that the virutalenv is activated we can use the &lt;code&gt;pip&lt;/code&gt; command to install
GitPython.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install &lt;span class="nv"&gt;gitpython&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.1.7
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Run the &lt;code&gt;pip&lt;/code&gt; command and after everything is installed you should see output
similar to the following "Successfully installed" message.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;gitpy&lt;span class="o"&gt;)&lt;/span&gt; $ pip install &lt;span class="nv"&gt;gitpython&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.1.7
Collecting &lt;span class="nv"&gt;gitpython&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.1.7
  Downloading GitPython-2.1.7-py2.py3-none-any.whl &lt;span class="o"&gt;(&lt;/span&gt;446kB&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="m"&gt;100&lt;/span&gt;% &lt;span class="p"&gt;|&lt;/span&gt;████████████████████████████████&lt;span class="p"&gt;|&lt;/span&gt; 450kB 651kB/s 
Collecting gitdb2&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.0.0 &lt;span class="o"&gt;(&lt;/span&gt;from &lt;span class="nv"&gt;gitpython&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.1.7&lt;span class="o"&gt;)&lt;/span&gt;
  Downloading gitdb2-2.0.3-py2.py3-none-any.whl &lt;span class="o"&gt;(&lt;/span&gt;63kB&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="m"&gt;100&lt;/span&gt;% &lt;span class="p"&gt;|&lt;/span&gt;████████████████████████████████&lt;span class="p"&gt;|&lt;/span&gt; 71kB 947kB/s 
Collecting smmap2&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.0.0 &lt;span class="o"&gt;(&lt;/span&gt;from gitdb2&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.0.0-&amp;gt;gitpython&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.1.7&lt;span class="o"&gt;)&lt;/span&gt;
  Downloading smmap2-2.0.3-py2.py3-none-any.whl
Installing collected packages: smmap2, gitdb2, gitpython
Successfully installed gitdb2-2.0.3 gitpython-2.1.7 smmap2-2.0.3
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Next we can start programmatically interacting with Git repositories in our
Python applications with the GitPython installed.&lt;/p&gt;
&lt;h2&gt;Clone Repository&lt;/h2&gt;
&lt;p&gt;GitPython can work with remote repositories but for simplicity in this 
tutorial we'll use a cloned repository on our local system.&lt;/p&gt;
&lt;p&gt;Clone a repository you want to work with to your local system. If you don't
have a specific one in mind use the 
&lt;a href="https://github.com/mattmakai/fullstackpython.com"&gt;open source Full Stack Python Git repository&lt;/a&gt;
that is hosted on GitHub.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;git clone git@github.com:mattmakai/fullstackpython.com fsp
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Take note of the location where you cloned the repository because we need
the path to tell GitPython what repository to handle. Change into the 
directory for the new Git repository with &lt;code&gt;cd&lt;/code&gt; then run the &lt;code&gt;pwd&lt;/code&gt; (present
working directory) command to get the full path.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; fsp
&lt;span class="nb"&gt;pwd&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;You will see some output like &lt;code&gt;/Users/matt/devel/py/fsp&lt;/code&gt;. This path is your
absolute path to the base of the Git repository.&lt;/p&gt;
&lt;p&gt;Use the &lt;code&gt;export&lt;/code&gt; command to set an environment variable for the absolute path
to the Git repository.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;GIT_REPO_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/Users/matt/devel/py/fsp&amp;#39;&lt;/span&gt; &lt;span class="c1"&gt;# make sure this your own path&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Our Git repository and path environment variable are all set so let's write
the Python code that uses GitPython.&lt;/p&gt;
&lt;h2&gt;Read Repository and Commit Data&lt;/h2&gt;
&lt;p&gt;Create a new Python file named &lt;code&gt;read_repo.py&lt;/code&gt; and open it so we can start
to code up a simple script.&lt;/p&gt;
&lt;p&gt;Start with a couple of imports and a constant:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;git&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Repo&lt;/span&gt;


&lt;span class="n"&gt;COMMITS_TO_PRINT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;os&lt;/code&gt; module makes it easy to read environment variables, such as our
&lt;code&gt;GIT_REPO_PATH&lt;/code&gt; variable we set earlier. &lt;code&gt;from git import Repo&lt;/code&gt; gives our
application access to the GitPython library when we create the &lt;code&gt;Repo&lt;/code&gt; object.
&lt;code&gt;COMMITS_TO_PRINT&lt;/code&gt; is a constant that limits the number of lines of output
based on the amount of commits we want our script to print information on.
Full Stack Python has over 2,250 commits so there'd be a whole lot of output
if we printed every commit.&lt;/p&gt;
&lt;p&gt;Next within our &lt;code&gt;read_repo.py&lt;/code&gt; file create a function to print individual
commit information:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;print_commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;----&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;print&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;commit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hexsha&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="se"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="s2"&gt; by &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;)&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                     &lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;author&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;commit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nb"&gt;print&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;commit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;authored_datetime&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nb"&gt;print&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="s2"&gt;&amp;quot;count: &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt; and size: &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                                              &lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;print_commit&lt;/code&gt; function takes in a GitPython commit object and
prints the 40-character SHA-1 hash for the commit followed by:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;the commit summary&lt;/li&gt;
&lt;li&gt;author name &lt;/li&gt;
&lt;li&gt;author email&lt;/li&gt;
&lt;li&gt;commit date and time&lt;/li&gt;
&lt;li&gt;count and update size&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Below the &lt;code&gt;print_commit&lt;/code&gt; function, create another function named 
&lt;code&gt;print_repository&lt;/code&gt; to print details of the &lt;code&gt;Repo&lt;/code&gt; object:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;print_repository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Repo description: &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Repo active branch is &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active_branch&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;remote&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remotes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Remote named &amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;quot; with URL &amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;quot;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;remote&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Last commit for repo is &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;.&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&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;repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hexsha&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;code&gt;print_repository&lt;/code&gt; is similar to &lt;code&gt;print_commit&lt;/code&gt; but instead prints the
repository description, active branch, all remote Git URLs configured
for this repository and the latest commit.&lt;/p&gt;
&lt;p&gt;Finally, we need a "main" function for when we invoke the script from the
terminal using the &lt;code&gt;python&lt;/code&gt; command. Round out our &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;repo_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;GIT_REPO_PATH&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Repo object used to programmatically interact with Git repositories&lt;/span&gt;
    &lt;span class="n"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Repo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# check that the repository loaded correctly&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;repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bare&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Repo at &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt; successfully loaded.&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo_path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;print_repository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# create list of commits then print some of them to stdout&lt;/span&gt;
        &lt;span class="n"&gt;commits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iter_commits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;master&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))[:&lt;/span&gt;&lt;span class="n"&gt;COMMITS_TO_PRINT&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;commit&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;commits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;print_commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;pass&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Could not load repository at &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt; :(&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo_path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The main function handles grabbing the &lt;code&gt;GIT_REPO_PATH&lt;/code&gt; environment variable
and creates a Repo object based on the path if possible.&lt;/p&gt;
&lt;p&gt;If the repository is not empty, which indicates a failure to find the 
repository, then the &lt;code&gt;print_repository&lt;/code&gt; and &lt;code&gt;print_commit&lt;/code&gt; functions are 
called to show the repository data.&lt;/p&gt;
&lt;p&gt;If you want to copy and paste all of the code found above at once, take a
look at the 
&lt;a href="https://github.com/fullstackpython/blog-code-examples/blob/master/first-steps-gitpython/read_repo.py"&gt;&lt;code&gt;read_repo.py&lt;/code&gt; file on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Time to test our GitPython-using script. Invoke the &lt;code&gt;read_repo.py&lt;/code&gt; file using
the following command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;gitpy&lt;span class="o"&gt;)&lt;/span&gt; $ python read_repo.py
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If the virtualenv is activated and the &lt;code&gt;GIT_REPO_PATH&lt;/code&gt; environment variable
is set properly, we should see output similar to the following.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Repo at ~/devel/py/fsp/ successfully loaded.
Repo description: Unnamed repository&lt;span class="p"&gt;;&lt;/span&gt; edit this file &lt;span class="s1"&gt;&amp;#39;description&amp;#39;&lt;/span&gt; to name the repository.
Repo active branch is master
Remote named &lt;span class="s2"&gt;&amp;quot;origin&amp;quot;&lt;/span&gt; with URL &lt;span class="s2"&gt;&amp;quot;git@github.com:mattmakai/fullstackpython.com&amp;quot;&lt;/span&gt;
Last commit &lt;span class="k"&gt;for&lt;/span&gt; repo is 1fa2de70aeb2ea64315f69991ccada51afac1ced.
----
1fa2de70aeb2ea64315f69991ccada51afac1ced
&lt;span class="s2"&gt;&amp;quot;update latest blog post with code&amp;quot;&lt;/span&gt; by Matt Makai &lt;span class="o"&gt;(&lt;/span&gt;matthew.makai@gmail.com&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="m"&gt;2017&lt;/span&gt;-11-30 &lt;span class="m"&gt;17&lt;/span&gt;:15:14-05:00
count: &lt;span class="m"&gt;2256&lt;/span&gt; and size: &lt;span class="m"&gt;254&lt;/span&gt;
----
1b026e4268d3ee1bd55f1979e9c397ca99bb5864
&lt;span class="s2"&gt;&amp;quot;new blog post, just needs completed code section&amp;quot;&lt;/span&gt; by Matt Makai &lt;span class="o"&gt;(&lt;/span&gt;matthew.makai@gmail.com&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="m"&gt;2017&lt;/span&gt;-11-30 &lt;span class="m"&gt;09&lt;/span&gt;:00:06-05:00
count: &lt;span class="m"&gt;2255&lt;/span&gt; and size: &lt;span class="m"&gt;269&lt;/span&gt;
----
2136d845de6f332505c3df38efcfd4c7d84a45e2
&lt;span class="s2"&gt;&amp;quot;change previous email newsletters list style&amp;quot;&lt;/span&gt; by Matt Makai &lt;span class="o"&gt;(&lt;/span&gt;matthew.makai@gmail.com&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="m"&gt;2017&lt;/span&gt;-11-20 &lt;span class="m"&gt;11&lt;/span&gt;:44:13-05:00
count: &lt;span class="m"&gt;2254&lt;/span&gt; and size: &lt;span class="m"&gt;265&lt;/span&gt;
----
9df077a50027d9314edba7e4cbff6bb05c433257
&lt;span class="s2"&gt;&amp;quot;ensure picture sizes are reasonable&amp;quot;&lt;/span&gt; by Matt Makai &lt;span class="o"&gt;(&lt;/span&gt;matthew.makai@gmail.com&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="m"&gt;2017&lt;/span&gt;-11-14 &lt;span class="m"&gt;13&lt;/span&gt;:29:39-05:00
count: &lt;span class="m"&gt;2253&lt;/span&gt; and size: &lt;span class="m"&gt;256&lt;/span&gt;
----
3f6458c80b15f58a6e6c85a46d06ade72242c572
&lt;span class="s2"&gt;&amp;quot;add databases logos to relational databases pagem&amp;quot;&lt;/span&gt; by Matt Makai &lt;span class="o"&gt;(&lt;/span&gt;matthew.makai@gmail.com&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="m"&gt;2017&lt;/span&gt;-11-14 &lt;span class="m"&gt;13&lt;/span&gt;:28:02-05:00
count: &lt;span class="m"&gt;2252&lt;/span&gt; and size: &lt;span class="m"&gt;270&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The specific commits you see will vary based on the last 5 commits I've
pushed to the GitHub repository, but if you see something like the output
above that is a good sign everything worked as expected.&lt;/p&gt;
&lt;h2&gt;What's next?&lt;/h2&gt;
&lt;p&gt;We just cloned a &lt;a href="/git.html"&gt;Git&lt;/a&gt; repository and used the GitPython 
library to read a slew of data about the repository and all of its commits.&lt;/p&gt;
&lt;p&gt;GitPython can do more than just read data though - it can also create and 
write to Git repositories! Take a look at the 
&lt;a href="http://gitpython.readthedocs.io/en/stable/tutorial.html#modifying-references"&gt;modifying references&lt;/a&gt;
documentation page in the official GitPython tutorial or check back here in
the future when I get a chance to write up a more advanced GitPython 
walkthrough.&lt;/p&gt;
&lt;p&gt;Questions? Let me know via 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/issues"&gt;a GitHub issue ticket on the Full Stack Python repository&lt;/a&gt;, 
on Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;See something wrong in this blog post? Fork
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/171129-first-steps-gitpython.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Thu, 30 Nov 2017 00:00:00 -0500</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2017-11-29:blog/first-steps-gitpython.html</guid></item><item><title>DevOps, Thank You Maintainers and Contributing to Open Source</title><link>https://www.fullstackpython.com/blog/devops-python-maintaining-contributing-open-source.html</link><description>&lt;p&gt;&lt;a href="/blog/devops-continuous-delivery-you.html"&gt;&lt;strong&gt;DevOps, Continuous Delivery... and You&lt;/strong&gt;&lt;/a&gt;
is a blog post with the slides and notes based on a class I taught at
the &lt;a href="http://www.virginia.edu/"&gt;University of Virginia&lt;/a&gt; this past week. The
talk is relevant as a brief introduction to 
&lt;a href="/devops.html"&gt;DevOps&lt;/a&gt; and Continuous Delivery, 
especially for junior developers and less-technical managers of software 
teams. I'm experimenting with the "talk as blog post" style so let me know 
via email or a tweet if you enjoy it and would want to see future technical 
talks in that format.&lt;/p&gt;
&lt;p&gt;Speaking of feedback on projects, 
&lt;a href="https://github.com/jhund/filterrific/issues/147#issuecomment-341867147"&gt;this GitHub issue thread named "&lt;strong&gt;thank you&lt;/strong&gt;"&lt;/a&gt;
is incredible to read. The issue ticket blew up on the front page of Hacker
News as an example of how powerful genuine positive comments can be for 
project maintainers.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://talkpython.fm/episodes/show/132/contributing-to-open-source"&gt;&lt;strong&gt;Contributing to open source&lt;/strong&gt;&lt;/a&gt; 
is a recent &lt;a href="https://talkpython.fm/"&gt;Talk Python to Me&lt;/a&gt; podcast episode in 
the same vein as thanking your maintainer. Working on open source projects
with your own contributions to documentation or simple bug fixes can be a
great way to become a better programmer. I particularly enjoyed the 
recommendations of the panel to cut your teeth on smaller open source projects
rather than trying to jump into a massive codebase like 
&lt;a href="https://github.com/django/django"&gt;Django&lt;/a&gt; or the 
&lt;a href="https://github.com/python/cpython"&gt;CPython&lt;/a&gt; implementation. Take a listen
to that podcast episode if you are new to open source or have been wondering
how to get involved.&lt;/p&gt;
&lt;p&gt;As always, send me an email or &lt;a href="https://github.com/mattmakai/fullstackpython.com/issues"&gt;submit an issue ticket on GitHub&lt;/a&gt; 
to let me know how to improve 
&lt;a href="https://www.fullstackpython.com/"&gt;Full Stack Python&lt;/a&gt; 
as I continue to 
&lt;a href="/table-of-contents.html"&gt;fill in the table of contents&lt;/a&gt; 
with &lt;a href="/change-log.html"&gt;new pages&lt;/a&gt;
and 
&lt;a href="/blog.html"&gt;new tutorials&lt;/a&gt;.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Mon, 13 Nov 2017 00:00:00 -0500</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2017-11-13:blog/devops-python-maintaining-contributing-open-source.html</guid></item><item><title>DevOps, Continuous Delivery... and You</title><link>https://www.fullstackpython.com/blog/devops-continuous-delivery-you.html</link><description>&lt;p&gt;This blog post contains the slides along with a loose transcript and 
additional resources from my technical talk on DevOps and Continuous
Delivery concepts given at my alma mater, the University of Virginia,
to the &lt;a href="https://www.commerce.virginia.edu/ms-mit"&gt;M.S. in Management of Information Technology program&lt;/a&gt; on November 2nd and 4th of 2017.&lt;/p&gt;
&lt;p&gt;Links to learn more about the concepts presented in this talk can
be found in the sidebar and at the bottom of this page.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.001.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Title slide for technical talk."&gt;
Hey folks, my name is &lt;a href="/about-author.html"&gt;Matt Makai&lt;/a&gt;. I am a 
&lt;a href="https://www.twilio.com/blog/2014/02/introducing-developer-evangelist-matt-makai.html"&gt;software developer at Twilio&lt;/a&gt;
and the creator of &lt;a href="https://www.fullstackpython.com/"&gt;Full Stack Python&lt;/a&gt;,
which over 125,000 developers read each month to learn how to 
&lt;a href="/web-development.html"&gt;build&lt;/a&gt;, &lt;a href="/deployment.html"&gt;deploy&lt;/a&gt; and 
&lt;a href="/devops.html"&gt;operate&lt;/a&gt; &lt;a href="/why-use-python.html"&gt;Python-based applications&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.004.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="What's the point of Agile?"&gt;
You've talked about using the Agile software development methodology
on your teams, but what's the purpose? Why does Agile development matter 
to you and your organization?&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.005.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Cargo ship with containers."&gt;
Agile matters because it allows you to ship more code, faster than 
traditional "waterfall" methodology approaches. &lt;/p&gt;
&lt;p&gt;Shipping is a common allegory in software development nowadays, because 
code that is not in production, in the hands of your users, doesn't create
value for anyone.&lt;/p&gt;
&lt;p&gt;If code is not running in production, it's not creating value. New
code created by your Agile development teams every couple of weeks does
not create more value until it is executing in production.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.006.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Docker logo."&gt;
Shipping code is so important to high functioning companies that the
maritime theme is used across all sorts of projects, including in the Docker
logo.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.007.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Kubernetes logo."&gt;
As well as in the Kubernetes logo in the form of a ship steering wheel.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.008.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Agile sprints need to ship code into production to create anything of value."&gt;
Here is a super high-level diagram of the ideal scenario we need for
Agile development teams. Create working code and get it shipped as soon
as possible into production.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.009.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Move fast and break things."&gt;
Facebook's internal motto used to be "Move fast and break things." They 
thought that if you aren't breaking things then you aren't moving fast 
enough. &lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.010.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="If you do not have the right processes and tools in place eventually production will break."&gt;
And eventually if you're constantly shipping to production and you do not
have the appropriate processes and tools in place, your applications
will break. The breakage has nothing to do with the Agile methodology
itself.&lt;/p&gt;
&lt;p&gt;Your team and organization will come to a fork in the road when you
end up with a broken environment.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.011.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Fight the urge to put manual processes in place that slow you down. You must automate."&gt;
Traditionally, organizations have tried to prevent breakage by putting
more manual tools and processes in place. Manual labor slows... down...
your... ability... to... execute.&lt;/p&gt;
&lt;p&gt;This is one path provided by the fork in the road. Put your "Enterprise
Change Review Boards" in place. Require production sign-offs by some 
Executive Vice President who has never written a line of code in his life.
Put several dozen "technical architects" in a room together to argue over
who gets to deploy their changes to production that month.&lt;/p&gt;
&lt;p&gt;The manual path is insanity. Eventually the best developers in your
organization will get frustrated and leave. Executives will ask why
nothing ever gets done. Why does it take our organization three years
to ship a small change to a critical application?&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.012.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Some teams try to get around the production problem by shipping to dev, but they still are not creating value."&gt;
Some development teams try to get around the manual production challenges
by shipping everything to a development environment. The dev environment is
under their control.&lt;/p&gt;
&lt;p&gt;But what's the huge glaring problem in this situation?&lt;/p&gt;
&lt;p&gt;If you are not shipping to production, then you are not creating any value
for your users. The teams have made a rational decision to ship to development
but the organization still suffers due to the manual controls.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.013.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="This session is about DevOps and Continuous Delivery."&gt;
The problems we are talking about are created by the Agile methodology
because they become acute when your development team is producing code at
high velocity. Once code is created faster, you need a way to reliably,
consistently put the code into production so that it can create value for
its users.&lt;/p&gt;
&lt;p&gt;DevOps and Continuous Delivery are the broad terms that encompass how to
reliably ship code to production and operate it when the code is running in 
production.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.014.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="What DevOps is NOT."&gt;
We are going to use the terms "DevOps" and "Continuous Delivery" a lot today,
so let's start by defining what they mean. In fact, the term "DevOps" has 
already accumulated a lot of buzzword baggage, so we'll start by defining
what DevOps is &lt;em&gt;not&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;First,DevOps is not a new role. If you go hire a bunch of people and call them
"DevOps engineers" then sit them in the middle of your developers and system
admin/ops folks, you are going to have a bad time. You just added a new layer
between the two groups you need to pull closer together.&lt;/p&gt;
&lt;p&gt;Second, DevOps is not a specific tool or application. You do not need to
use Docker or Puppet to do DevOps in your organization. The processes that
make DevOps work are made much easier by some tools such as cloud platforms
where infrastructure is transient, but even those platforms are not required
to do DevOps right.&lt;/p&gt;
&lt;p&gt;Third, DevOps is not tied to a specific programming language ecosystem. You
do not need to use Node.js or Ruby on Rails. You can still use DevOps
in a COBOL- or J2EE-only organization.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.015.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="What DevOps IS."&gt;
With those misconceptions out of the way, let's talk about what DevOps IS.
First, at the risk of being way too obvious, DevOps is the combination of the
two words Development and Operations. This combination is not a random
pairing, it's an intentional term. &lt;/p&gt;
&lt;p&gt;Second, DevOps means your application developers handle operations. Not 
necessarily &lt;em&gt;all&lt;/em&gt; operations work, but ops work that deals with the code they
write and deploy as part of their sprints. The developers also will likely
become intimately familiar with the underlying infrastructure such as the
web application servers, &lt;a href="/web-servers.html"&gt;web servers&lt;/a&gt; and 
&lt;a href="/deployment.html"&gt;deployment&lt;/a&gt; code for 
&lt;a href="/configuration-management.html"&gt;configuration management&lt;/a&gt; tools.&lt;/p&gt;
&lt;p&gt;Third, DevOps allows your organization to be more efficient in handling
issues by ensuring the correct person is handling errors and application
failures.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.016.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="What Continuous Delivery is."&gt;
We are not going to go through Continuous Delivery (CD) by defining what it is
not, but there are a couple bits to say about it. First, CD is a collection of 
engineering practices aimed at automating the delivery of code from 
version control check-in until it is running in a production environment.&lt;/p&gt;
&lt;p&gt;The benefit of the automation CD approach is that your organization will have
far greater confidence in the code running in production even as the code
itself changes more frequently with every deployment.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.017.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Move fast and BUILD things."&gt;
Facebook's original motto changed a few years ago to "Move Fast and Build 
Things" because they realized that breaking production was not a byproduct
of moving fast, it was a result of immature organizational processes and
tools. DevOps and Continuous Delivery are why organizations can now deploy
hundreds or thousands of times to production every day but have increasing,
not decreasing, confidence in their systems as they continue to move faster.&lt;/p&gt;
&lt;p&gt;Let's take a look at a couple of example scenarios that drive home what
DevOps and CD are all about, as well as learn about some of the processes, 
concepts and tools that fall in this domain.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.018.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="San Francisco skyline at night."&gt;
Here is a beautiful evening picture of the city I just moved away from, San 
Francisco.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.019.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Twilio billboard, ask your developer!"&gt;
The company I work for, &lt;a href="https://www.twilio.com/"&gt;Twilio&lt;/a&gt; is located in
San Francisco. If you ever fly into the SFO airport and catch a ride towards
downtown, you will see our billboard on the right side of the road. &lt;/p&gt;
&lt;p&gt;Twilio makes it easy for software developers to add communications, such as
phone calling, messaging and video, into their applications. We are a 
telecommunications company built with the power of software that eliminates
the need for customers to buy all the expensive legacy hardware that they
used to have to acquire. As a telecomm company, we can never go down, or
our customers are hosed and then our business is hosed.&lt;/p&gt;
&lt;p&gt;However, we have had challenges in our history that have forced us to 
confront the fork in the road between manual processes and moving faster via 
trust in our automation.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.020.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="August 2013."&gt;
In August 2013, Twilio faced an infrastructure failure.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.021.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="How customers pay for Twilio."&gt;
First, some context. When a developer signs up for Twilio, she puts some 
credit on their account and the credit is drawn upon by making phone calls,
sending messages and such. When credit runs low we can re-charge your card
so you get more credit.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.022.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Hacker News post on Twilio not billing correctly."&gt;
There was a major production issue with the recurring charges in August 2013.
Our engineers were alerted to the errors and the issue blew up on the top of
&lt;a href="https://news.ycombinator.com/"&gt;Hacker News&lt;/a&gt;, drawing widespread atttention.&lt;/p&gt;
&lt;p&gt;So now there is a major production error... what do we do? &lt;/p&gt;
&lt;p&gt;(Reader note: this section is primarily audience discussion based on their 
own experiences handling these difficult technical situations.)&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.023.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Billing incident update blog post."&gt;
One step is to figure out when the problem started and whether or not it
is over. If it's not over, triage the specific problems and start 
communicating with customers. Be as accurate and transparent as possible.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.024.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Redis logo."&gt;
The specific technical issue in this case was due to our misconfiguration of
Redis instances.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.025.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Text that reads 'Root cause?'"&gt;
We know the particular technical failure was due to our Redis mishandling,
but how do we look past the specific bit and get to a broader understanding
of the processes that caused the issue?&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.026.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Billing incident response from Twilio developer evangelist."&gt;
Let's take a look at the resolution of the situation and then learn about
the concepts and tools that could prevent future problems.&lt;/p&gt;
&lt;p&gt;In this case, we communicated with our customers as much about the problem
as possible. As a developer-focused company, we were fortunate that by being
transparent about the specific technical issue, many of our customers gained
respect for us because they had also faced similar misconfigurations in their 
own environments.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.027.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Twilio status page."&gt;
Twilio became more transparent with the status of services, especially with
showing partial failures and outages.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.028.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Twilio number of production deployments."&gt;
Twilio was also deliberate in avoiding the accumulation of manual processes
and controls that other organizations often put in place after failures. We
doubled down on resiliency through automation to increase our ability to
deploy to production.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.029.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Text that reads 'tools and concepts'."&gt;
What are some of the tools and concepts we use at Twilio to prevent future
failure scenarios?&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.030.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Eventually you ship code into production that breaks your application."&gt;
If you do not have the right tools and processes in place, eventually you
end up with a broken production environment after shipping code. What is
one tool we can use to be confident that the code going into production is
not broken?&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.031.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Text that reads 'automated testing' with example code coverage in the background."&gt;
Automated &lt;a href="/testing.html"&gt;testing&lt;/a&gt;, in its many forms, such as unit testing, 
integration testing, security testing and performance testing, helps to 
ensure the integrity of the code. You need to automate because manual 
testing is too slow.&lt;/p&gt;
&lt;p&gt;Other important tools that fall into the automated testing bucket but are
not traditionally thought of as a "test case" include code coverage and
&lt;a href="/code-metrics.html"&gt;code metrics&lt;/a&gt; (such as Cyclomatic Complexity).&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.032.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Automated tests in dev only deploy to production when they are successful."&gt;
Awesome, now you only deploy to production when a big batch of automated
test cases ensure the integrity of your code. All good, right?&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.033.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Bugs can still occur in production."&gt;
Err, well no. Stuff can still break in production, espcially in environments
where for various reasons you do not have the same exact data in test
that you do in production. Your automated tests and code metrics will
simply not catch every last scenario that could go wrong in production.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.034.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Text that reads 'monitoring and alerting' with New Relic dashboard in the background."&gt;
When something goes wrong with your application, you need monitoring to
know what the problem is, and alerting to tell the right folks. Traditionally,
the "right" people were in operations. But over time many organizations 
realized the ops folks ended up having to call the original application 
developers who wrote the code that had the problem. &lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.035.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="When something breaks in prod, your developers know about it and can fix the problem."&gt;
A critical piece to DevOps is about ensuring the appropriate developers 
are carrying the pagers. It sucks to carry the pager and get woken up in the
middle of the night, but it's a heck of a lot easier to debug the code that
your team wrote than if you are a random ops person who's never seen the
code before in her life.&lt;/p&gt;
&lt;p&gt;Another by-product of having application developers carry the "pagers" for
alerts on production issues is that over time the code they write is more
defensive. Errors are handled more appropriately, because otherwise you know
something will blow up on you later on at a less convenient time.  &lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.036.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="When production is running smoothly with many tests, do that increase the chance of black swan-type events?"&gt;
Typically you find though that there are still plenty of production errors
even when you have defensive code in place with a huge swath of the most 
important parts of your codebase being constantly tested.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.037.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Text that reads 'Chaos engineering' with the chaos engineering monkey logo in the background."&gt;
That's where a concept known as "chaos engineering" can come in. Chaos
engineering breaks parts of your production environment on a schedule and
even unscheduled basis. This is a very advanced technique- you are not going
to sell this in an environment that has no existing automated test coverage
or appropriate controls in place.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.038.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Chaos engineering introduces intentional failures in your infrastructure both on a scheduled and unschedule basis."&gt;
By deliberately introducing failures, especially during the day when your
well-caffeinated team can address the issues and put further safeguards in
place, you make your production environment more resilient.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.039.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Text that reads '1. other peoples money' with money in the background."&gt;
We talked about the failure in Twilio's payments infrastructure several years 
ago that led us to ultimately become more resilient to failure by putting 
appropriate automation in place.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.040.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Text that reads '2. other peoples lives' with people in the background."&gt;
Screwing with other people's money is really bad, and so is messing with
people's lives.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.041.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Text that reads 'War on Terror' with an exploded vehicle in the background."&gt;
Let's discuss a scenario where human lives were at stake. &lt;/p&gt;
&lt;p&gt;To be explicit about this next scenario, I'm only going to talk about public 
information, so my cleared folks in the audience can relax.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.042.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="U.S. military and civilian casualties in Iraq."&gt;
During the height of U.S forces' Iraq surge in 2007, more improvised explosive
devices were killing and maiming soldiers and civilians than ever before. It
was an incredible tragedy that contributed to the uncertainty of the time in
the country.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.043.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Biometrics devices."&gt;
However, efforts in biometrics were one part of the puzzle that helped to
prevent more attacks, as shown in this picture from General Petraeus' report
to Congress.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.044.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Eclipse IDE."&gt;
One major challenge with the project was a terrible manual build process that
literally involved clicking buttons in an integrated 
&lt;a href="/development-environments.html"&gt;development environment&lt;/a&gt; to create the
application artifacts. The process was too manual and the end result was that
the latest version of the software took far too long to get into production.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.045.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="The situation did not have reasonable deployments to dev or to production."&gt;
We did not have automated deployments to a development environment, staging
or production.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.046.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Start somewhere, automate your deployments to dev environment."&gt;
Our team had to start somewhere, but with a lack of approved tools, all we
had available to us was shell scripts. But shell scripts were a start. We were
able to make a very brittle but repeatable, automated deployment process to
a development environment?&lt;/p&gt;
&lt;p&gt;There is still a huge glaring issue though: until the code is actually 
deployed to production it does not provide any value for the users.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.047.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Some environments have tricky issues with automated prod deployments like disconnected networks."&gt;
In this case, we could never fully automate the deployment because we had to
burn to a CD before moving to a physically different computer network. The
team could automate just about everything else though, and that really mattered
for iteration and speed to deployment.&lt;/p&gt;
&lt;p&gt;You do the best you can with the tools at your disposal.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.048.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Text that reads 'Tools and concepts'."&gt;
What are the tools and concepts behind automating deployments?&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.049.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Several development teams commit to a Git repository."&gt;
Source code is stored in a 
&lt;a href="/source-control.html"&gt;source control (or version control)&lt;/a&gt; repository.
Source control is the start of the automation process, but what do we need
to get the code into various environments using a repeatable, automated
process?&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.050.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Text that reads 'continuous integration' with a screenshot of Jenkins dashboard in the background."&gt;
This is where &lt;a href="/continuous-integration.html"&gt;continuous integration&lt;/a&gt; comes
in. Continuous integration takes your code from the version control system,
builds it, tests it and calculate the appropriate code metrics before the
code is deployed to an environment.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.051.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Add a continuous integration server to build the code that is committed to your source control repository."&gt;
Now we have a continuous integration server hooked up to source control, but
this picture still looks odd.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.052.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="How do we automate the building of these environments and the deployments themselves?"&gt;
Technically, continuous integration does not handle the details of the build
and how to configure individual execution environments.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.053.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Text that reads 'configuration management' with a screenshot of Ansible AWX in the background."&gt;
&lt;a href="/configuration-management.html"&gt;Configuration management&lt;/a&gt; tools handle the
setup of application code and environments.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.054.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Agile sprints deliver code to a development environment and then automate the deployment into production."&gt;
Those two scenarios provided some context for why DevOps and Continuous 
Delivery matter to organizations in varying industries. When you have high
performing teams working via the Agile development methodology, you will
encounter a set of problems that are not solvable by doing Agile "better". You
need the tools and concepts we talked about today as well as a slew of other
engineering practices to get that new code into production.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.055.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Review list of continuous delivery tools."&gt;
The tools and concepts we covered today were 
&lt;a href="/testing.html"&gt;automated testing&lt;/a&gt;, &lt;a href="/monitoring.html"&gt;monitoring&lt;/a&gt;, chaos
engineering, &lt;a href="/continuous-integration.html"&gt;continuous integration&lt;/a&gt; and
&lt;a href="/configuration-management.html"&gt;configuration management&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.056.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="A list of more concepts and tools for continuous delivery."&gt;
There are many other practices you will need as you continue your journey.
You can learn about 
&lt;a href="/table-of-contents.html"&gt;all of them on Full Stack Python&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/171101-devops-cd-you/devops-cd-you.057.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Thank you slide."&gt;&lt;/p&gt;
&lt;p&gt;That's all for today. My name is &lt;a href="/about-author.html"&gt;Matt Makai&lt;/a&gt;
and I'm a software developer at &lt;a href="/twilio.html"&gt;Twilio&lt;/a&gt; and the
author of &lt;a href="https://www.fullstackpython.com/"&gt;Full Stack Python&lt;/a&gt;.
Thank you very much.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Additional resources to learn more about the following topics can be found
on their respective pages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/deployments.html"&gt;Deployments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/continuous-integration.html"&gt;Continuous integration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/serverless.html"&gt;Serverless computing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/aws-lambda.html"&gt;AWS Lambda&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/static-site-generator.html"&gt;Static site generators&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/monitoring.html"&gt;Monitoring&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/devops.html"&gt;DevOps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/configuration-management.html"&gt;Configuration management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/platform-as-a-service.html"&gt;Platform-as-a-Service (PaaS)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/docker.html"&gt;Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/web-application-security.html"&gt;Web application security&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/testing.html"&gt;Testing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/source-control.html"&gt;Source control&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/git.html"&gt;Git&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/code-metrics.html"&gt;Code metrics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/no-sql-datastore.html"&gt;NoSQL&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Sun, 05 Nov 2017 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2017-11-01:blog/devops-continuous-delivery-you.html</guid></item><item><title>PyDev of the Week, Django 2.0 and Twilio Voices</title><link>https://www.fullstackpython.com/blog/pydev-week-django-2-twilio-voices.html</link><description>&lt;p&gt;&lt;a href="https://www.blog.pythonlibrary.org/category/pydevoftheweek/"&gt;&lt;strong&gt;PyDev of the Week&lt;/strong&gt;&lt;/a&gt; 
is a developer interview series by 
&lt;a href="https://github.com/driscollis"&gt;Mike Driscoll&lt;/a&gt; that asks Python programmers 
how they started coding, the projects they're working on and what advice
they have for beginners. Mike was kind enough to 
&lt;a href="https://www.blog.pythonlibrary.org/2017/10/30/pydev-of-the-week-matthew-makai/"&gt;interview me in the latest PyDev of the Week post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the PyDev interview I gave a big shoutout to the fine folks working on the
&lt;a href="/django.html"&gt;Django project&lt;/a&gt;, which is 
currently beta testing the major upcoming
&lt;a href="https://docs.djangoproject.com/en/2.0/releases/2.0/"&gt;&lt;strong&gt;Django 2.0 release&lt;/strong&gt;&lt;/a&gt;.
Django 2.0 is the first release to support only Python 3, specifically 
&lt;a href="https://docs.djangoproject.com/en/2.0/releases/2.0/#python-compatibility"&gt;Python 3.4, 3.5 and 3.6&lt;/a&gt;. 
The &lt;a href="https://www.djangoproject.com/weblog/2017/oct/16/django-20-beta-1-released/"&gt;Django 2.0 beta 1 release&lt;/a&gt; 
needs
&lt;a href="https://code.djangoproject.com/query?version=2.0&amp;amp;col=id&amp;amp;col=summary&amp;amp;col=status&amp;amp;col=owner&amp;amp;col=type&amp;amp;col=component&amp;amp;col=version&amp;amp;desc=1&amp;amp;order=id"&gt;feedback on bugs in the issue tracker&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One bit I missed calling out in the PyDev interview is a new program I'm 
working on called &lt;a href="http://www.twiliovoices.com"&gt;&lt;strong&gt;Twilio Voices&lt;/strong&gt;&lt;/a&gt;. Twilio
Voices pays developers to write great technical tutorials for the 
&lt;a href="https://www.twilio.com/blog"&gt;Twilio blog&lt;/a&gt;. We have already published a slew 
of awesome Python walkthroughs such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.twilio.com/blog/2017/06/hacked-my-universitys-registration-system-python-twilio.html"&gt;How I Hacked My University's Registration System with Twilio SMS&lt;/a&gt; by Samuel Taylor&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.twilio.com/blog/2017/08/geospatial-analysis-python-geojson-geopandas.html"&gt;Getting Started on Geospatial Analysis with Python, GeoJSON and GeoPandas&lt;/a&gt; by Lesley Cordero&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.twilio.com/blog/2017/08/json-serialization-in-python-using-serpy.html"&gt;JSON Serialization in Python using serpy&lt;/a&gt; by Siddhant Goel&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.twilio.com/blog/2017/04/wedding-at-scale-how-i-used-twilio-python-and-google-to-automate-my-wedding.html"&gt;Wedding at Scale: How I Used Twilio, Python and Google to Automate My Wedding&lt;/a&gt; by Thomas Curtis&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.twilio.com/blog/2017/09/never-forget-friends-birthday-python-flask-twilio.html"&gt;Never Forget A Friend’s Birthday with Python, Flask and Twilio&lt;/a&gt;
  by Bob Belderbos&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Take a look at the &lt;a href="http://www.twiliovoices.com/"&gt;Twilio Voices page&lt;/a&gt; and 
submit the interest form if you want to get paid to write code tutorials 
in any programming language of your choice. We'll take care of promoting your 
posts to the broader developer community.&lt;/p&gt;
&lt;p&gt;As always, send me an email or &lt;a href="https://github.com/mattmakai/fullstackpython.com/issues"&gt;submit an issue ticket on GitHub&lt;/a&gt; 
to let me know how to improve 
&lt;a href="https://www.fullstackpython.com/"&gt;Full Stack Python&lt;/a&gt; 
as I continue to 
&lt;a href="/table-of-contents.html"&gt;fill in the table of contents&lt;/a&gt; 
with &lt;a href="/change-log.html"&gt;new pages&lt;/a&gt;
and 
&lt;a href="/blog.html"&gt;new tutorials&lt;/a&gt;.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Mon, 30 Oct 2017 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2017-10-30:blog/pydev-week-django-2-twilio-voices.html</guid></item><item><title>PyCon US 2018 CFP, Python Bytes and Pelican</title><link>https://www.fullstackpython.com/blog/pycon-us-2018-cfp-python-bytes-pelican.html</link><description>&lt;p&gt;&lt;a href="https://us.pycon.org/2018/"&gt;&lt;strong&gt;PyCon US 2018&lt;/strong&gt;&lt;/a&gt; is coming up in Cleveland, Ohio
on May 9th-17th. The 
&lt;a href="https://us.pycon.org/2018/speaking/talks/"&gt;call for proposals (CFP)&lt;/a&gt; 
went live in the past few days so now is the time to sharpen your keyboards 
and get yourself into the proposal writing zone. &lt;/p&gt;
&lt;p&gt;When you start working on your proposal, here are some awesome resources 
on building a great tech talk, public speaking and describing your session
by writing a solid proposal:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://emptysqua.re/blog/seven-tips-for-pycon/"&gt;Seven tips to get into PyCon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://akaptur.com/blog/2014/09/11/rejected-pycon-proposals/"&gt;Rejected PyCon proposals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hynek.me/articles/speaking/"&gt;On conference speaking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.oreilly.com/conferences/sample_proposals.html"&gt;Example conference proposals&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Looking forward to seeing you all at PyCon US in Cleveland early next year!&lt;/p&gt;
&lt;p&gt;Speaking of folks who will definitely be at PyCon, 
&lt;a href="https://pythonbytes.fm/"&gt;&lt;strong&gt;Python Bytes&lt;/strong&gt;&lt;/a&gt; is an awesome weekly Python
podcast by &lt;a href="https://twitter.com/mkennedy"&gt;Michael Kennedy&lt;/a&gt; of 
&lt;a href="https://talkpython.fm/"&gt;Talk Python to Me&lt;/a&gt; and 
&lt;a href="https://twitter.com/brianokken"&gt;Brian Okken&lt;/a&gt; of 
&lt;a href="http://testandcode.com/"&gt;Test and Code&lt;/a&gt;. Michael and Brian teamed up to
host and produce Python Bytes. I really enjoy listening to the rapid-fire 
discussion of several programming topics within a single podcast. &lt;/p&gt;
&lt;p&gt;Michael and Brian were kind enough to invite me on as a co-host for the
&lt;a href="https://pythonbytes.fm/episodes/show/38/hacking-classic-nintendo-games-with-python"&gt;38th episode "Hacking Classic Nintendo Games with Python"&lt;/a&gt;
while Michael was on vacation. 
The &lt;a href="https://pythonbytes.fm/episodes/show/39/the-new-pypi"&gt;following episode "The new PyPI"&lt;/a&gt; 
also had a great discussion of the 
&lt;a href="/object-relational-mappers-orms.html"&gt;Object-Relational Mappers (ORMs) page on Full Stack Python&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One of the projects I talked about on the Python Bytes podcast episode that
I guest hosted is &lt;a href="http://docs.getpelican.com/en/stable/"&gt;&lt;strong&gt;Pelican&lt;/strong&gt;&lt;/a&gt;, the
&lt;a href="/static-site-generator.html"&gt;static site generator&lt;/a&gt; 
that turns &lt;a href="/markdown.html"&gt;Markdown&lt;/a&gt; and some 
&lt;a href="/jinja2.html"&gt;Jinja templates&lt;/a&gt; into the 
Full Stack Python site. Here are some additional tutorials and resources
to get started using Pelican if you've been meaning to build a static site 
yourself:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="/blog/generating-static-websites-pelican-jinja2-markdown.html"&gt;How to Create Your First Static Site with Pelican and Jinja2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/pelican.html"&gt;An overview of the Pelican static site generator&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;One last bit: &lt;a href="https://training.talkpython.fm/courses/explore_entrepreneurs/python-for-entrepreneurs-build-and-launch-your-online-business"&gt;Python for Entrepreneurs&lt;/a&gt; 
is now fully released with all 20 hours of content. Got non-developer 
friends who wants you to build them an app? Send them the 
&lt;a href="https://training.talkpython.fm/courses/explore_entrepreneurs/python-for-entrepreneurs-build-and-launch-your-online-business"&gt;Python for Entrepreneurs course&lt;/a&gt; 
so they can stop bugging you and build it themselves :)&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Mon, 09 Oct 2017 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2017-10-09:blog/pycon-us-2018-cfp-python-bytes-pelican.html</guid></item><item><title>How to Monitor Python Web Applications</title><link>https://www.fullstackpython.com/blog/monitor-python-web-applications.html</link><description>&lt;p&gt;A quick way to check for errors and issues in your operational 
&lt;a href="/web-development.html"&gt;Python web application&lt;/a&gt; is to drop-in one of many
awesome hosted &lt;a href="/monitoring.html"&gt;monitoring&lt;/a&gt; tools.&lt;/p&gt;
&lt;p&gt;Let's learn to quickly add &lt;a href="https://rollbar.com"&gt;Rollbar monitoring&lt;/a&gt;
to a web app to visualize when our application is running properly and
when it has issues. This tutorial will use &lt;a href="/bottle.html"&gt;Bottle&lt;/a&gt; as the 
example &lt;a href="/web-frameworks.html"&gt;web framework&lt;/a&gt; along with Rollbar as the 
monitoring service but you can also check out the list of other tools 
on the &lt;a href="/monitoring.html"&gt;monitoring page&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Our Tools&lt;/h2&gt;
&lt;p&gt;We can use either &lt;a href="/python-2-or-3.html"&gt;Python 2 or 3&lt;/a&gt; to build this
tutorial, but Python 3 is &lt;em&gt;strongly&lt;/em&gt; recommended for all new applications. 
&lt;a href="https://www.python.org/downloads/release/python-362/"&gt;Python 3.6.2&lt;/a&gt; 
was used to build this tutorial. We will also use the following 
&lt;a href="/application-dependencies.html"&gt;application dependencies&lt;/a&gt; throughout
the post: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pip.pypa.io/en/stable/"&gt;pip&lt;/a&gt; and 
  &lt;a href="https://virtualenv.pypa.io/en/latest/"&gt;virtualenv&lt;/a&gt;, which come installed 
  with Python 3, to install and isolate the Bottle and Rollbar libraries 
  from your other projects&lt;/li&gt;
&lt;li&gt;&lt;a href="/bottle.html"&gt;Bottle&lt;/a&gt; web framework, 
  &lt;a href="https://bottlepy.org/docs/0.12/"&gt;version 0.12.13&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rollbar.com/docs/notifier/pyrollbar/"&gt;pyrollbar&lt;/a&gt; monitoring 
  instrumentation library,
  &lt;a href="https://github.com/rollbar/pyrollbar/tree/v0.13.13"&gt;version 0.13.13&lt;/a&gt;
  in Bottle applications so pyrollbar can report on all errors&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://rollbar.com/"&gt;free Rollbar account&lt;/a&gt; where we will send error
  data and view it when it is captured&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you need help getting your 
&lt;a href="/development-environments.html"&gt;development environment&lt;/a&gt; configured
before running this code, take a look at
&lt;a href="/blog/python-3-bottle-gunicorn-ubuntu-1604-xenial-xerus.html"&gt;this guide for setting up Python 3 and Bottle on Ubuntu 16.04 LTS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;All code in this blog post is available open source under the MIT license 
on GitHub under the 
&lt;a href="https://github.com/fullstackpython/blog-code-examples"&gt;monitor-python-bottle-apps directory of the blog-code-examples repository&lt;/a&gt;. 
Use and abuse the source code as you desire for your own applications.&lt;/p&gt;
&lt;h2&gt;Installing Dependencies&lt;/h2&gt;
&lt;p&gt;Create a new virtual environment for this project using the following
command. I recommend keeping a separate directory for virtualenvs under 
&lt;code&gt;~/Envs/&lt;/code&gt; so you will know where all your project virtualenvs are located.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python3 -m venv monitorpython
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Activate the virtualenv with the &lt;code&gt;activate&lt;/code&gt; shell script:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt; monitorpython/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The command prompt will change after activating the virtualenv:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170926-monitor-python-web-apps/activate-python-virtualenv.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Activating our Python virtual environment on the command line."&gt;&lt;/p&gt;
&lt;p&gt;Remember that you need to activate your virtualenv in every new terminal 
window where you want to use the virtualenv to run the project.&lt;/p&gt;
&lt;p&gt;We can now install Bottle and Rollbar into the activated
virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install bottle==0.12.13 rollbar==0.13.13
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Look for output like the following to confirm the 
dependencies installed correctly.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;Installing&lt;/span&gt; &lt;span class="nv"&gt;collected&lt;/span&gt; &lt;span class="nv"&gt;packages&lt;/span&gt;: &lt;span class="nv"&gt;bottle&lt;/span&gt;, &lt;span class="nv"&gt;urllib3&lt;/span&gt;, &lt;span class="nv"&gt;certifi&lt;/span&gt;, &lt;span class="nv"&gt;idna&lt;/span&gt;, &lt;span class="nv"&gt;chardet&lt;/span&gt;, &lt;span class="nv"&gt;requests&lt;/span&gt;, &lt;span class="nv"&gt;six&lt;/span&gt;, &lt;span class="nv"&gt;rollbar&lt;/span&gt;
  &lt;span class="nv"&gt;Running&lt;/span&gt; &lt;span class="nv"&gt;setup&lt;/span&gt;.&lt;span class="nv"&gt;py&lt;/span&gt; &lt;span class="nv"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;bottle&lt;/span&gt; ... &lt;span class="nv"&gt;done&lt;/span&gt;
    &lt;span class="nv"&gt;Running&lt;/span&gt; &lt;span class="nv"&gt;setup&lt;/span&gt;.&lt;span class="nv"&gt;py&lt;/span&gt; &lt;span class="nv"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;rollbar&lt;/span&gt; ... &lt;span class="nv"&gt;done&lt;/span&gt;
    &lt;span class="nv"&gt;Successfully&lt;/span&gt; &lt;span class="nv"&gt;installed&lt;/span&gt; &lt;span class="nv"&gt;bottle&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;.&lt;span class="mi"&gt;12&lt;/span&gt;.&lt;span class="mi"&gt;13&lt;/span&gt; &lt;span class="nv"&gt;certifi&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2017&lt;/span&gt;.&lt;span class="mi"&gt;7&lt;/span&gt;.&lt;span class="mi"&gt;27&lt;/span&gt;.&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nv"&gt;chardet&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;.&lt;span class="mi"&gt;0&lt;/span&gt;.&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="nv"&gt;idna&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;.&lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="nv"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;.&lt;span class="mi"&gt;18&lt;/span&gt;.&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="nv"&gt;rollbar&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;.&lt;span class="mi"&gt;13&lt;/span&gt;.&lt;span class="mi"&gt;13&lt;/span&gt; &lt;span class="nv"&gt;six&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;.&lt;span class="mi"&gt;11&lt;/span&gt;.&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="nv"&gt;urllib3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;.&lt;span class="mi"&gt;22&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We have our dependencies ready to go so now we can build
our Python web application.&lt;/p&gt;
&lt;h2&gt;Our Python Web App&lt;/h2&gt;
&lt;p&gt;Create a folder for your project named &lt;code&gt;monitor-python-apps&lt;/code&gt;. &lt;code&gt;cd&lt;/code&gt; into
the folder and then create a file named &lt;code&gt;app.py&lt;/code&gt; with the following
code.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;bottle&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;bottle&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;


&lt;span class="n"&gt;TEMPLATE_STRING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt; &amp;lt;head&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;  &amp;lt;title&amp;gt;Full Stack Python Web App&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt; &amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt; &amp;lt;body&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;  &amp;lt;h1&amp;gt;{{ h1 }}&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt; &amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;MIN_MSG_LENGTH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;


&lt;span class="nd"&gt;@route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;lt;msg&amp;gt;/&amp;quot;&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;show_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Display a message if the msg value is greater than 2 characters&lt;/span&gt;
&lt;span class="sd"&gt;    in the path.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;valid_length&lt;/span&gt; &lt;span class="o"&gt;=&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;msg&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;MIN_MSG_LENGTH&lt;/span&gt;
    &lt;span class="n"&gt;valid_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;^[a-z\-]+$&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;valid_length&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;valid_name&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;template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TEMPLATE_STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;msg&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;error_msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Sorry, only alpha characters and hyphens allowed.&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error_msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;bottle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;localhost&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The above application code has a few standard Bottle imports so we can
create a Bottle web app and handle URL routes. &lt;/p&gt;
&lt;p&gt;We have a single function, &lt;code&gt;show_message&lt;/code&gt;, that handles a single Bottle 
URL route. &lt;code&gt;show_message&lt;/code&gt; checks if the URL path contains only alphabetic 
characters and hyphens for a message to display. If the message passes 
the conditions then a page is rendered with that message
in an &lt;code&gt;h1&lt;/code&gt; element. If &lt;code&gt;msg&lt;/code&gt; does not pass the condition test then an 
exception is thrown that only alpha characters and hyphens are allowed.&lt;/p&gt;
&lt;p&gt;Save &lt;code&gt;app.py&lt;/code&gt; and we can run our code. Execute &lt;code&gt;app.py&lt;/code&gt; using the &lt;code&gt;python&lt;/code&gt;
command as follows (make sure your virtualenv is still activated in the
terminal where you are running this command):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python app.py
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The Bottle development server should start up and display a few lines
of output.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170926-monitor-python-web-apps/run-bottle-app.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Run the local Bottle development server."&gt;&lt;/p&gt;
&lt;p&gt;Try to access a URL with a path that contains only alphabetic characters and 
hyphens, such as 
&lt;a href="http://localhost:8080/hello-world/"&gt;localhost:8080/hello-world/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170926-monitor-python-web-apps/localhost-hello-world.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Testing at /hello-world/ returns an HTTP 200 response."&gt;&lt;/p&gt;
&lt;p&gt;The application was successful in displaying "hello-world" but what if we
try a URL that contains numbers in addition to the alphabetic characters,
such as
&lt;a href="http://localhost:8080/fullstackpython/"&gt;localhost:8080/fullstackpython123/&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170926-monitor-python-web-apps/localhost-500.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="We receive a 500 error when numbers are added to the URL."&gt;&lt;/p&gt;
&lt;p&gt;An HTTP 500 error. That is surely not a good user experience.&lt;/p&gt;
&lt;p&gt;The 500 error is obvious to us right now because we are 
testing the application locally during development. However, what happens 
when the app is deployed and a user gets the error in their own web 
browser? They will likely quit out of frustration and you will never 
know what happened unless you add some error tracking and application 
monitoring.&lt;/p&gt;
&lt;p&gt;Time to modify our code to add Rollbar to report errors that occur.&lt;/p&gt;
&lt;h2&gt;Monitoring for Errors with Rollbar&lt;/h2&gt;
&lt;p&gt;Go to the &lt;a href="https://rollbar.com/"&gt;Rollbar homepage in your browser&lt;/a&gt; 
to add their tool to our Bottle app.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170926-monitor-python-web-apps/rollbar-homepage.jpg" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="The Rollbar homepage in the Chrome web browser."&gt;&lt;/p&gt;
&lt;p&gt;Click the "Sign Up" button in the upper right-hand corner. Enter your 
email address, a username and the password you want on the sign up page.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170926-monitor-python-web-apps/sign-up-page.jpg" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Enter your account information on the sign up page."&gt;&lt;/p&gt;
&lt;p&gt;After the sign up page you will see the onboarding flow where you can
enter a project name and select a programming language. For the project
name type in "Full Stack Python" then select that you are monitoring a 
Python app.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170926-monitor-python-web-apps/create-new-project.jpg" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Create a new project named 'Battlegrounds' and select Python as the programming language."&gt;&lt;/p&gt;
&lt;p&gt;Press the "Continue" button at the bottom to move along. The next
screen shows us a few instructions to add monitoring to a Python
application.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170723-monitor-flask-apps/project-setup.jpg" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Set up your project using your server-side access token."&gt;&lt;/p&gt;
&lt;p&gt;Let's change our Bottle code to let Rollbar collect and aggregate the
errors that pop up in our application. Modify &lt;code&gt;app.py&lt;/code&gt; to include the 
following highlighted lines. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;bottle&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;bottle&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;rollbar.contrib.bottle&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RollbarBottleReporter&lt;/span&gt;


&lt;span class="n"&gt;TEMPLATE_STRING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt; &amp;lt;head&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;  &amp;lt;title&amp;gt;Full Stack Python Web App&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt; &amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt; &amp;lt;body&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;  &amp;lt;h1&amp;gt;{{ h1 }}&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt; &amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;MIN_MSG_LENGTH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="n"&gt;ROLLBAR_SECRET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ROLLBAR_SECRET&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="n"&gt;rb_monitor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RollbarBottleReporter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;access_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ROLLBAR_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;                                   &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;production&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="n"&gt;bottle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rb_monitor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;lt;msg&amp;gt;/&amp;quot;&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;show_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Display a message if the msg value is greater than 2 characters&lt;/span&gt;
&lt;span class="sd"&gt;    in the path.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;valid_length&lt;/span&gt; &lt;span class="o"&gt;=&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;msg&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;MIN_MSG_LENGTH&lt;/span&gt;
    &lt;span class="n"&gt;valid_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;^[a-z\-]+$&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;valid_length&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;valid_name&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;template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TEMPLATE_STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;msg&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;error_msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Sorry, only alpha characters and hyphens allowed.&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error_msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;bottle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;localhost&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;A new import &lt;code&gt;from rollbar.contrib.bottle import RollbarBottleReporter&lt;/code&gt;
is our conduit between the application and the Rollbar server. &lt;code&gt;rollbar&lt;/code&gt;
is the library we installed earlier. &lt;/p&gt;
&lt;p&gt;The &lt;code&gt;ROLLBAR_SECRET&lt;/code&gt; token needs to be set in an environment variable.
Save and quit &lt;code&gt;app.py&lt;/code&gt;. Run the following command in the terminal where your
virtualenv is activated:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;ROLLBAR_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;token here&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If you are uncertain about what your secret token is, it can be found on 
the Rollbar onboarding screen. &lt;/p&gt;
&lt;p&gt;Note that I typically store all my environment variables in a &lt;code&gt;.env&lt;/code&gt; 
file and use a 
&lt;a href="https://github.com/fullstackpython/blog-code-examples/blob/master/monitor-flask-apps/template.env"&gt;template.env&lt;/a&gt; 
as a template for what I should fill into &lt;code&gt;.env&lt;/code&gt;. &lt;code&gt;.env&lt;/code&gt; can be invoked
from the terminal using the &lt;code&gt;. .env&lt;/code&gt; command. Make sure to &lt;em&gt;never&lt;/em&gt; commit 
your secret tokens to a source control repository though, especially if 
the repository is public!&lt;/p&gt;
&lt;p&gt;After exporting your &lt;code&gt;ROLLBAR_SECRET&lt;/code&gt; key as an environment variable
we can test that Rollbar is working as we run our application. Run it
now using &lt;code&gt;python&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python app.py
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Back in your web browser press the "Done! Go to Dashboard" button.&lt;/p&gt;
&lt;p&gt;If an event hasn't been reported yet we'll see a waiting screen like this
one:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170926-monitor-python-web-apps/waiting.jpg" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Waiting for data on the Rollbar dashboard."&gt;&lt;/p&gt;
&lt;p&gt;Make sure your Bottle development server is running and try to go to 
&lt;a href="http://localhost:8080/fullstackpython123/"&gt;localhost:8080/fullstackpython123/&lt;/a&gt;.
A 500 server error is immediately reported on the dashboard:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170926-monitor-python-web-apps/exception-reported.jpg" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Viewing the 500 errors reported in the Rollbar dashboard."&gt;&lt;/p&gt;
&lt;p&gt;We even get an email with the error (which can also be turned off if you
don't want emails for every error):&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170926-monitor-python-web-apps/email-error-report.jpg" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Email with the Rollbar error report."&gt;&lt;/p&gt;
&lt;p&gt;Nice, with just a few lines of code we now have our Bottle app reporting
errors for any user that's working with our application.&lt;/p&gt;
&lt;h2&gt;What now?&lt;/h2&gt;
&lt;p&gt;We just learned how to catch and handle errors with Rollbar as a hosted
monitoring platform in a simple example 
&lt;a href="/bottle.html"&gt;Bottle application&lt;/a&gt;. Next you will want to 
add &lt;a href="/monitoring.html"&gt;monitoring&lt;/a&gt; to more complicated web apps, including
ones that use &lt;a href="/django.html"&gt;Django&lt;/a&gt; or &lt;a href="/flask.html"&gt;Flask&lt;/a&gt;. You can also 
try Rollbar's more advanced features to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://rollbar.com/docs/custom-grouping/"&gt;set up rules to group errors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rollbar.com/docs/deploy-tracking/"&gt;debug and track deployment issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rollbar.com/docs/person-tracking/"&gt;sort and view errors by user&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There is plenty more to learn about in the areas of 
&lt;a href="/web-development.html"&gt;web development&lt;/a&gt; and 
&lt;a href="/deployments.html"&gt;deployments&lt;/a&gt; so keep learning by reading 
about &lt;a href="/web-frameworks.html"&gt;web frameworks&lt;/a&gt;. You can also learn more 
about integrating Rollbar with Python applications via 
&lt;a href="https://rollbar.com/docs/notifier/pyrollbar/"&gt;their Python documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Questions? Let me know via 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/issues"&gt;a GitHub issue ticket on the Full Stack Python repository&lt;/a&gt;, 
on Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Do you see a typo, syntax issue or just something that's confusing in this 
blog post? Fork
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/170926-monitor-python-web-apps.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request with a fix or 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/issues"&gt;file an issue ticket on GitHub&lt;/a&gt;.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Fri, 29 Sep 2017 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2017-09-26:blog/monitor-python-web-applications.html</guid></item><item><title>How to Provision Ubuntu 16.04 Linux Servers on Linode</title><link>https://www.fullstackpython.com/blog/provision-ubuntu-linux-servers-linode.html</link><description>&lt;p&gt;Your live web application must be &lt;a href="/deployment.html"&gt;deployed&lt;/a&gt; and run 
somewhere other than your local 
&lt;a href="/development-environments.html"&gt;development environment&lt;/a&gt;. That deployment 
location is known as a "production environment" and it is built out of 
one or more &lt;a href="/servers.html"&gt;servers&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let's learn how to provision an &lt;a href="/ubuntu.html"&gt;Ubuntu Linux 16.04 LTS&lt;/a&gt; 
&lt;a href="/virtual-private-servers-vps.html"&gt;virtual private server (VPS)&lt;/a&gt; on Linode 
that can be used for production or development purposes.&lt;/p&gt;
&lt;h2&gt;Signing up for Linode&lt;/h2&gt;
&lt;p&gt;We need a Linode account to provision a server, so start by pointing your 
web browser to &lt;a href="https://www.linode.com/"&gt;Linode.com&lt;/a&gt;. Their 
landing page will look something like the following image.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170920-provision-ubuntu-linode/linode-landing-page.jpg" class="technical-diagram img-rounded" width="100%"&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://manager.linode.com/session/signup"&gt;Sign up&lt;/a&gt; for an account.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170920-provision-ubuntu-linode/linode-sign-up.png" class="technical-diagram img-rounded" style="border:1px solid #ccc" width="100%"&gt;&lt;/p&gt;
&lt;p&gt;You should receive an email for account confirmation. Fill out the 
appropriate information and add initial credit to your account. If you 
want to enter a referral code, mine is 
&lt;code&gt;bfeecaf55a83cd3dd224a5f2a3a001fdf95d4c3d&lt;/code&gt;. Your account will go for 
a quick review to ensure you are not a malicious spam bot and then 
your account will be fully activated.&lt;/p&gt;
&lt;p&gt;Once your account is activated refresh the page. The new page will allow
you to add a Linode instance.&lt;/p&gt;
&lt;p&gt;Provisioning a server for $5 or $10/month (depending on
how much memory and storage you want) is more than enough for small-scale
Python web applications.&lt;/p&gt;
&lt;p&gt;Select the 1024 option and the data center location of your choice. I chose 
Newark, NJ because I grew up in northern NJ and otherwise the location is not
important for my deployment. If your most of your users are located in a 
specific region then you should select the data center location closest to 
them.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170920-provision-ubuntu-linode/select-instance.jpg" class="technical-diagram img-rounded" style="border:1px solid #ccc" width="100%"&gt;&lt;/p&gt;
&lt;p&gt;Click the "Add this Linode!" button and a dashboard will appear that
shows the Linode is being provisioned.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170920-provision-ubuntu-linode/dashboard.jpg" class="technical-diagram img-rounded" style="border:1px solid #ccc" width="100%"&gt;&lt;/p&gt;
&lt;p&gt;Refresh the page and look for the status to change to "Brand New." Write 
down or copy the IP address as it will be needed later to SSH into the 
server, then click on the name of the Linode. A page will appear to 
show more information about your new virtual private server.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170920-provision-ubuntu-linode/dashboard-provisioned.jpg" class="technical-diagram img-rounded" style="border:1px solid #ccc" width="100%"&gt;&lt;/p&gt;
&lt;p&gt;Click the "Rebuild" link.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170920-provision-ubuntu-linode/rebuild.jpg" class="technical-diagram img-rounded" style="border:1px solid #ccc" width="100%"&gt;&lt;/p&gt;
&lt;p&gt;Select Ubuntu 16.04, which is the current Long Term Support (LTS) release 
and has a 5 year support lifecycle. This version will receive security 
updates until April 2021 as shown on the 
&lt;a href="https://www.ubuntu.com/info/release-end-of-life"&gt;Ubuntu wiki page for LTS releases&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170920-provision-ubuntu-linode/ubuntu-lts-releases.jpg" class="technical-diagram img-rounded" style="border:1px solid #ccc" width="100%"&gt;&lt;/p&gt;
&lt;p&gt;Enter a root password. Make sure you type the password in carefully and 
remember it! The password will be needed when you log into the server
as the root user. The "Deployment Disk Size" and "Swap Disk" can be left as 
their default values.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170920-provision-ubuntu-linode/deploy-distribution.png" class="technical-diagram img-rounded" style="border:1px solid #ccc" width="100%"&gt;&lt;/p&gt;
&lt;p&gt;When the build process begins Linode will send us back to our server's 
dashboard page. The progress bars will show the status and in a couple of 
minutes the server will be ready to boot up.&lt;/p&gt;
&lt;h2&gt;Boot and Log In&lt;/h2&gt;
&lt;p&gt;Click the "Boot" button and the Ubuntu boot process will get started. 
Booting should take less than a minute. Bring up your local command line 
as we will need it to connect to the remote machine.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170920-provision-ubuntu-linode/boot.jpg" class="technical-diagram img-rounded" style="border:1px solid #ccc" width="100%"&gt;&lt;/p&gt;
&lt;p&gt;SSH into your server with &lt;code&gt;ssh root@{ip.address.here}&lt;/code&gt; where 
&lt;code&gt;{ip.address.here}&lt;/code&gt; is your server's IP address, which can be found on the 
Linode dashboard. For example, if your new Linode's IP address 
is 66.175.209.129, you'll enter &lt;code&gt;ssh root@66.175.209.129&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You'll likely receive a prompt like the following warning. This prompt 
states that you've never connected to this server before and it asks if 
you are sure that this host's signature matches the server on which you 
intend to connect. Enter &lt;code&gt;yes&lt;/code&gt; then enter the root password you created 
during the earlier Linode server provisioning step.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;The authenticity of host &lt;span class="s1"&gt;&amp;#39;66.175.209.192 (66.175.209.192)&amp;#39;&lt;/span&gt; can&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;t be established.
RSA key fingerprint is &lt;span class="m"&gt;51&lt;/span&gt;:3c:ba:bc:c3:83:1a:36:b1:2d:e3:f6:6d:f0:11:56.
Are you sure you want to &lt;span class="k"&gt;continue&lt;/span&gt; connecting &lt;span class="o"&gt;(&lt;/span&gt;yes/no&lt;span class="o"&gt;)&lt;/span&gt;? yes
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;A message like "Welcome to Ubuntu 16.04.3 LTS" will appear followed by a 
prompt. Now we can enter commands on the remote machine to get the 
server secured and setup.&lt;/p&gt;
&lt;h2&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;You are all set to start configuring your server. You will want to 
immediately create 
&lt;a href="https://www.fullstackpython.com/blog/ssh-keys-ubuntu-linux.html"&gt;SSH keys&lt;/a&gt;
and disable password logins as well as install tools like 
&lt;a href="https://www.fail2ban.org/wiki/index.php/Main_Page"&gt;fail2ban&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Questions? Contact me via Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;. I'm also on GitHub with
the username &lt;a href="https://github.com/mattmakai"&gt;mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See something wrong in this post? Fork 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/170920-provision-ubuntu-16-04-server-linode.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Wed, 20 Sep 2017 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2017-09-20:blog/provision-ubuntu-linux-servers-linode.html</guid></item><item><title>Creating Bar Chart Visuals with Bokeh, Bottle and Python 3</title><link>https://www.fullstackpython.com/blog/python-bottle-bokeh-bar-charts.html</link><description>&lt;p&gt;The &lt;a href="/bokeh.html"&gt;Bokeh&lt;/a&gt; open source Python visualization library assists 
developers with creating web browser visuals. You can build charts for 
web applications &lt;em&gt;without coding any JavaScript&lt;/em&gt;, like you'd need to do
to use libraries such as &lt;a href="https://d3js.org/"&gt;d3.js&lt;/a&gt; and plotly.&lt;/p&gt;
&lt;p&gt;Bokeh can create many common and custom visualizations using only
Python, such as this bar chart we will create in this tutorial:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170725-bottle-bokeh-bar-charts/chart-example-48.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Responsive Bokeh bar chart with 48 bars."&gt;&lt;/p&gt;
&lt;p&gt;Let's use the 
&lt;a href="/bottle.html"&gt;Bottle&lt;/a&gt; &lt;a href="/web-frameworks.html"&gt;web framework&lt;/a&gt; with Bokeh to 
build custom Python web app bar charts.&lt;/p&gt;
&lt;h2&gt;Our Tools&lt;/h2&gt;
&lt;p&gt;This tutorial works with either &lt;a href="/python-2-or-3.html"&gt;Python 2 or 3&lt;/a&gt;, 
but Python 3 is strongly recommended for new applications. I used
&lt;a href="https://www.python.org/downloads/release/python-362/"&gt;Python 3.6.2&lt;/a&gt; while 
writing this post. In addition to Python throughout this tutorial we 
will also use the following 
&lt;a href="/application-dependencies.html"&gt;application dependencies&lt;/a&gt;: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/bottle.html"&gt;Bottle&lt;/a&gt; web framework, 
  &lt;a href="https://github.com/bottlepy/bottle/tree/0.12.13"&gt;version 0.12.13&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/bokeh.html"&gt;Bokeh&lt;/a&gt; data visualization library, 
  &lt;a href="https://github.com/bokeh/bokeh/releases/tag/0.12.6"&gt;version 0.12.6&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/pandas.html"&gt;pandas&lt;/a&gt; data structures and analysis library, 
  &lt;a href="https://github.com/pandas-dev/pandas/releases/tag/v0.20.3"&gt;version 0.20.3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pip.pypa.io/en/stable/"&gt;pip&lt;/a&gt; and 
  &lt;a href="https://virtualenv.pypa.io/en/latest/"&gt;virtualenv&lt;/a&gt;, which come
  packaged with Python 3, to install and isolate the Bottle, Bokeh,
  and pandas libraries from other Python projects you are working on&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you need help getting your 
&lt;a href="/development-environments.html"&gt;development environment&lt;/a&gt; configured
before running this code, take a look at
&lt;a href="/blog/python-3-bottle-gunicorn-ubuntu-1604-xenial-xerus.html"&gt;this guide for setting up Python 3 and Bottle on Ubuntu 16.04 LTS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;All code in this blog post is available open source under the MIT license 
on GitHub under the 
&lt;a href="https://github.com/fullstackpython/blog-code-examples"&gt;bar-charts-bokeh-bottle-python-3 directory of the blog-code-examples repository&lt;/a&gt;. 
Use the source code as you want to for your own projects.&lt;/p&gt;
&lt;h2&gt;Installing Bottle and Bokeh&lt;/h2&gt;
&lt;p&gt;Create a new virtual environment for this project to isolate our 
dependencies using the following command in the terminal. I usually run the 
venv command within a separate &lt;code&gt;venvs&lt;/code&gt; directory where all my virtualenvs
are store.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python3 -m venv bottlechart
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Activate the virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt; bottlechart/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The command prompt will change after activating the virtualenv:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170725-bottle-bokeh-bar-charts/activate-virtualenv.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Activating our Python virtualenv for this project on the command line."&gt;&lt;/p&gt;
&lt;p&gt;Keep in mind that you need to activate the virtualenv in every new terminal 
window where you want to use the virtualenv to run the project.&lt;/p&gt;
&lt;p&gt;Bokeh and Bottle are installable into the now-activated virtualenv
using pip. Run this command to get the appropriate Bokeh and Bottle
versions.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install bokeh==0.12.6 bottle==0.12.13 pandas==0.20.3
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Our required dependencies will be installed within our virtualenv after
a brief download and installation period.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;Installing&lt;/span&gt; &lt;span class="nv"&gt;collected&lt;/span&gt; &lt;span class="nv"&gt;packages&lt;/span&gt;: &lt;span class="nv"&gt;bottle&lt;/span&gt;, &lt;span class="nv"&gt;six&lt;/span&gt;, &lt;span class="nv"&gt;chardet&lt;/span&gt;, &lt;span class="nv"&gt;certifi&lt;/span&gt;, &lt;span class="nv"&gt;idna&lt;/span&gt;, &lt;span class="nv"&gt;urllib3&lt;/span&gt;, &lt;span class="nv"&gt;requests&lt;/span&gt;, &lt;span class="nv"&gt;PyYAML&lt;/span&gt;, &lt;span class="nv"&gt;python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;dateutil&lt;/span&gt;, &lt;span class="nv"&gt;MarkupSafe&lt;/span&gt;, &lt;span class="nv"&gt;Jinja2&lt;/span&gt;, &lt;span class="nv"&gt;numpy&lt;/span&gt;, &lt;span class="nv"&gt;tornado&lt;/span&gt;, &lt;span class="nv"&gt;bkcharts&lt;/span&gt;, &lt;span class="nv"&gt;bokeh&lt;/span&gt;, &lt;span class="nv"&gt;pytz&lt;/span&gt;, &lt;span class="nv"&gt;pandas&lt;/span&gt;
  &lt;span class="nv"&gt;Running&lt;/span&gt; &lt;span class="nv"&gt;setup&lt;/span&gt;.&lt;span class="nv"&gt;py&lt;/span&gt; &lt;span class="nv"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;bottle&lt;/span&gt; ... &lt;span class="nv"&gt;done&lt;/span&gt;
  &lt;span class="nv"&gt;Running&lt;/span&gt; &lt;span class="nv"&gt;setup&lt;/span&gt;.&lt;span class="nv"&gt;py&lt;/span&gt; &lt;span class="nv"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;PyYAML&lt;/span&gt; ... &lt;span class="nv"&gt;done&lt;/span&gt;
  &lt;span class="nv"&gt;Running&lt;/span&gt; &lt;span class="nv"&gt;setup&lt;/span&gt;.&lt;span class="nv"&gt;py&lt;/span&gt; &lt;span class="nv"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;MarkupSafe&lt;/span&gt; ... &lt;span class="nv"&gt;done&lt;/span&gt;
  &lt;span class="nv"&gt;Running&lt;/span&gt; &lt;span class="nv"&gt;setup&lt;/span&gt;.&lt;span class="nv"&gt;py&lt;/span&gt; &lt;span class="nv"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;tornado&lt;/span&gt; ... &lt;span class="nv"&gt;done&lt;/span&gt;
  &lt;span class="nv"&gt;Running&lt;/span&gt; &lt;span class="nv"&gt;setup&lt;/span&gt;.&lt;span class="nv"&gt;py&lt;/span&gt; &lt;span class="nv"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;bkcharts&lt;/span&gt; ... &lt;span class="nv"&gt;done&lt;/span&gt;
  &lt;span class="nv"&gt;Running&lt;/span&gt; &lt;span class="nv"&gt;setup&lt;/span&gt;.&lt;span class="nv"&gt;py&lt;/span&gt; &lt;span class="nv"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;bokeh&lt;/span&gt; ... &lt;span class="nv"&gt;done&lt;/span&gt;
&lt;span class="nv"&gt;Successfully&lt;/span&gt; &lt;span class="nv"&gt;installed&lt;/span&gt; &lt;span class="nv"&gt;Jinja2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;.&lt;span class="mi"&gt;9&lt;/span&gt;.&lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="nv"&gt;MarkupSafe&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;.&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="nv"&gt;PyYAML&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;.&lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="nv"&gt;bkcharts&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;.&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="nv"&gt;bokeh&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;.&lt;span class="mi"&gt;12&lt;/span&gt;.&lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="nv"&gt;bottle&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;.&lt;span class="mi"&gt;12&lt;/span&gt;.&lt;span class="mi"&gt;13&lt;/span&gt; &lt;span class="nv"&gt;certifi&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2017&lt;/span&gt;.&lt;span class="mi"&gt;7&lt;/span&gt;.&lt;span class="mi"&gt;27&lt;/span&gt;.&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nv"&gt;chardet&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;.&lt;span class="mi"&gt;0&lt;/span&gt;.&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="nv"&gt;idna&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;.&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="nv"&gt;numpy&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;.&lt;span class="mi"&gt;13&lt;/span&gt;.&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nv"&gt;pandas&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;.&lt;span class="mi"&gt;20&lt;/span&gt;.&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="nv"&gt;python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;dateutil&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;.&lt;span class="mi"&gt;6&lt;/span&gt;.&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nv"&gt;pytz&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2017&lt;/span&gt;.&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="nv"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;.&lt;span class="mi"&gt;18&lt;/span&gt;.&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="nv"&gt;six&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;.&lt;span class="mi"&gt;10&lt;/span&gt;.&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="nv"&gt;tornado&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;.&lt;span class="mi"&gt;5&lt;/span&gt;.&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nv"&gt;urllib3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;.&lt;span class="mi"&gt;22&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We can now begin coding our web app.&lt;/p&gt;
&lt;h2&gt;Building the Bottle App&lt;/h2&gt;
&lt;p&gt;First we'll code a basic Bottle application and then we will add the
bar charts to the rendered page.&lt;/p&gt;
&lt;p&gt;Create a folder for your project named &lt;code&gt;bottle-bokeh-charts&lt;/code&gt;. Within
&lt;code&gt;bottle-bokeh-charts&lt;/code&gt; create a new file named &lt;code&gt;app.py&lt;/code&gt; with the following
code: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;bottle&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;bottle&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;


&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bottle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_app&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;TEMPLATE_STRING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt; &amp;lt;head&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;  &amp;lt;title&amp;gt;Bar charts with Bottle and Bokeh&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt; &amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt; &amp;lt;body&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;  &amp;lt;h1&amp;gt;Bugs found over the past {{ bars_count }} days&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt; &amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;


&lt;span class="nd"&gt;@route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;lt;num_bars:int&amp;gt;/&amp;#39;&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;chart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num_bars&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Returns a simple template stating the number of bars that should&lt;/span&gt;
&lt;span class="sd"&gt;    be generated when the rest of the function is complete.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;num_bars&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;num_bars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TEMPLATE_STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bars_count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;num_bars&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;127.0.0.1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reloader&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The code shown above provides a short &lt;a href="/bottle.html"&gt;Bottle&lt;/a&gt; application
with a single route, defined with the &lt;code&gt;chart&lt;/code&gt; function. &lt;code&gt;chart&lt;/code&gt; receives
an arbitrary integer value as input. The &lt;code&gt;template&lt;/code&gt; function within 
&lt;code&gt;chart&lt;/code&gt; uses the HTML template defined in &lt;code&gt;TEMPLATE_STRING&lt;/code&gt; to render
an HTML page as a response to incoming requests.&lt;/p&gt;
&lt;p&gt;The last two lines in the allow us to run the Bottle application 
in debug mode on port 8000. 
&lt;strong&gt;Never use debug mode for production deployments!&lt;/strong&gt;
&lt;a href="/wsgi-servers.html"&gt;WSGI servers&lt;/a&gt; like 
&lt;a href="/green-unicorn-gunicorn.html"&gt;Gunicorn&lt;/a&gt; are built for handling real
traffic and will be easier to configure without major security
holes.&lt;/p&gt;
&lt;p&gt;We can now test out our application.&lt;/p&gt;
&lt;p&gt;Make sure your virtualenv is still activated and that you are in the 
base directory of your project where &lt;code&gt;app.py&lt;/code&gt; is located. Run &lt;code&gt;app.py&lt;/code&gt;
using the &lt;code&gt;python&lt;/code&gt; command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(bottlechart)$ python app.py
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Go to &lt;a href="http://localhost:8000/16/"&gt;localhost:8000/16/&lt;/a&gt; in your web browser.
You should see a header message about the number of bugs found over the
past 16 days. However, there's no bar chart to accompany that message
just yet.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170725-bottle-bokeh-bar-charts/basic-app-no-chart.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="A simple Bottle app without the bar chart."&gt;&lt;/p&gt;
&lt;p&gt;Our single Bottle route is in place but it is not very exciting. Time
to create a nice-looking bar chart.&lt;/p&gt;
&lt;h2&gt;Creating A Bar Chart with Bokeh&lt;/h2&gt;
&lt;p&gt;We'll build on our basic Bottle app foundation using some new Python code 
to engage the &lt;a href="/bokeh.html"&gt;Bokeh&lt;/a&gt; library. &lt;/p&gt;
&lt;p&gt;Open &lt;code&gt;app.py&lt;/code&gt; back up and add the following highlighted import lines.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;bottle&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;random&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;bokeh.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HoverTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FactorRange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Plot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LinearAxis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Grid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;                          &lt;span class="n"&gt;Range1d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;bokeh.models.glyphs&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;VBar&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;bokeh.plotting&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;figure&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;bokeh.charts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Bar&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;bokeh.embed&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;components&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;bokeh.models.sources&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ColumnDataSource&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;bottle&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The rest of our application will use these imports to generate random
data and the bar chart.&lt;/p&gt;
&lt;p&gt;Our bar chart will have "software bugs found" for its theme. The data will
randomly generate each time the page is generated. In a real application
you would of course likely have a more stable and useful data source.&lt;/p&gt;
&lt;p&gt;Continue modifying &lt;code&gt;app.py&lt;/code&gt; so the section after the imports looks like
the following code. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bottle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_app&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;TEMPLATE_STRING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt; &amp;lt;head&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;  &amp;lt;title&amp;gt;Bar charts with Bottle and Bokeh&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;~~  &amp;lt;link href=&amp;quot;http://cdn.pydata.org/bokeh/release/bokeh-0.12.6.min.css&amp;quot; &lt;/span&gt;
&lt;span class="s2"&gt;~~        rel=&amp;quot;stylesheet&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;~~  &amp;lt;link href=&amp;quot;http://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.6.min.css&amp;quot; &lt;/span&gt;
&lt;span class="s2"&gt;~~        rel=&amp;quot;stylesheet&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt; &amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt; &amp;lt;body&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;  &amp;lt;h1&amp;gt;Bugs found over the past {{ bars_count }} days&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;~~  {{ !the_div }}&lt;/span&gt;
&lt;span class="s2"&gt;~~  &amp;lt;script src=&amp;quot;http://cdn.pydata.org/bokeh/release/bokeh-0.12.6.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;~~  &amp;lt;script src=&amp;quot;http://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.6.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;~~  {{ !the_script }}&lt;/span&gt;
&lt;span class="s2"&gt; &amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;


&lt;span class="nd"&gt;@route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;lt;num_bars:int&amp;gt;/&amp;#39;&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;chart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num_bars&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Returns a simple template stating the number of bars that should&lt;/span&gt;
&lt;span class="sd"&gt;    be generated when the rest of the function is complete.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;num_bars&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;num_bars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;days&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;bugs&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;costs&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]}&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num_bars&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&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="s1"&gt;&amp;#39;days&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&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="s1"&gt;&amp;#39;bugs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;100&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="s1"&gt;&amp;#39;costs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1000.00&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="n"&gt;hover&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_hover_tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="n"&gt;plot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_bar_chart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Bugs found per day&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;days&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;                            &lt;span class="s2"&gt;&amp;quot;bugs&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hover&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;components&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TEMPLATE_STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bars_count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;num_bars&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;                    &lt;span class="n"&gt;the_div&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;the_script&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;chart&lt;/code&gt; function gains three new lists that are randomly generated by 
&lt;a href="https://docs.python.org/3/library/random.html"&gt;Python 3's super-handy random module&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;chart&lt;/code&gt; calls two functions, &lt;code&gt;create_hover_tool&lt;/code&gt; and &lt;code&gt;create_bar_chart&lt;/code&gt;.
We haven't written those functions yet, so let's do that now. Add these
two new functions below the &lt;code&gt;chart&lt;/code&gt; function, but before the 
&lt;code&gt;if __name__ == '__main__':&lt;/code&gt; line.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_hover_tool&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# we&amp;#39;ll code this function in a moment&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_bar_chart&lt;/span&gt;&lt;span class="p"&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;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hover_tool&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Creates a bar chart plot with the exact styling for the centcom&lt;/span&gt;
&lt;span class="sd"&gt;       dashboard. Pass in data as a dictionary, desired plot title,&lt;/span&gt;
&lt;span class="sd"&gt;       name of x axis, y axis and the hover tool HTML.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ColumnDataSource&lt;/span&gt;&lt;span class="p"&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;xdr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FactorRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;factors&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;x_name&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;ydr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Range1d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&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="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&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;y_name&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;hover_tool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;hover_tool&lt;/span&gt;&lt;span class="p"&gt;,]&lt;/span&gt;

    &lt;span class="n"&gt;plot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x_range&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;xdr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_range&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ydr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plot_width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="n"&gt;plot_height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h_symmetry&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v_symmetry&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="n"&gt;min_border&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;toolbar_location&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;above&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="n"&gt;responsive&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;outline_line_color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;#666666&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;glyph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;x_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;y_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bottom&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="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;fill_color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;#6599ed&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_glyph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;glyph&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;xaxis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LinearAxis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;yaxis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LinearAxis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_layout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Grid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dimension&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="n"&gt;ticker&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;xaxis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_layout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Grid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dimension&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;yaxis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toolbar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;min_border_top&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xgrid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grid_line_color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ygrid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grid_line_color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;#999999&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yaxis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;axis_label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Bugs found&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ygrid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grid_line_alpha&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;
    &lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xaxis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;axis_label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Days after app deployment&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xaxis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;major_label_orientation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;plot&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;That's a lot of new code. The &lt;code&gt;create_hover_tool&lt;/code&gt; function does not do 
anything just yet other than returning.  &lt;code&gt;None&lt;/code&gt;, which is used when no
hover tool is desired for the graph.&lt;/p&gt;
&lt;p&gt;Within the &lt;code&gt;create_bar_chart&lt;/code&gt; function we take in our randomly-generated 
data source and convert it into a &lt;code&gt;ColumnDataSource&lt;/code&gt; object that is one 
type of input object we can pass to Bokeh functions. We specify two ranges 
for the chart's x and y axes.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;tools&lt;/code&gt; list will remain empty because we do not yet have a hover tool.
A lot of the magic happens in the lines where we create &lt;code&gt;plot&lt;/code&gt; using the 
&lt;code&gt;figure&lt;/code&gt; function. We specify all the parameters we want our graph to have
such as the size, toolbar, borders and whether or not the graph should be
responsive upon changing the web browser size.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;VBar&lt;/code&gt; object creates vertical bars to add them to the plot with
the &lt;code&gt;add_glyph&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;The last lines of the function change the graph's appearance. For
example, we took away the &lt;code&gt;Bokeh&lt;/code&gt; logo by specifying 
&lt;code&gt;plot.toolbar.logo = None&lt;/code&gt; and added labels to both axes. I recommend 
keeping the 
&lt;a href="http://bokeh.pydata.org/en/latest/docs/reference/plotting.html#bokeh-plotting"&gt;bokeh.plotting&lt;/a&gt;
documentation open so you know what your options are for customizing the
charts and visualizations.&lt;/p&gt;
&lt;p&gt;Let's test our app by trying a 6-bar chart. The Bottle app should 
automatically reload when you save &lt;code&gt;app.py&lt;/code&gt; with the new code. If you shut 
down the development server, start it back up using &lt;code&gt;python app.py&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;When you start up the development server you will receive the following 
warning because we are using the latest (at the time of this writing) 0.12.6 
Bokeh release. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;matt&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;Envs&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;bottlechart&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;python3&lt;/span&gt;.&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;site&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;bokeh&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;util&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;deprecation&lt;/span&gt;.&lt;span class="nv"&gt;py&lt;/span&gt;:&lt;span class="mi"&gt;34&lt;/span&gt;: &lt;span class="nv"&gt;BokehDeprecationWarning&lt;/span&gt;: 
&lt;span class="nv"&gt;The&lt;/span&gt; &lt;span class="nv"&gt;bokeh&lt;/span&gt;.&lt;span class="nv"&gt;charts&lt;/span&gt; &lt;span class="nv"&gt;API&lt;/span&gt; &lt;span class="nv"&gt;has&lt;/span&gt; &lt;span class="nv"&gt;moved&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;a&lt;/span&gt; &lt;span class="nv"&gt;separate&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;bkcharts&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt; &lt;span class="nv"&gt;package&lt;/span&gt;.

&lt;span class="nv"&gt;This&lt;/span&gt; &lt;span class="nv"&gt;compatibility&lt;/span&gt; &lt;span class="nv"&gt;shim&lt;/span&gt; &lt;span class="nv"&gt;will&lt;/span&gt; &lt;span class="nv"&gt;remain&lt;/span&gt; &lt;span class="k"&gt;until&lt;/span&gt; &lt;span class="nv"&gt;Bokeh&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;.&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="nv"&gt;is&lt;/span&gt; &lt;span class="nv"&gt;released&lt;/span&gt;.
&lt;span class="nv"&gt;After&lt;/span&gt; &lt;span class="nv"&gt;that&lt;/span&gt;, &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;you&lt;/span&gt; &lt;span class="nv"&gt;want&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;this&lt;/span&gt; &lt;span class="nv"&gt;API&lt;/span&gt; &lt;span class="nv"&gt;you&lt;/span&gt; &lt;span class="nv"&gt;will&lt;/span&gt; &lt;span class="nv"&gt;have&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;install&lt;/span&gt;
&lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;bkcharts&lt;/span&gt; &lt;span class="nv"&gt;package&lt;/span&gt; &lt;span class="nv"&gt;explicitly&lt;/span&gt;.
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Eventually a separate &lt;code&gt;bkcharts&lt;/code&gt; project will be required but for now 
we can keep our code as is.&lt;/p&gt;
&lt;p&gt;Open your browser to &lt;a href="http://localhost:8000/6/"&gt;localhost:8000/6/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170725-bottle-bokeh-bar-charts/chart-example-6.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Responsive Bokeh bar chart with 6 bars."&gt;&lt;/p&gt;
&lt;p&gt;That one looks a bit sparse, so we can crank it up by 3x to 18 bars
by going to &lt;a href="http://localhost:5000/18/"&gt;localhost:5000/18/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170725-bottle-bokeh-bar-charts/chart-example-18.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Responsive Bokeh bar chart with 18 bars."&gt;&lt;/p&gt;
&lt;p&gt;Now another 5x to 90 bars with 
&lt;a href="http://localhost:8000/90/"&gt;localhost:5000/90/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170725-bottle-bokeh-bar-charts/chart-example-90.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Responsive Bokeh bar chart with 90 bars."&gt;&lt;/p&gt;
&lt;p&gt;Looking good so far! What about that hover tool we skipped over though?
We can add the hover tool with just a few more lines of code in the 
&lt;code&gt;create_hover_tool&lt;/code&gt; function.&lt;/p&gt;
&lt;h2&gt;Creating a Hover Tool&lt;/h2&gt;
&lt;p&gt;Add these highlighted lines to &lt;code&gt;app.py&lt;/code&gt; within the &lt;code&gt;create_hover_tool&lt;/code&gt;
function.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_hover_tool&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;Generates the HTML for the Bokeh&amp;#39;s hover data tool on our graph.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="n"&gt;hover_html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;~~      &amp;lt;div&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;~~        &amp;lt;span class=&amp;quot;hover-tooltip&amp;quot;&amp;gt;$x&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;~~      &amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;~~      &amp;lt;div&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;~~        &amp;lt;span class=&amp;quot;hover-tooltip&amp;quot;&amp;gt;@bugs bugs&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;~~      &amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;~~      &amp;lt;div&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;~~        &amp;lt;span class=&amp;quot;hover-tooltip&amp;quot;&amp;gt;$@costs&lt;/span&gt;&lt;span class="si"&gt;{0.00}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;~~      &amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;~~    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;HoverTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tooltips&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;hover_html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Embedding HTML within your Python application isn't usually a great
idea but it works for small snippets like this hover tool.
The hover tool uses &lt;code&gt;$x&lt;/code&gt; to show the bar's x axis, &lt;code&gt;@bugs&lt;/code&gt; to show the 
"bugs" field from our data source, and &lt;code&gt;$@costs{0.00}&lt;/code&gt; to show the "costs" 
field formatted as a dollar amount with exactly 2 decimal places.&lt;/p&gt;
&lt;p&gt;Ensure that you changed &lt;code&gt;return None&lt;/code&gt; to 
&lt;code&gt;return HoverTool(tooltips=hover_html)&lt;/code&gt; in your function so the results of 
the new code are reflected in the refreshed graph.&lt;/p&gt;
&lt;p&gt;Go back to the browser and reload the 
&lt;a href="http://localhost:8000/122"&gt;localhost:8000/122/&lt;/a&gt; page.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170725-bottle-bokeh-bar-charts/chart-example-122.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Responsive Bokeh bar chart with 122 bars."&gt;&lt;/p&gt;
&lt;p&gt;Well done! Try playing around with the number of bars in the URL and the
window size to see what the graph looks like under different conditions.&lt;/p&gt;
&lt;p&gt;The chart gets crowded with more than 100. However, you can try to create
as many bars as you want if your computer can handle the rendering.
This screenshot shows what the completely impractical amount of 40,000
bars looks like:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170725-bottle-bokeh-bar-charts/chart-example-40000.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Responsive Bokeh bar chart with 40000 bars."&gt;&lt;/p&gt;
&lt;p&gt;You may need to do some more work to get the chart to be useful for displaying
more than a couple hundred bars at a time.&lt;/p&gt;
&lt;h2&gt;What now?&lt;/h2&gt;
&lt;p&gt;We created a nice little configurable bar chart using the Bokeh code library. &lt;/p&gt;
&lt;p&gt;Next you can change the input data source, work with other types of charts
or modify the chart color scheme.&lt;/p&gt;
&lt;p&gt;There is a lot more than Bokeh can do. Take a look at the 
&lt;a href="http://bokeh.pydata.org/en/latest/"&gt;official project documentation&lt;/a&gt; , 
&lt;a href="https://github.com/bokeh/bokeh"&gt;GitHub repository&lt;/a&gt;, 
the &lt;a href="/bokeh.html"&gt;Full Stack Python Bokeh page&lt;/a&gt; or take a look at 
&lt;a href="/table-of-contents.html"&gt;other topics on Full Stack Python&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Questions? Let me know via 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/issues"&gt;a GitHub issue ticket on the Full Stack Python repository&lt;/a&gt;, 
on Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Do you see something wrong in this blog post? Fork
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/170725-bar-charts-bottle-bokeh.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request with a fix.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Sun, 30 Jul 2017 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2017-07-25:blog/python-bottle-bokeh-bar-charts.html</guid></item><item><title>How to Add Hosted Monitoring to Flask Web Applications</title><link>https://www.fullstackpython.com/blog/hosted-monitoring-flask-web-apps.html</link><description>&lt;p&gt;How do you know whether your application is running properly with minimal 
errors after &lt;a href="/web-development.html"&gt;building&lt;/a&gt; and 
&lt;a href="/deployment.html"&gt;deploying&lt;/a&gt; it? The fastest and easiest way
to monitor your operational &lt;a href="/flask.html"&gt;Flask web application&lt;/a&gt; is to 
integrate one of the many available fantastic hosted 
&lt;a href="/monitoring.html"&gt;monitoring&lt;/a&gt; tools.&lt;/p&gt;
&lt;p&gt;In this post we will quickly add &lt;a href="https://rollbar.com"&gt;Rollbar monitoring&lt;/a&gt;
to catch errors and visualize our application is running properly. There
are also many other great hosted monitoring tools, which you can check
out on the &lt;a href="/monitoring.html"&gt;monitoring&lt;/a&gt; page.&lt;/p&gt;
&lt;h2&gt;Our Tools&lt;/h2&gt;
&lt;p&gt;We can use either &lt;a href="/python-2-or-3.html"&gt;Python 2 or 3&lt;/a&gt; to build this
tutorial, but Python 3 is &lt;em&gt;strongly&lt;/em&gt; recommended for all new applications. 
I used 
&lt;a href="https://www.python.org/downloads/release/python-362/"&gt;Python 3.6.2&lt;/a&gt; to 
execute my code. We will also use the following 
&lt;a href="/application-dependencies.html"&gt;application dependencies&lt;/a&gt; throughout
the post: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/flask.html"&gt;Flask&lt;/a&gt; web framework, 
  &lt;a href="https://github.com/pallets/flask/releases/tag/0.12.2"&gt;version 0.12.2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rollbar.com/docs/notifier/pyrollbar/"&gt;pyrollbar&lt;/a&gt; monitoring 
  instrumentation library,
  &lt;a href="https://github.com/rollbar/pyrollbar/tree/v0.13.12"&gt;version 0.13.12&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pypi.org/project/blinker"&gt;blinker&lt;/a&gt; for signaling support
  in Flask applications so pyrollbar can report on all errors&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://rollbar.com/"&gt;free Rollbar account&lt;/a&gt; where we will send error
  data and view it when it is captured&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pip.pypa.io/en/stable/"&gt;pip&lt;/a&gt; and the 
  &lt;a href="https://virtualenv.pypa.io/en/latest/"&gt;virtualenv&lt;/a&gt; virtual environment
  library, which come packaged with Python 3, to install and isolate the 
  Flask and Rollbar libraries from other Python projects you are working on&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you need help getting your 
&lt;a href="/development-environments.html"&gt;development environment&lt;/a&gt; configured
before running this code, take a look at
&lt;a href="/blog/python-3-flask-green-unicorn-ubuntu-1604-xenial-xerus.html"&gt;this guide for setting up Python 3 and Flask on Ubuntu 16.04 LTS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;All code in this blog post is available open source under the MIT license 
on GitHub under the 
&lt;a href="https://github.com/fullstackpython/blog-code-examples"&gt;monitor-flask-apps directory of the blog-code-examples repository&lt;/a&gt;. 
Use and abuse the source code as you desire for your own applications.&lt;/p&gt;
&lt;h2&gt;Installing Dependencies&lt;/h2&gt;
&lt;p&gt;Change into the directory where you keep your Python virtualenvs. 
Create a new virtual environment for this project using the following
command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python3 -m venv monitorflask
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Activate the virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt; monitorflask/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The command prompt will change after activating the virtualenv:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170723-monitor-flask-apps/activate-virtualenv.png" width="100%" class="shot rnd outl" alt="Activating our Python virtual environment on the command line."&gt;&lt;/p&gt;
&lt;p&gt;Remember that you need to activate the virtualenv in every new terminal 
window where you want to use the virtualenv to run the project.&lt;/p&gt;
&lt;p&gt;Flask, Rollbar and Blinker can now be installed into the now-activated 
virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install flask==0.12.2 rollbar==0.13.12 blinker==1.4
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Our required dependencies should be installed within our virtualenv 
after a short installation period. Look for output like the following to 
confirm everything worked.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;Installing&lt;/span&gt; &lt;span class="nv"&gt;collected&lt;/span&gt; &lt;span class="nv"&gt;packages&lt;/span&gt;: &lt;span class="nv"&gt;blinker&lt;/span&gt;, &lt;span class="nv"&gt;itsdangerous&lt;/span&gt;, &lt;span class="nv"&gt;click&lt;/span&gt;, &lt;span class="nv"&gt;MarkupSafe&lt;/span&gt;, &lt;span class="nv"&gt;Jinja2&lt;/span&gt;, &lt;span class="nv"&gt;Werkzeug&lt;/span&gt;, &lt;span class="nv"&gt;Flask&lt;/span&gt;, &lt;span class="nv"&gt;idna&lt;/span&gt;, &lt;span class="nv"&gt;urllib3&lt;/span&gt;, &lt;span class="nv"&gt;chardet&lt;/span&gt;, &lt;span class="nv"&gt;certifi&lt;/span&gt;, &lt;span class="nv"&gt;requests&lt;/span&gt;, &lt;span class="nv"&gt;six&lt;/span&gt;, &lt;span class="nv"&gt;rollbar&lt;/span&gt;
  &lt;span class="nv"&gt;Running&lt;/span&gt; &lt;span class="nv"&gt;setup&lt;/span&gt;.&lt;span class="nv"&gt;py&lt;/span&gt; &lt;span class="nv"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;blinker&lt;/span&gt; ... &lt;span class="nv"&gt;done&lt;/span&gt;
  &lt;span class="nv"&gt;Running&lt;/span&gt; &lt;span class="nv"&gt;setup&lt;/span&gt;.&lt;span class="nv"&gt;py&lt;/span&gt; &lt;span class="nv"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;itsdangerous&lt;/span&gt; ... &lt;span class="nv"&gt;done&lt;/span&gt;
  &lt;span class="nv"&gt;Running&lt;/span&gt; &lt;span class="nv"&gt;setup&lt;/span&gt;.&lt;span class="nv"&gt;py&lt;/span&gt; &lt;span class="nv"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;MarkupSafe&lt;/span&gt; ... &lt;span class="nv"&gt;done&lt;/span&gt;
  &lt;span class="nv"&gt;Running&lt;/span&gt; &lt;span class="nv"&gt;setup&lt;/span&gt;.&lt;span class="nv"&gt;py&lt;/span&gt; &lt;span class="nv"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;rollbar&lt;/span&gt; ... &lt;span class="nv"&gt;done&lt;/span&gt;
&lt;span class="nv"&gt;Successfully&lt;/span&gt; &lt;span class="nv"&gt;installed&lt;/span&gt; &lt;span class="nv"&gt;Flask&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;.&lt;span class="mi"&gt;12&lt;/span&gt;.&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="nv"&gt;Jinja2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;.&lt;span class="mi"&gt;9&lt;/span&gt;.&lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="nv"&gt;MarkupSafe&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;.&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="nv"&gt;Werkzeug&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;.&lt;span class="mi"&gt;12&lt;/span&gt;.&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="nv"&gt;blinker&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;.&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="nv"&gt;certifi&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2017&lt;/span&gt;.&lt;span class="mi"&gt;4&lt;/span&gt;.&lt;span class="mi"&gt;17&lt;/span&gt; &lt;span class="nv"&gt;chardet&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;.&lt;span class="mi"&gt;0&lt;/span&gt;.&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="nv"&gt;click&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;.&lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="nv"&gt;idna&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;.&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="nv"&gt;itsdangerous&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;.&lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="nv"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;.&lt;span class="mi"&gt;18&lt;/span&gt;.&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nv"&gt;rollbar&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;.&lt;span class="mi"&gt;13&lt;/span&gt;.&lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="nv"&gt;six&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;.&lt;span class="mi"&gt;10&lt;/span&gt;.&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="nv"&gt;urllib3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;.&lt;span class="mi"&gt;21&lt;/span&gt;.&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now that we have our Python dependencies installed into our virtualenv
we can create the initial version of our application.&lt;/p&gt;
&lt;h2&gt;Building Our Flask App&lt;/h2&gt;
&lt;p&gt;Create a folder for your project named &lt;code&gt;monitor-flask-apps&lt;/code&gt;. Change into
the folder and then create a file named &lt;code&gt;app.py&lt;/code&gt; with the following
code.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;werkzeug.exceptions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;NotFound&lt;/span&gt;


&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;MIN_PAGE_NAME_LENGTH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;lt;string:page&amp;gt;/&amp;quot;&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;show_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;valid_length&lt;/span&gt; &lt;span class="o"&gt;=&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;page&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;MIN_PAGE_NAME_LENGTH&lt;/span&gt;
        &lt;span class="n"&gt;valid_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;^[a-z]+$&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;valid_length&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;valid_name&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;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;.html&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&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;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Sorry, couldn&amp;#39;t find page with name &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&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;NotFound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;404 Not Found&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The above application code has some standard Flask imports so we can
create a Flask web app and render template files. We have a single
function named &lt;code&gt;show_page&lt;/code&gt; to serve a single Flask route. &lt;code&gt;show_page&lt;/code&gt;
checks if the URL path contains only lowercase alpha characters for a
potential page name. If the page name can be found in the &lt;code&gt;templates&lt;/code&gt;
folder then the page is rendered, otherwise an exception is thrown
that the page could not be found. We need to create at least one template
file if our function is ever going to return a non-error reponse.&lt;/p&gt;
&lt;p&gt;Save &lt;code&gt;app.py&lt;/code&gt; and make a new subdirectory named &lt;code&gt;templates&lt;/code&gt; under your
project directory. Create a new file named &lt;code&gt;battlegrounds.html&lt;/code&gt; and put
the following &lt;a href="/jinja2.html"&gt;Jinja2&lt;/a&gt; template markup into it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;You found the Battlegrounds GIF!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;PUBG so good.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://media.giphy.com/media/3ohzdLMlhId2rJuLUQ/giphy.gif&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The above &lt;a href="/jinja2.html"&gt;Jinja2&lt;/a&gt; template is basic HTML without any
&lt;a href="http://jinja.pocoo.org/docs/latest/templates/"&gt;embedded template tags&lt;/a&gt;. 
The template creates a very plain page with a header description of
"PUBG so good" and a GIF from this
&lt;a href="https://store.steampowered.com/app/578080/PUBG_BATTLEGROUNDS/"&gt;excellent computer game&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Time to run and test our code. Change into the base directory of your
project where &lt;code&gt;app.py&lt;/code&gt; file is located. Execute &lt;code&gt;app.py&lt;/code&gt; using the &lt;code&gt;python&lt;/code&gt;
command as follows (make sure your virtualenv is still activated in the
terminal where you are running this command):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python app.py
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The Flask development server should start up and display a few lines
of output.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170723-monitor-flask-apps/python-app-py.png" width="100%" class="shot rnd outl" alt="Run the Flask development server locally."&gt;&lt;/p&gt;
&lt;p&gt;What happens when we access the application running on 
&lt;a href="http://localhost:5000"&gt;localhost port 5000&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170723-monitor-flask-apps/localhost-base-url.png" width="100%" class="shot rnd outl" alt="Testing our Flask application at the base URL receives an HTTP 404 error."&gt;&lt;/p&gt;
&lt;p&gt;HTTP status 404 page not found, which is what we expected because we only
defined a single route and it did not live at the base path.&lt;/p&gt;
&lt;p&gt;We created a template named &lt;code&gt;battlegrounds.html&lt;/code&gt; that should be accessible
when we go to 
&lt;a href="http://localhost:5000/battlegrounds/"&gt;localhost:5000/battlegrounds/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170723-monitor-flask-apps/localhost-pubg-gif.jpg" width="100%" class="shot rnd outl" alt="Testing our Flask application at /battlegrounds/ gets the proper template with a GIF."&gt;&lt;/p&gt;
&lt;p&gt;The application successfully found the &lt;code&gt;battlegrounds.html&lt;/code&gt; template but
that is the only one available. What if we try 
&lt;a href="http://localhost:5000/fullstackpython/"&gt;localhost:5000/fullstackpython/&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170723-monitor-flask-apps/localhost-no-template.jpg" width="100%" class="shot rnd outl" alt="If no template is found we receive a 500 error."&gt;&lt;/p&gt;
&lt;p&gt;HTTP 500 error. That's no good.&lt;/p&gt;
&lt;p&gt;The 404 and 500 errors are obvious to us right now because we are 
testing the application locally. However, what happens when the app is 
deployed and a user gets the error in their own web browser? They will 
typically quit out of frustration and you will never know what happened 
unless you add some error tracking and application monitoring.&lt;/p&gt;
&lt;p&gt;We will now modify our code to add Rollbar to catch and report those
errors that occur for our users.&lt;/p&gt;
&lt;h2&gt;Handling Errors&lt;/h2&gt;
&lt;p&gt;Head to &lt;a href="https://rollbar.com/"&gt;Rollbar's homepage&lt;/a&gt; so we can add their
hosted monitoring tools to our oft-erroring Flask app.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170723-monitor-flask-apps/rollbar-homepage.jpg" width="100%" class="shot rnd outl" alt="Rollbar homepage in the web browser."&gt;&lt;/p&gt;
&lt;p&gt;Click the "Sign Up" button in the upper right-hand corner. Enter your 
email address, a username and the password you want on the sign up page.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170723-monitor-flask-apps/sign-up.jpg" width="100%" class="shot rnd outl" alt="Enter your basic account information on the sign up page."&gt;&lt;/p&gt;
&lt;p&gt;After the sign up page you will see the onboarding flow where you can
enter a project name and select a programming language. For project
name enter "Battlegrounds" and select that you are monitoring a Python app.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170723-monitor-flask-apps/create-new-project.jpg" width="100%" class="shot rnd outl" alt="Create a new project named 'Battlegrounds' and select Python as the programming language."&gt;&lt;/p&gt;
&lt;p&gt;Press the "Continue" button at the bottom to move along. The next
screen shows us a few quick instructions to add monitoring to our Flask
application.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170723-monitor-flask-apps/project-setup.jpg" width="100%" class="shot rnd outl" alt="Set up your project using your server-side access token."&gt;&lt;/p&gt;
&lt;p&gt;Let's modify our Flask application to test whether we can properly connect
to Rollbar's service. Change &lt;code&gt;app.py&lt;/code&gt; to include the following highlighted
lines. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;re&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;rollbar&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;werkzeug.exceptions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;NotFound&lt;/span&gt;


&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;MIN_PAGE_NAME_LENGTH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;


&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;before_first_request&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_monitoring&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="n"&gt;rollbar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ROLLBAR_SECRET&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="n"&gt;rollbar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;report_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Rollbar is configured correctly&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;lt;string:page&amp;gt;/&amp;quot;&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;show_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;valid_length&lt;/span&gt; &lt;span class="o"&gt;=&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;page&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;MIN_PAGE_NAME_LENGTH&lt;/span&gt;
        &lt;span class="n"&gt;valid_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;^[a-z]+$&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;valid_length&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;valid_name&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;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;.html&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&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;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Sorry, couldn&amp;#39;t find page with name &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&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;NotFound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;404 Not Found&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We added a couple of new imports, &lt;code&gt;os&lt;/code&gt; and &lt;code&gt;rollbar&lt;/code&gt;. &lt;code&gt;os&lt;/code&gt; allows us to
grab environment variable values, such as our Rollbar secret key. &lt;code&gt;rollbar&lt;/code&gt;
is the library we installed earlier. The two lines below the Flask app
instantiation are to initialize Rollbar using the Rollbar secret token and
send a message to the service that it started correctly.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;ROLLBAR_SECRET&lt;/code&gt; token needs to be set in an environment variable.
Save an quit the &lt;code&gt;app.py&lt;/code&gt;. Run &lt;code&gt;export ROLLBAR_SECRET='token here'&lt;/code&gt; on the
command line where your virtualenv is activated. This token can be found
on the Rollbar onboarding screen. &lt;/p&gt;
&lt;p&gt;I typically store all my environment variables in a file like 
&lt;a href="https://github.com/fullstackpython/blog-code-examples/blob/master/monitor-flask-apps/template.env"&gt;template.env&lt;/a&gt; and invoke it from the terminal using
the &lt;code&gt;. ./template.env&lt;/code&gt; command. Make sure to avoid committing your secret
tokens to a source control repository, especially if the repository is 
public!&lt;/p&gt;
&lt;p&gt;After exporting your &lt;code&gt;ROLLBAR_SECRET&lt;/code&gt; key as an environment variable
we can test that Rollbar is working as we run our application. Run it
now using &lt;code&gt;python&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python app.py
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Back in your web browser press the "Done! Go to Dashboard" button. Don't 
worry about the "Report an Error" section code, we can get back to that in a 
moment.&lt;/p&gt;
&lt;p&gt;If the event hasn't been reported yet we'll see a waiting screen like this
one:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170723-monitor-flask-apps/waiting.jpg" width="100%" class="shot rnd outl" alt="Waiting for data on the dashboard."&gt;&lt;/p&gt;
&lt;p&gt;Once Flask starts up though, the first event will be populated on the 
dashboard.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170723-monitor-flask-apps/first-event.jpg" width="100%" class="shot rnd outl" alt="First event populated on our dashboard for this project."&gt;&lt;/p&gt;
&lt;p&gt;Okay, our first test event has been populated, but we really want to see
all the errors from our application, not a test event.&lt;/p&gt;
&lt;h2&gt;Testing Error Handling&lt;/h2&gt;
&lt;p&gt;How do we make sure real errors are reported rather than just a simple
test event? We just need to add a few more lines of code to our app.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;rollbar&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;rollbar.contrib.flask&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;got_request_exception&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;werkzeug.exceptions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;NotFound&lt;/span&gt;


&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;MIN_PAGE_NAME_LENGTH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;before_first_request&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_monitoring&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;rollbar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ROLLBAR_SECRET&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="c1"&gt;## delete the next line if you dont want this event anymore&lt;/span&gt;
    &lt;span class="n"&gt;rollbar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;report_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Rollbar is configured correctly&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;    &lt;span class="n"&gt;got_request_exception&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rollbar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contrib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;report_exception&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;lt;string:page&amp;gt;/&amp;quot;&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;show_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;valid_length&lt;/span&gt; &lt;span class="o"&gt;=&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;page&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;MIN_PAGE_NAME_LENGTH&lt;/span&gt;
        &lt;span class="n"&gt;valid_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;^[a-z]+$&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;valid_length&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;valid_name&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;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;.html&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&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;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Sorry, couldn&amp;#39;t find page with name &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&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;NotFound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;~~&lt;/span&gt;        &lt;span class="n"&gt;rollbar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;report_exc_info&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;404 Not Found&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The above highlighted code modifies the application so it reports all Flask
errors as well as our HTTP 404 not found issues that happen within the
&lt;code&gt;show_page&lt;/code&gt; function. &lt;/p&gt;
&lt;p&gt;Make sure your Flask development server is running and try to go to 
&lt;a href="http://localhost:5000/b/"&gt;localhost:5000/b/&lt;/a&gt;. You will receive an HTTP
404 exception and it will be reported to Rollbar. Next go to 
&lt;a href="http://localhost:5000/fullstackpython/"&gt;localhost:5000/fullstackpython/&lt;/a&gt; and 
an HTTP 500 error will occur.&lt;/p&gt;
&lt;p&gt;You should see an aggregation of errors as you test out these errors:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170723-monitor-flask-apps/error-aggregation.jpg" width="100%" class="shot rnd outl" alt="Rollbar dashboard showing aggregations of errors."&gt;&lt;/p&gt;
&lt;p&gt;Woohoo, we finally have our Flask app reporting all errors that occur
for any user back to the hosted Rollbar monitoring service!&lt;/p&gt;
&lt;h2&gt;What's Next?&lt;/h2&gt;
&lt;p&gt;We just learned how to catch and handle errors with Rollbar as a hosted
monitoring platform in a simple Flask application. Next you will want to 
add monitoring to your more complicated web apps. You can also check out 
some of Rollbar's more advanced features such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://rollbar.com/docs/deploy-tracking/"&gt;tracking and debugging deployment issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rollbar.com/docs/person-tracking/"&gt;sorting and viewing errors by user&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rollbar.com/docs/custom-grouping/"&gt;setting up custom rules to group errors&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There is a lot more to learn about &lt;a href="/web-development.html"&gt;web development&lt;/a&gt;
and &lt;a href="/deployments.html"&gt;deployments&lt;/a&gt; so keep learning by reading up on 
&lt;a href="/flask.html"&gt;Flask&lt;/a&gt; and other &lt;a href="/web-frameworks.html"&gt;web frameworks&lt;/a&gt; 
such as &lt;a href="/django.html"&gt;Django&lt;/a&gt;, &lt;a href="/pyramid.html"&gt;Pyramid&lt;/a&gt; and 
&lt;a href="/sanic.html"&gt;Sanic&lt;/a&gt;. You can also learn more about integrating Rollbar
with Python applications via 
&lt;a href="https://rollbar.com/docs/notifier/pyrollbar/"&gt;their Python documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Questions? Let me know via 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/issues"&gt;a GitHub issue ticket on the Full Stack Python repository&lt;/a&gt;, 
on Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See something wrong in this blog post? Fork
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/170723-monitor-flask-apps.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request with a fix.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Sun, 23 Jul 2017 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2017-07-23:blog/hosted-monitoring-flask-web-apps.html</guid></item><item><title>How to Create Your First Static Site with Pelican and Jinja2</title><link>https://www.fullstackpython.com/blog/generating-static-websites-pelican-jinja2-markdown.html</link><description>&lt;p&gt;&lt;a href="/pelican.html"&gt;Pelican&lt;/a&gt; is an incredibly well-built Python tool for 
&lt;a href="/static-site-generator.html"&gt;creating static sites&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.fullstackpython.com/"&gt;Full Stack Python&lt;/a&gt; is generated with 
Pelican, &lt;a href="/jinja2.html"&gt;Jinja2 templates&lt;/a&gt; and &lt;a href="/markdown.html"&gt;Markdown&lt;/a&gt;.
This site is deployed to Amazon S3 and currently handles over one hundred 
thousand readers per month. There are never scaling concerns because a static
site is pre-generated before deployment and a web server simply responds
with existing files rather than executing any code on the server during
the HTTP request-response cycle.&lt;/p&gt;
&lt;p&gt;In this tutorial you will learn how to create your own
&lt;a href="/static-site-generator.html"&gt;static website&lt;/a&gt; from scratch using 
&lt;a href="/pelican.html"&gt;Pelican&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170609-static-sites-pelican/gunship-bootstrap-css.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Articles page after Bootstrap CSS has been added."&gt;&lt;/p&gt;
&lt;p&gt;Our simple static site will have pages that look like the above screenshot
but the entire site can be easily customized and expanded with your own design 
and content.&lt;/p&gt;
&lt;h2&gt;Our Tools&lt;/h2&gt;
&lt;p&gt;This tutorial should work with either &lt;a href="/python-2-or-3.html"&gt;Python 2 or 3&lt;/a&gt;, 
but Python 3 is strongly recommended for all new applications. I used
&lt;a href="https://www.python.org/downloads/release/python-361/"&gt;Python 3.6.1&lt;/a&gt; to 
write this post. In addition to Python, throughout this tutorial we 
will also use the following 
&lt;a href="/application-dependencies.html"&gt;application dependencies&lt;/a&gt;: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/pelican.html"&gt;Pelican&lt;/a&gt; 
  &lt;a href="/static-site-generator.html"&gt;static site generator&lt;/a&gt;, 
  &lt;a href="https://github.com/getpelican/pelican/releases/tag/3.7.1"&gt;version 3.7.1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/markdown.html"&gt;Markdown&lt;/a&gt; parsing library to handle Markdown as a content 
  input format, version 
  &lt;a href="https://github.com/waylan/Python-Markdown/releases/tag/2.6.8-final"&gt;2.6.8&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/jinja2.html"&gt;Jinja2&lt;/a&gt;, a Python &lt;a href="/template-engines.html"&gt;template engine&lt;/a&gt;, 
  version &lt;a href="https://github.com/pallets/jinja/releases/tag/2.9.6"&gt;2.9.6&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pip.pypa.io/en/stable/"&gt;pip&lt;/a&gt; and 
  &lt;a href="https://virtualenv.pypa.io/en/latest/"&gt;virtualenv&lt;/a&gt;, which come
  packaged with Python 3, to install and isolate the Pelican, Markdown,
  and Jinja2 libraries from any of your other Python projects&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you need help getting your 
&lt;a href="/development-environments.html"&gt;development environment&lt;/a&gt; configured, take a 
look at
&lt;a href="/blog/python-3-flask-green-unicorn-ubuntu-1604-xenial-xerus.html"&gt;this guide for setting up Python 3 and Flask on Ubuntu 16.04 LTS&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;All code in this blog post is available open source under the MIT license 
on GitHub under the 
&lt;a href="https://github.com/fullstackpython/blog-code-examples/tree/master/generating-static-websites-pelican-jinja2-markdown"&gt;generating-static-websites-pelican-jinja2-markdown directory of the blog-code-examples repository&lt;/a&gt;.
Use and abuse the source code as you like for your own applications.&lt;/p&gt;
&lt;h2&gt;Install the Pelican and Markdown libraries&lt;/h2&gt;
&lt;p&gt;Start by creating a new virtual environment for your project. My virtualenv
is named &lt;code&gt;staticsite&lt;/code&gt; but you can name yours whatever matches the project 
you are creating.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python3 -m venv staticsite
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Activate the virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;source staticsite/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The virtualenv will prepend its name to your command prompt when it is
activated.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170609-static-sites-pelican/activate-virtualenv.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Create and activate the Python virtual environment."&gt;&lt;/p&gt;
&lt;p&gt;Install the appropriate dependencies after your virtualenv is activated. 
Use the &lt;code&gt;pip&lt;/code&gt; command to install Pelican and Markdown, which will also 
install Jinja2 because Pelican specifies it as a dependency.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install &lt;span class="nv"&gt;pelican&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.7.1 &lt;span class="nv"&gt;markdown&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.6.8
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Run the &lt;code&gt;pip&lt;/code&gt; command and after everything is installed you should see output
similar to the following "Successfully installed" message.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Installing collected packages: pygments, pytz, six, feedgenerator, blinker, unidecode, MarkupSafe, jinja2, python-dateutil, docutils, pelican, markdown
  Running setup.py install &lt;span class="k"&gt;for&lt;/span&gt; feedgenerator ... &lt;span class="k"&gt;done&lt;/span&gt;
  Running setup.py install &lt;span class="k"&gt;for&lt;/span&gt; blinker ... &lt;span class="k"&gt;done&lt;/span&gt;
  Running setup.py install &lt;span class="k"&gt;for&lt;/span&gt; MarkupSafe ... &lt;span class="k"&gt;done&lt;/span&gt;
  Running setup.py install &lt;span class="k"&gt;for&lt;/span&gt; markdown ... &lt;span class="k"&gt;done&lt;/span&gt;
Successfully installed MarkupSafe-1.0 blinker-1.4 docutils-0.13.1 feedgenerator-1.9 jinja2-2.9.6 markdown-2.6.8 pelican-3.7.1 pygments-2.2.0 python-dateutil-2.6.0 pytz-2017.2 six-1.10.0 unidecode-0.4.20
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now that our dependencies are installed into the virtualenv we can start 
building our static site.&lt;/p&gt;
&lt;h2&gt;Generate a Basic Site&lt;/h2&gt;
&lt;p&gt;Create a new directory to store your project. My site will contain some of 
my favorite &lt;a href="https://www.youtube.com/watch?v=uYRZV8dV10w"&gt;retro synthwave&lt;/a&gt; 
artists as examples, but of course your site can contain whatever subjects 
that you want.&lt;/p&gt;
&lt;p&gt;Change into the project directory after creating it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;mkdir retrosynth
cd retrosynth
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Run the &lt;code&gt;pelican-quickstart&lt;/code&gt; command within the new project directory.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;staticsite&lt;span class="o"&gt;)&lt;/span&gt; $ pelican-quickstart
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The quickstart script will rattle off a bunch of questions. Follow
along with the answers below or modify them for your own site name and
desired configuration.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Welcome to pelican-quickstart v3.7.1.

This script will &lt;span class="nb"&gt;help&lt;/span&gt; you create a new Pelican-based website.

Please answer the following questions so this script can generate the files
needed by Pelican.


&amp;gt; Where &lt;span class="k"&gt;do&lt;/span&gt; you want to create your new web site? &lt;span class="o"&gt;[&lt;/span&gt;.&lt;span class="o"&gt;]&lt;/span&gt;  
&amp;gt; What will be the title of this web site? RetroSynthwave
&amp;gt; Who will be the author of this web site? Matt Makai
&amp;gt; What will be the default language of this web site? &lt;span class="o"&gt;[&lt;/span&gt;en&lt;span class="o"&gt;]&lt;/span&gt; 
&amp;gt; Do you want to specify a URL prefix? e.g., http://example.com   &lt;span class="o"&gt;(&lt;/span&gt;Y/n&lt;span class="o"&gt;)&lt;/span&gt; n
&amp;gt; Do you want to &lt;span class="nb"&gt;enable&lt;/span&gt; article pagination? &lt;span class="o"&gt;(&lt;/span&gt;Y/n&lt;span class="o"&gt;)&lt;/span&gt; n
&amp;gt; What is your &lt;span class="nb"&gt;time&lt;/span&gt; zone? &lt;span class="o"&gt;[&lt;/span&gt;Europe/Paris&lt;span class="o"&gt;]&lt;/span&gt; America/New_York
&amp;gt; Do you want to generate a Fabfile/Makefile to automate generation and publishing? &lt;span class="o"&gt;(&lt;/span&gt;Y/n&lt;span class="o"&gt;)&lt;/span&gt;y
&amp;gt; Do you want an auto-reload &lt;span class="p"&gt;&amp;amp;&lt;/span&gt; simpleHTTP script to assist with theme and site development? &lt;span class="o"&gt;(&lt;/span&gt;Y/n&lt;span class="o"&gt;)&lt;/span&gt; y
&amp;gt; Do you want to upload your website using FTP? &lt;span class="o"&gt;(&lt;/span&gt;y/N&lt;span class="o"&gt;)&lt;/span&gt; n
&amp;gt; Do you want to upload your website using SSH? &lt;span class="o"&gt;(&lt;/span&gt;y/N&lt;span class="o"&gt;)&lt;/span&gt; n
&amp;gt; Do you want to upload your website using Dropbox? &lt;span class="o"&gt;(&lt;/span&gt;y/N&lt;span class="o"&gt;)&lt;/span&gt; n
&amp;gt; Do you want to upload your website using S3? &lt;span class="o"&gt;(&lt;/span&gt;y/N&lt;span class="o"&gt;)&lt;/span&gt; y
&amp;gt; What is the name of your S3 bucket? &lt;span class="o"&gt;[&lt;/span&gt;my_s3_bucket&lt;span class="o"&gt;]&lt;/span&gt; 
&amp;gt; Do you want to upload your website using Rackspace Cloud Files? &lt;span class="o"&gt;(&lt;/span&gt;y/N&lt;span class="o"&gt;)&lt;/span&gt; n
&amp;gt; Do you want to upload your website using GitHub Pages? &lt;span class="o"&gt;(&lt;/span&gt;y/N&lt;span class="o"&gt;)&lt;/span&gt; n
Done. Your new project is available at /Users/matt/devel/py/retrosynth
&lt;span class="o"&gt;(&lt;/span&gt;staticsite&lt;span class="o"&gt;)&lt;/span&gt; $ 
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;What did we just create using Pelican's quickstart script? Check out
the new files in the directory.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;staticsite&lt;span class="o"&gt;)&lt;/span&gt; $ ls
Makefile        develop_server.sh   pelicanconf.py
content         fabfile.py          publishconf.py
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The quickstart created five files and one new directory:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Makefile&lt;/code&gt;: &lt;code&gt;make&lt;/code&gt; command convenience tasks for common operations such as 
  running a development server, building a site and cleaning extraneous 
  build files&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fabfile.py&lt;/code&gt;: A &lt;a href="http://www.fabfile.org/"&gt;Fabric&lt;/a&gt; file that has some of 
  the same types of commands as the &lt;code&gt;Makefile&lt;/code&gt;. Fabric is a wonderful code 
  library but for now I recommend skipping the Fabric file because 
  unfortunately Fabric does not yet support Python 3.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;develop_server.sh&lt;/code&gt;: shell script for running the development server&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pelicanconf.py&lt;/code&gt;: settings file for your Pelican project. If you are used
  to earlier versions of Pelican this file was instead named &lt;code&gt;settings.py&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;publishconf.py&lt;/code&gt;: another (optional) settings file that can be considered 
  as a "production" settings file when you move past the development phase
  and want to deploy your site&lt;/li&gt;
&lt;li&gt;&lt;code&gt;content&lt;/code&gt;: location for your markup files, which should be stored under
  &lt;code&gt;pages&lt;/code&gt; and &lt;code&gt;posts&lt;/code&gt; directories&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We can use these files as the base for our new static site. Let's see what
it looks like by default by running it via the &lt;code&gt;devserver&lt;/code&gt; task in the 
Makefile.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;make devserver
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The Pelican development server will start serving up your site with a 
daemon process. Go to &lt;a href="http://localhost:8000"&gt;localhost:8000&lt;/a&gt; in your web 
browser and you will see the first version of your static site.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170609-static-sites-pelican/default-style.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Default styling on the Pelican static site."&gt;&lt;/p&gt;
&lt;p&gt;What if you don't have &lt;code&gt;make&lt;/code&gt; installed on your system? Change into the
&lt;code&gt;output&lt;/code&gt; directory and use the &lt;code&gt;python -m http.server&lt;/code&gt; command to use the
built-in Python 3 HTTP server for your generated files.&lt;/p&gt;
&lt;p&gt;When you want to kill the development server look for a file named 
&lt;code&gt;pelican.pid&lt;/code&gt;under your project directory. The &lt;code&gt;pelican.pid&lt;/code&gt; file is created
by Pelican and contains the process ID for your development server.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(staticsite) $ cat pelican.pid 
1365
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Use the &lt;code&gt;ps&lt;/code&gt; and &lt;code&gt;grep&lt;/code&gt; commands to view the process then stop the process
with the &lt;code&gt;kill&lt;/code&gt; command as follows. Remember that your process ID will almost
definitely be different from the &lt;code&gt;1365&lt;/code&gt; ID for my process.&lt;/p&gt;
&lt;p&gt;Kill the development server now so that we can use different commands to 
serve our site after we create our initial content.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;staticsite&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt; &lt;span class="n"&gt;ps&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;grep&lt;/span&gt; &lt;span class="mi"&gt;1365&lt;/span&gt;
 &lt;span class="mi"&gt;1365&lt;/span&gt; &lt;span class="n"&gt;ttys003&lt;/span&gt;    &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;01.43&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Library&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Frameworks&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Python&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;framework&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Versions&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;3.6&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Resources&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Python&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Contents&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;MacOS&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Python&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;matt&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Envs&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;staticsite&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pelican&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;autoreload&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;matt&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;devel&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;retrosynth&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;matt&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;devel&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;retrosynth&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;matt&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;devel&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;retrosynth&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pelicanconf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;
 &lt;span class="mi"&gt;1411&lt;/span&gt; &lt;span class="n"&gt;ttys003&lt;/span&gt;    &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;00.00&lt;/span&gt; &lt;span class="n"&gt;grep&lt;/span&gt; &lt;span class="mi"&gt;1365&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;staticsite&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt; &lt;span class="n"&gt;kill&lt;/span&gt; &lt;span class="mi"&gt;1365&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;staticsite&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt; &lt;span class="n"&gt;ps&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;grep&lt;/span&gt; &lt;span class="mi"&gt;1365&lt;/span&gt;
 &lt;span class="mi"&gt;1413&lt;/span&gt; &lt;span class="n"&gt;ttys003&lt;/span&gt;    &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;00.00&lt;/span&gt; &lt;span class="n"&gt;grep&lt;/span&gt; &lt;span class="mi"&gt;1365&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;It is up to you whether you want to use the development server or not
while creating your site. Every time I want to view my changes for 
Full Stack Python I regenerate the site using my own Makefile that 
wraps the &lt;code&gt;pelican&lt;/code&gt; command. The &lt;code&gt;python -m http.server&lt;/code&gt; command constantly 
serves up each build's changes.&lt;/p&gt;
&lt;p&gt;Alright, now that we have our starter files we can get to work creating
some initial content.&lt;/p&gt;
&lt;h2&gt;Write Some Content&lt;/h2&gt;
&lt;p&gt;Pelican can accept both &lt;a href="/markdown.html"&gt;Markdown&lt;/a&gt; and reStructureText
markup files as input.&lt;/p&gt;
&lt;p&gt;Make a new subdirectory under the &lt;code&gt;content&lt;/code&gt; named &lt;code&gt;posts&lt;/code&gt;. Change into
the &lt;code&gt;posts&lt;/code&gt; directory. Create a new file named &lt;code&gt;gunship.markdown&lt;/code&gt; with
the following content.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;title: Gunship
slug: gunship
category: bands
date: 2017-06-09
modified: 2017-06-09


[&lt;span class="nt"&gt;Gunship&lt;/span&gt;](&lt;span class="na"&gt;https://www.gunshipmusic.com/&lt;/span&gt;) is a &lt;span class="ge"&gt;*retro synthwave*&lt;/span&gt; artist out of the UK.

[&lt;span class="nt"&gt;Revel in Your Time&lt;/span&gt;](&lt;span class="na"&gt;https://www.youtube.com/watch?v=uYRZV8dV10w&lt;/span&gt;), 
[&lt;span class="nt"&gt;Tech Noir&lt;/span&gt;](&lt;span class="na"&gt;https://www.youtube.com/watch?v=-nC5TBv3sfU&lt;/span&gt;), 
[&lt;span class="nt"&gt;Fly for Your Life&lt;/span&gt;](&lt;span class="na"&gt;https://www.youtube.com/watch?v=Jv1ZN8c4_Gs&lt;/span&gt;) 
and 
[&lt;span class="nt"&gt;The Mountain&lt;/span&gt;](&lt;span class="na"&gt;https://www.youtube.com/watch?v=-HYRTJr8EyA&lt;/span&gt;) 
are all quality songs by Gunship. Check out those amazing music videos!

Also take a look at other retro synthwave artists such as
[&lt;span class="nt"&gt;Trevor Something&lt;/span&gt;](&lt;span class="na"&gt;https://trevorsomething.bandcamp.com/&lt;/span&gt;), 
[&lt;span class="nt"&gt;Droid Bishop&lt;/span&gt;](&lt;span class="na"&gt;https://droidbishop.bandcamp.com/&lt;/span&gt;),
[&lt;span class="nt"&gt;FM-84&lt;/span&gt;](&lt;span class="na"&gt;https://fm84.bandcamp.com/&lt;/span&gt;)
and 
[&lt;span class="nt"&gt;Daniel Deluxe&lt;/span&gt;](&lt;span class="na"&gt;https://danieldeluxe.bandcamp.com/&lt;/span&gt;).
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Our &lt;code&gt;make&lt;/code&gt; file can also help us regenerate the site when changes occur
if we choose to not use the development server.&lt;/p&gt;
&lt;p&gt;We used the &lt;code&gt;devserver&lt;/code&gt; task earlier, but what other task are available 
to us via the &lt;code&gt;Makefile&lt;/code&gt;?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;make
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;code&gt;make&lt;/code&gt; should show us all of the following tasks we can run.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;Makefile&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;pelican&lt;/span&gt; &lt;span class="n"&gt;Web&lt;/span&gt; &lt;span class="n"&gt;site&lt;/span&gt;                                           

&lt;span class="n"&gt;Usage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;                                                                    
   &lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;                           &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;generate&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt; &lt;span class="n"&gt;site&lt;/span&gt;          
   &lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="n"&gt;clean&lt;/span&gt;                          &lt;span class="n"&gt;remove&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;generated&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;         
   &lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="n"&gt;regenerate&lt;/span&gt;                     &lt;span class="n"&gt;regenerate&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="n"&gt;upon&lt;/span&gt; &lt;span class="n"&gt;modification&lt;/span&gt; 
   &lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="n"&gt;publish&lt;/span&gt;                        &lt;span class="n"&gt;generate&lt;/span&gt; &lt;span class="n"&gt;using&lt;/span&gt; &lt;span class="n"&gt;production&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt; 
   &lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="n"&gt;serve&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;              &lt;span class="n"&gt;serve&lt;/span&gt; &lt;span class="n"&gt;site&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt;
   &lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="n"&gt;serve&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;global&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;SERVER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="n"&gt;serve&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;    
   &lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="n"&gt;devserver&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;          &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;restart&lt;/span&gt; &lt;span class="n"&gt;develop_server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sh&lt;/span&gt;    
   &lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="n"&gt;stopserver&lt;/span&gt;                     &lt;span class="n"&gt;stop&lt;/span&gt; &lt;span class="n"&gt;local&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;                  
   &lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="n"&gt;ssh_upload&lt;/span&gt;                     &lt;span class="n"&gt;upload&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt; &lt;span class="n"&gt;site&lt;/span&gt; &lt;span class="n"&gt;via&lt;/span&gt; &lt;span class="n"&gt;SSH&lt;/span&gt;        
   &lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="n"&gt;rsync_upload&lt;/span&gt;                   &lt;span class="n"&gt;upload&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt; &lt;span class="n"&gt;site&lt;/span&gt; &lt;span class="n"&gt;via&lt;/span&gt; &lt;span class="n"&gt;rsync&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;ssh&lt;/span&gt;  
   &lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="n"&gt;dropbox_upload&lt;/span&gt;                 &lt;span class="n"&gt;upload&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt; &lt;span class="n"&gt;site&lt;/span&gt; &lt;span class="n"&gt;via&lt;/span&gt; &lt;span class="n"&gt;Dropbox&lt;/span&gt;    
   &lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="n"&gt;ftp_upload&lt;/span&gt;                     &lt;span class="n"&gt;upload&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt; &lt;span class="n"&gt;site&lt;/span&gt; &lt;span class="n"&gt;via&lt;/span&gt; &lt;span class="n"&gt;FTP&lt;/span&gt;        
   &lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="n"&gt;s3_upload&lt;/span&gt;                      &lt;span class="n"&gt;upload&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt; &lt;span class="n"&gt;site&lt;/span&gt; &lt;span class="n"&gt;via&lt;/span&gt; &lt;span class="n"&gt;S3&lt;/span&gt;         
   &lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="n"&gt;cf_upload&lt;/span&gt;                      &lt;span class="n"&gt;upload&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt; &lt;span class="n"&gt;site&lt;/span&gt; &lt;span class="n"&gt;via&lt;/span&gt; &lt;span class="n"&gt;Cloud&lt;/span&gt; &lt;span class="n"&gt;Files&lt;/span&gt;
   &lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="n"&gt;github&lt;/span&gt;                         &lt;span class="n"&gt;upload&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt; &lt;span class="n"&gt;site&lt;/span&gt; &lt;span class="n"&gt;via&lt;/span&gt; &lt;span class="n"&gt;gh&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;pages&lt;/span&gt;   

&lt;span class="n"&gt;Set&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;DEBUG&lt;/span&gt; &lt;span class="n"&gt;variable&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="n"&gt;debugging&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;   
&lt;span class="n"&gt;Set&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;RELATIVE&lt;/span&gt; &lt;span class="n"&gt;variable&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="n"&gt;relative&lt;/span&gt; &lt;span class="n"&gt;urls&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;html&lt;/code&gt; task is what we are looking for to invoke the &lt;code&gt;pelican&lt;/code&gt; command 
using our &lt;code&gt;pelicanconf.py&lt;/code&gt; settings file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(staticsite) $ make html
pelican /Users/matt/devel/py/retrosynth/content -o /Users/matt/devel/py/retrosynth/output -s /Users/matt/devel/py/retrosynth/pelicanconf.py 
Done: Processed 1 article, 0 drafts, 0 pages and 0 hidden pages in 0.14 seconds.
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Our site has been regenerated and placed in the &lt;code&gt;output&lt;/code&gt; directory.&lt;/p&gt;
&lt;p&gt;If you used the &lt;code&gt;make devserver&lt;/code&gt; command earlier then change into the &lt;code&gt;output&lt;/code&gt;
directory and give Python's built-in HTTP server a shot with the following 
command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;cd output
python -m http.server
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Our first post in all its glory...&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170609-static-sites-pelican/gunship-first-post.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Gunship as our first band post on retro synthwave static site."&gt;&lt;/p&gt;
&lt;p&gt;You can change the HTTP server port binding by adding a number after the 
command, if you want to serve more than one static site at a time or you 
already have an application bound to port 8000.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python -m http.server 8005
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Note that if you are using Python 2 the equivalent HTTP server command is
&lt;code&gt;python -m SimpleHTTPServer&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Our site now has some very basic content. We could expand this start into 
many more posts and pages but let's learn how to modify the site 
configuration.&lt;/p&gt;
&lt;h2&gt;Edit Site Configuration&lt;/h2&gt;
&lt;p&gt;Pelican's quickstart assumed a bunch of defaults that may or may not be
applicable to your site. Open up the &lt;code&gt;pelicanconf.py&lt;/code&gt; file to change some
of the defaults.&lt;/p&gt;
&lt;p&gt;Look for the &lt;code&gt;TIMEZONE&lt;/code&gt; variable. If it's not right for your location
then modify it to your zone. Wikipedia has a handy
&lt;a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones"&gt;table of valid time zones values&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Also modify the &lt;code&gt;LINKS&lt;/code&gt; tuple to include your site (or Full Stack Python!)
instead of including the "you can modify those links" link. Change the
last line of &lt;code&gt;LINKS&lt;/code&gt; so it looks like the following tuple of tuples.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Blogroll&lt;/span&gt;
&lt;span class="n"&gt;LINKS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Pelican&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;http://getpelican.com/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
         &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Python.org&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;http://python.org/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
         &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Jinja2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;http://jinja.pocoo.org/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
         &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Full Stack Python&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://www.fullstackpython.com/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Instead of using the &lt;code&gt;make html&lt;/code&gt; file, this time we will invoke the
&lt;code&gt;pelican&lt;/code&gt; command directly from the command line. There is nothing wrong
with the &lt;code&gt;Makefile&lt;/code&gt;, but it is a good idea to get comfortable with Pelican
directly instead of only through build files.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pelican -s pelicanconf.py -o output content
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now run the HTTP server if you do not already have it running in another
terminal window.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;cd output
python -m http.server
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Head back to the browser and refresh to view the updated configuration.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170609-static-sites-pelican/updated-configuration.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="New links created by the pelicanconf.py configuration settings file."&gt;&lt;/p&gt;
&lt;p&gt;What happens when we click on the blog post title? It takes us to a 
very similar-looking page with the
&lt;a href="http://localhost:8000/gunship.html"&gt;localhost:8000/gunship.html&lt;/a&gt; URL.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170609-static-sites-pelican/gunship-post.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Gunship subpage for the site."&gt;&lt;/p&gt;
&lt;p&gt;Alright, we updated some basic site-wide data, but our site really could 
use a change of paint.&lt;/p&gt;
&lt;h2&gt;Modify Site Theme&lt;/h2&gt;
&lt;p&gt;Changing the site theme is really where you can turn a standard blog into
whatever type of site you want to build. While the default Pelican 
configuration creates a blog template, you do not need to have a 
chronological structure if it is not right for your website.&lt;/p&gt;
&lt;p&gt;Create a new directory under your project directory that is named
&lt;code&gt;theme&lt;/code&gt;. Within &lt;code&gt;theme&lt;/code&gt; create another directory named &lt;code&gt;templates&lt;/code&gt;.
&lt;code&gt;templates&lt;/code&gt; is where our &lt;a href="/jinja2.html"&gt;Jinja2&lt;/a&gt; templates will be stored and
can override the default theme.&lt;/p&gt;
&lt;p&gt;Start by creating a file named &lt;code&gt;base.html&lt;/code&gt; which will store the boilerplate
used by templates across the site. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;en&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;title&lt;/span&gt; &lt;span class="cp"&gt;%}{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;container&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;content&lt;/span&gt; &lt;span class="cp"&gt;%}{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
 &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; 
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Within &lt;code&gt;theme/templates&lt;/code&gt; create a file named &lt;code&gt;article.html&lt;/code&gt; that will have a
different theme for blog posts than the rest of the site. Fill &lt;code&gt;article.html&lt;/code&gt;
with the following Jinja2 markup.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;base.html&amp;quot;&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;title&lt;/span&gt; &lt;span class="cp"&gt;%}{{&lt;/span&gt; &lt;span class="nv"&gt;article.title&lt;/span&gt; &lt;span class="cp"&gt;}}{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;content&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;row&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;col-md-8&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;article.title&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Posted on &lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;article.date&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;article.content&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Next we will use a Jinja2 template to override the default &lt;code&gt;index.html&lt;/code&gt; main
page. Again within the &lt;code&gt;theme/templates&lt;/code&gt; directory, create a file named
&lt;code&gt;index.html&lt;/code&gt; with the following markup.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;base.html&amp;quot;&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;title&lt;/span&gt; &lt;span class="cp"&gt;%}{{&lt;/span&gt; &lt;span class="nv"&gt;SITENAME&lt;/span&gt; &lt;span class="cp"&gt;}}{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;content&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;row&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;col-md-8&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;SITENAME&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;article&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;articles&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;article.slug&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;.html&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;article.title&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Posted on &lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;article.date&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
   &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;article.content&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;truncate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;110&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
  &lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
   No posts yet!
  &lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endfor&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Regenerate the site and make sure you are serving it with the development
server or the &lt;code&gt;python -m http.server&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;Make sure to use the new &lt;code&gt;-t theme&lt;/code&gt; flag to specify that the Jinja2 
templates within the &lt;code&gt;theme&lt;/code&gt; directory should be applied to the site.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pelican -s pelicanconf.py -o output -t theme content
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Go to &lt;a href="http://localhost:8000"&gt;localhost:8000&lt;/a&gt; and refresh the page.
The styling on the main page is now different because it uses the &lt;code&gt;index.html&lt;/code&gt;
theme.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170609-static-sites-pelican/index-no-styling.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="The index.html page without any styling applied."&gt;&lt;/p&gt;
&lt;p&gt;Click on the title of the Gunship post. This page uses the &lt;code&gt;article.html&lt;/code&gt; 
template, although it's hard to tell because there is no 
&lt;a href="/cascading-style-sheets.html"&gt;CSS&lt;/a&gt; applied to the page.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170609-static-sites-pelican/gunship-no-styling.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Articles have an entirely different theme based on article.html markup."&gt;&lt;/p&gt;
&lt;p&gt;Pretty sparse! We can at least add the Bootstrap CSS to the HTML to 
align our content.&lt;/p&gt;
&lt;p&gt;Within &lt;code&gt;base.html&lt;/code&gt;, add the following line for Bootstrap under 
&lt;code&gt;&amp;lt;title&amp;gt;{% block title %}{% endblock %}&amp;lt;/title&amp;gt;&lt;/code&gt; and above &lt;code&gt;&amp;lt;/head&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;!--&lt;/span&gt; &lt;span class="n"&gt;Latest&lt;/span&gt; &lt;span class="n"&gt;compiled&lt;/span&gt; &lt;span class="kr"&gt;and&lt;/span&gt; &lt;span class="n"&gt;minified&lt;/span&gt; &lt;span class="n"&gt;Bootstrap&lt;/span&gt; &lt;span class="n"&gt;CSS&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;stylesheet&amp;quot;&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css&amp;quot;&lt;/span&gt; &lt;span class="n"&gt;integrity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u&amp;quot;&lt;/span&gt; &lt;span class="n"&gt;crossorigin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;anonymous&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Regenerate the site and refresh the Gunship page.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170609-static-sites-pelican/gunship-bootstrap-css.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Articles page after Bootstrap CSS has been added."&gt;&lt;/p&gt;
&lt;p&gt;Well at least our design has moved from 1996 to 2001. I am sure you can 
do a whole lot more to improve your own site's design.&lt;/p&gt;
&lt;p&gt;The new &lt;code&gt;base.html&lt;/code&gt; does not provide much of a theme yet but it at least 
provides a fresh start for completely customized sites.&lt;/p&gt;
&lt;h2&gt;What's next?&lt;/h2&gt;
&lt;p&gt;You generated your first &lt;a href="/pelican.html"&gt;Pelican&lt;/a&gt; static website using
&lt;a href="/markdown.html"&gt;Markdown&lt;/a&gt; and &lt;a href="/jinja2.html"&gt;Jinja2&lt;/a&gt;. Additional modifications
can be made to the Jinja2 templates and the content contained in the Markdown
files. &lt;/p&gt;
&lt;p&gt;Do you want to deploy your new static website to GitHub Pages or an S3 bucket?
Well, that's a story for another &lt;a href="/blog.html"&gt;Full Stack Python tutorial&lt;/a&gt;...&lt;/p&gt;
&lt;p&gt;Questions? Let me know via 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/issues"&gt;a GitHub issue ticket on the Full Stack Python repository&lt;/a&gt;, 
on Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;.  &lt;/p&gt;
&lt;p&gt;See something wrong in this blog post? Fork
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/170609-static-sites-pelican.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Fri, 09 Jun 2017 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2017-06-09:blog/generating-static-websites-pelican-jinja2-markdown.html</guid></item><item><title>Responsive Bar Charts with Bokeh, Flask and Python 3</title><link>https://www.fullstackpython.com/blog/responsive-bar-charts-bokeh-flask-python-3.html</link><description>&lt;p&gt;&lt;a href="/bokeh.html"&gt;Bokeh&lt;/a&gt; is a powerful open source Python library that allows 
developers to generate JavaScript data visualizations for their web 
applications &lt;em&gt;without writing any JavaScript&lt;/em&gt;. While learning a 
JavaScript-based data visualization library like &lt;a href="https://d3js.org/"&gt;d3.js&lt;/a&gt;
can be useful, it's often far easier to knock out a few lines of Python
code to get the job done.&lt;/p&gt;
&lt;p&gt;With Bokeh, we can create incredibly detailed interactive visualizations, 
or just traditional ones like the following bar chart.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170526-bar-charts-bokeh-flask/chart-example-64.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Responsive Bokeh bar chart with 64 bars."&gt;&lt;/p&gt;
&lt;p&gt;Let's use the 
&lt;a href="/flask.html"&gt;Flask&lt;/a&gt; &lt;a href="/web-frameworks.html"&gt;web framework&lt;/a&gt; with Bokeh to 
create custom bar charts in a Python web app.&lt;/p&gt;
&lt;h2&gt;Our Tools&lt;/h2&gt;
&lt;p&gt;This tutorial works with either &lt;a href="/python-2-or-3.html"&gt;Python 2 or 3&lt;/a&gt;, 
but Python 3 is strongly recommended for new applications. I used
&lt;a href="https://www.python.org/downloads/release/python-361/"&gt;Python 3.6.1&lt;/a&gt; while 
writing this post. In addition to Python throughout this tutorial we 
will also use the following 
&lt;a href="/application-dependencies.html"&gt;application dependencies&lt;/a&gt;: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/flask.html"&gt;Flask&lt;/a&gt; web framework, 
  &lt;a href="https://github.com/pallets/flask/releases/tag/0.12.2"&gt;version 0.12.2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/bokeh.html"&gt;Bokeh&lt;/a&gt; data visualization library, 
  &lt;a href="https://github.com/bokeh/bokeh/releases/tag/0.12.5"&gt;version 0.12.5&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/pandas.html"&gt;pandas&lt;/a&gt; data structures and analysis library, 
  &lt;a href="https://github.com/pandas-dev/pandas/releases/tag/v0.20.1"&gt;version 0.20.1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pip.pypa.io/en/stable/"&gt;pip&lt;/a&gt; and 
  &lt;a href="https://virtualenv.pypa.io/en/latest/"&gt;virtualenv&lt;/a&gt;, which come
  packaged with Python 3, to install and isolate the Flask, Bokeh,
  and pandas libraries from any other Python projects you might be 
  working on&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you need help getting your 
&lt;a href="/development-environments.html"&gt;development environment&lt;/a&gt; configured
before running this code, take a look at
&lt;a href="/blog/python-3-flask-green-unicorn-ubuntu-1604-xenial-xerus.html"&gt;this guide for setting up Python 3 and Flask on Ubuntu 16.04 LTS&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;All code in this blog post is available open source under the MIT license 
on GitHub under the 
&lt;a href="https://github.com/fullstackpython/blog-code-examples"&gt;bar-charts-bokeh-flask-python-3 directory of the blog-code-examples repository&lt;/a&gt;. 
Use and abuse the source code as you like for your own applications.&lt;/p&gt;
&lt;h2&gt;Installing Bokeh and Flask&lt;/h2&gt;
&lt;p&gt;Create a fresh virtual environment for this project to isolate our 
dependencies using the following command in the terminal. I typically run 
this command within a separate &lt;code&gt;venvs&lt;/code&gt; directory where all my virtualenvs
are store.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python3 -m venv barchart
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Activate the virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt; barchart/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The command prompt will change after activating the virtualenv:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170526-bar-charts-bokeh-flask/activate-virtualenv.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Activating our Python virtual environment on the command line."&gt;&lt;/p&gt;
&lt;p&gt;Keep in mind that you need to activate the virtualenv in every new terminal 
window where you want to use the virtualenv to run the project.&lt;/p&gt;
&lt;p&gt;Bokeh and Flask are installable into the now-activated virtualenv
using pip. Run this command to get the appropriate Bokeh and Flask
versions.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install bokeh==0.12.5 flask==0.12.2 pandas==0.20.1
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;After a brief download and installation period our required dependencies
should be installed within our virtualenv. Look for output to confirm 
everything worked.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;Installing&lt;/span&gt; &lt;span class="nv"&gt;collected&lt;/span&gt; &lt;span class="nv"&gt;packages&lt;/span&gt;: &lt;span class="nv"&gt;six&lt;/span&gt;, &lt;span class="nv"&gt;requests&lt;/span&gt;, &lt;span class="nv"&gt;PyYAML&lt;/span&gt;, &lt;span class="nv"&gt;python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;dateutil&lt;/span&gt;, &lt;span class="nv"&gt;MarkupSafe&lt;/span&gt;, &lt;span class="nv"&gt;Jinja2&lt;/span&gt;, &lt;span class="nv"&gt;numpy&lt;/span&gt;, &lt;span class="nv"&gt;tornado&lt;/span&gt;, &lt;span class="nv"&gt;bokeh&lt;/span&gt;, &lt;span class="nv"&gt;Werkzeug&lt;/span&gt;, &lt;span class="nv"&gt;itsdangerous&lt;/span&gt;, &lt;span class="nv"&gt;click&lt;/span&gt;, &lt;span class="nv"&gt;flask&lt;/span&gt;, &lt;span class="nv"&gt;pytz&lt;/span&gt;, &lt;span class="nv"&gt;pandas&lt;/span&gt;
  &lt;span class="nv"&gt;Running&lt;/span&gt; &lt;span class="nv"&gt;setup&lt;/span&gt;.&lt;span class="nv"&gt;py&lt;/span&gt; &lt;span class="nv"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;PyYAML&lt;/span&gt; ... &lt;span class="nv"&gt;done&lt;/span&gt;
  &lt;span class="nv"&gt;Running&lt;/span&gt; &lt;span class="nv"&gt;setup&lt;/span&gt;.&lt;span class="nv"&gt;py&lt;/span&gt; &lt;span class="nv"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;MarkupSafe&lt;/span&gt; ... &lt;span class="nv"&gt;done&lt;/span&gt;
  &lt;span class="nv"&gt;Running&lt;/span&gt; &lt;span class="nv"&gt;setup&lt;/span&gt;.&lt;span class="nv"&gt;py&lt;/span&gt; &lt;span class="nv"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;tornado&lt;/span&gt; ... &lt;span class="nv"&gt;done&lt;/span&gt;
  &lt;span class="nv"&gt;Running&lt;/span&gt; &lt;span class="nv"&gt;setup&lt;/span&gt;.&lt;span class="nv"&gt;py&lt;/span&gt; &lt;span class="nv"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;bokeh&lt;/span&gt; ... &lt;span class="nv"&gt;done&lt;/span&gt;
  &lt;span class="nv"&gt;Running&lt;/span&gt; &lt;span class="nv"&gt;setup&lt;/span&gt;.&lt;span class="nv"&gt;py&lt;/span&gt; &lt;span class="nv"&gt;install&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;itsdangerous&lt;/span&gt; ... &lt;span class="nv"&gt;done&lt;/span&gt;
&lt;span class="nv"&gt;Successfully&lt;/span&gt; &lt;span class="nv"&gt;installed&lt;/span&gt; &lt;span class="nv"&gt;Jinja2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;.&lt;span class="mi"&gt;9&lt;/span&gt;.&lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="nv"&gt;MarkupSafe&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;.&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="nv"&gt;PyYAML&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;.&lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="nv"&gt;Werkzeug&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;.&lt;span class="mi"&gt;12&lt;/span&gt;.&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="nv"&gt;bokeh&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;.&lt;span class="mi"&gt;12&lt;/span&gt;.&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="nv"&gt;click&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;.&lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="nv"&gt;flask&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;.&lt;span class="mi"&gt;12&lt;/span&gt;.&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="nv"&gt;itsdangerous&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;.&lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="nv"&gt;numpy&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;.&lt;span class="mi"&gt;12&lt;/span&gt;.&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nv"&gt;pandas&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;.&lt;span class="mi"&gt;20&lt;/span&gt;.&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nv"&gt;python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;dateutil&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;.&lt;span class="mi"&gt;6&lt;/span&gt;.&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="nv"&gt;pytz&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2017&lt;/span&gt;.&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="nv"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;.&lt;span class="mi"&gt;14&lt;/span&gt;.&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="nv"&gt;six&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;.&lt;span class="mi"&gt;10&lt;/span&gt;.&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="nv"&gt;tornado&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;.&lt;span class="mi"&gt;5&lt;/span&gt;.&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now we can start building our web application.&lt;/p&gt;
&lt;h2&gt;Starting Our Flask App&lt;/h2&gt;
&lt;p&gt;We are going to first code a basic Flask application then add our bar 
chart to the rendered page.&lt;/p&gt;
&lt;p&gt;Create a folder for your project then within it create a file named
&lt;code&gt;app.py&lt;/code&gt; with these initial contents:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;render_template&lt;/span&gt;


&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;lt;int:bars_count&amp;gt;/&amp;quot;&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;chart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bars_count&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;bars_count&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;bars_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;chart.html&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bars_count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bars_count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The above code is a short one-route &lt;a href="/flask.html"&gt;Flask&lt;/a&gt; application
that defines the &lt;code&gt;chart&lt;/code&gt; function. &lt;code&gt;chart&lt;/code&gt; takes in an arbitrary integer
as input which will later be used to define how much data we want in our
bar chart. The &lt;code&gt;render_template&lt;/code&gt; function within &lt;code&gt;chart&lt;/code&gt; will use a template
from Flask's default &lt;a href="/template-engines.html"&gt;template engine&lt;/a&gt; named
&lt;a href="/jinja2.html"&gt;Jinja2&lt;/a&gt; to output HTML. &lt;/p&gt;
&lt;p&gt;The last two lines in the allow us to run the Flask application from the
command line on port 5000 in debug mode. Never use debug mode for production,
that's what &lt;a href="/wsgi-servers.html"&gt;WSGI servers&lt;/a&gt; like 
&lt;a href="/green-unicorn-gunicorn.html"&gt;Gunicorn&lt;/a&gt; are built for.&lt;/p&gt;
&lt;p&gt;Create a subdirectory within your project folder named &lt;code&gt;templates&lt;/code&gt;. Within
&lt;code&gt;templates&lt;/code&gt; create a file name &lt;code&gt;chart.html&lt;/code&gt;. &lt;code&gt;chart.html&lt;/code&gt; was referenced in 
the &lt;code&gt;chart&lt;/code&gt; function of our &lt;code&gt;app.py&lt;/code&gt; file so we need to create it before our
app will run properly. Populate &lt;code&gt;chart.html&lt;/code&gt; with the following 
&lt;a href="/jinja2.html"&gt;Jinja2&lt;/a&gt; markup.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Bar charts with Bokeh!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Bugs found over the past &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;bars_count&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt; days&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;code&gt;chart.html&lt;/code&gt;'s boilerplate displays the number of bars passed into the
&lt;code&gt;chart&lt;/code&gt; function via the URL. &lt;/p&gt;
&lt;p&gt;The &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; tag's message on the number of bugs found goes along with our
sample app's theme. We will pretend to be charting the number of bugs 
found by automated tests run each day.&lt;/p&gt;
&lt;p&gt;We can test our application out now.&lt;/p&gt;
&lt;p&gt;Make sure your virtualenv is still activated and that you are in the 
base directory of your project where &lt;code&gt;app.py&lt;/code&gt; is located. Run &lt;code&gt;app.py&lt;/code&gt;
using the &lt;code&gt;python&lt;/code&gt; command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$(barchart) python app.py
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Go to &lt;a href="http://localhost:5000/16/"&gt;localhost:5000/16/&lt;/a&gt; in your web browser.
You should see a large message that changes when you modify the URL.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170526-bar-charts-bokeh-flask/basic-app-no-chart.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Simple Flask app without bar chart"&gt;&lt;/p&gt;
&lt;p&gt;Our simple Flask route is in place but that's not very exciting. Time
to add our bar chart.&lt;/p&gt;
&lt;h2&gt;Generating the Bar Chart&lt;/h2&gt;
&lt;p&gt;We can build on the basic Flask app foundation that we just wrote with
some new Python code that uses &lt;a href="/bokeh.html"&gt;Bokeh&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;Open &lt;code&gt;app.py&lt;/code&gt; back up and change the top of the file to include the
following imports.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;random&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;bokeh.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HoverTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FactorRange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Plot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LinearAxis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Grid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="n"&gt;Range1d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;bokeh.models.glyphs&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;VBar&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;bokeh.plotting&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;figure&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;bokeh.charts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Bar&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;bokeh.embed&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;components&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;bokeh.models.sources&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ColumnDataSource&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;render_template&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Throughout the rest of the file we will need these Bokeh imports along
with the &lt;code&gt;random&lt;/code&gt; module to generate data and our bar chart.&lt;/p&gt;
&lt;p&gt;Our bar chart will use "software bugs found" as a theme. The data will
be randomly generated each time the page is refreshed. In a real application
you'd have a more stable and useful data source!&lt;/p&gt;
&lt;p&gt;Continue modifying &lt;code&gt;app.py&lt;/code&gt; so the section after the imports looks like
the following code. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;lt;int:bars_count&amp;gt;/&amp;quot;&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;chart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bars_count&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;bars_count&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;bars_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;days&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;bugs&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;costs&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bars_count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;days&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bugs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;costs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1000.00&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;hover&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_hover_tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;plot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_bar_chart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Bugs found per day&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;days&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="s2"&gt;&amp;quot;bugs&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hover&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;components&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plot&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;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;chart.html&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bars_count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bars_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                           &lt;span class="n"&gt;the_div&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;the_script&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;chart&lt;/code&gt; function gains three new lists that are randomly generated
by 
&lt;a href="https://docs.python.org/3/library/random.html"&gt;Python 3's super-handy random module&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;chart&lt;/code&gt; calls two functions, &lt;code&gt;create_hover_tool&lt;/code&gt; and &lt;code&gt;create_bar_chart&lt;/code&gt;.
We haven't written those functions yet so continue adding code below &lt;code&gt;chart&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_hover_tool&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# we&amp;#39;ll code this function in a moment&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_bar_chart&lt;/span&gt;&lt;span class="p"&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;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hover_tool&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Creates a bar chart plot with the exact styling for the centcom&lt;/span&gt;
&lt;span class="sd"&gt;       dashboard. Pass in data as a dictionary, desired plot title,&lt;/span&gt;
&lt;span class="sd"&gt;       name of x axis, y axis and the hover tool HTML.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ColumnDataSource&lt;/span&gt;&lt;span class="p"&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;xdr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FactorRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;factors&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;x_name&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;ydr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Range1d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&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="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&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;y_name&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;hover_tool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;hover_tool&lt;/span&gt;&lt;span class="p"&gt;,]&lt;/span&gt;

    &lt;span class="n"&gt;plot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x_range&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;xdr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_range&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ydr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plot_width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="n"&gt;plot_height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h_symmetry&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v_symmetry&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="n"&gt;min_border&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="n"&gt;toolbar_location&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;above&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="n"&gt;responsive&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;outline_line_color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;#666666&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;glyph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;x_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;y_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bottom&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="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;fill_color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;#e12127&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_glyph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;glyph&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;xaxis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LinearAxis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;yaxis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LinearAxis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_layout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Grid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dimension&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="n"&gt;ticker&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;xaxis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_layout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Grid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dimension&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;yaxis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toolbar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;min_border_top&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xgrid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grid_line_color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ygrid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grid_line_color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;#999999&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yaxis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;axis_label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Bugs found&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ygrid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grid_line_alpha&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;
    &lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xaxis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;axis_label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Days after app deployment&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xaxis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;major_label_orientation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;plot&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;There is a whole lot of new code above so let's break it down. The 
&lt;code&gt;create_hover_tool&lt;/code&gt; function does not do anything yet, it simply
returns &lt;code&gt;None&lt;/code&gt;, which we can use if we do not want a hover tool. The hover
tool is an overlay that appears when we move our mouse cursor over one of
the bars or touch a bar on a touchscreen so we can see more data about the
bar.&lt;/p&gt;
&lt;p&gt;Within the &lt;code&gt;create_bar_chart&lt;/code&gt; function we take in our generated data source 
and convert it into a &lt;code&gt;ColumnDataSource&lt;/code&gt; object that is one type of input
object we can pass to Bokeh functions. We specify two ranges for the chart's
x and y axes.&lt;/p&gt;
&lt;p&gt;Since we do not yet have a hover tool the &lt;code&gt;tools&lt;/code&gt; list will remain empty.
The line where we create &lt;code&gt;plot&lt;/code&gt; using the &lt;code&gt;figure&lt;/code&gt; function is where a lot of 
the magic happens. We specify all the parameters we want our graph to have
such as the size, toolbar, borders and whether or not the graph should be
responsive upon changing the web browser size.&lt;/p&gt;
&lt;p&gt;We create vertical bars with the &lt;code&gt;VBar&lt;/code&gt; object and add them to the plot using
the &lt;code&gt;add_glyph&lt;/code&gt; function that combines our source data with the &lt;code&gt;VBar&lt;/code&gt; 
specification.&lt;/p&gt;
&lt;p&gt;The last lines of the function modify the look and feel of the graph. For
example I took away the &lt;code&gt;Bokeh&lt;/code&gt; logo by specifying &lt;code&gt;plot.toolbar.logo = None&lt;/code&gt;
and added labels to both axes. I recommend keeping the 
&lt;a href="http://bokeh.pydata.org/en/latest/docs/reference/plotting.html#bokeh-plotting"&gt;bokeh.plottin&lt;/a&gt;
documentation open to know what your options are for customizing your
visualizations.&lt;/p&gt;
&lt;p&gt;We just need a few updates to our &lt;code&gt;templates/chart.html&lt;/code&gt; file to display
the visualization. Open the file and add these 6 lines to the file.
Two of these lines are for the required CSS, two are JavaScript Bokeh
files and the remaining two are the generated chart.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Bar charts with Bokeh!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://cdn.pydata.org/bokeh/release/bokeh-0.12.5.min.css&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;stylesheet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.0.min.css&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;stylesheet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Bugs found over the past &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;bars_count&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt; days&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;the_div&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;safe&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://cdn.pydata.org/bokeh/release/bokeh-0.12.5.min.js&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.5.min.js&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;the_script&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;safe&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Alright, let's give our app a try with a simple chart of 4 bars. The
Flask app should automatically reload when you save &lt;code&gt;app.py&lt;/code&gt; with the new
code but if you shut down the development server fire it back up with the
&lt;code&gt;python app.py&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;Open your browser to &lt;a href="localhost:5000/4/"&gt;localhost:5000/4/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170526-bar-charts-bokeh-flask/chart-example-4.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Responsive Bokeh bar chart with 4 bars."&gt;&lt;/p&gt;
&lt;p&gt;That one looks a bit sparse, so we can crank it up by 4x to 16 bars
by going to &lt;a href="localhost:5000/16/"&gt;localhost:5000/16/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170526-bar-charts-bokeh-flask/chart-example-16.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Responsive Bokeh bar chart with 16 bars."&gt;&lt;/p&gt;
&lt;p&gt;Now another 4x to 128 bars with &lt;a href="localhost:5000/128/"&gt;localhost:5000/128/&lt;/a&gt;...&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170526-bar-charts-bokeh-flask/chart-example-128.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Responsive Bokeh bar chart with 128 bars."&gt;&lt;/p&gt;
&lt;p&gt;Looking good so far. But what about that hover tool to drill down into each 
bar for more data? We can add the hover with just a few lines of code
in the &lt;code&gt;create_hover_tool&lt;/code&gt; function.&lt;/p&gt;
&lt;h2&gt;Adding a Hover Tool&lt;/h2&gt;
&lt;p&gt;Within &lt;code&gt;app.py&lt;/code&gt; modify the &lt;code&gt;create_hover_tool&lt;/code&gt; to match the following
code.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_hover_tool&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Generates the HTML for the Bokeh&amp;#39;s hover data tool on our graph.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;hover_html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;      &amp;lt;div&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;        &amp;lt;span class=&amp;quot;hover-tooltip&amp;quot;&amp;gt;$x&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;      &amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;      &amp;lt;div&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;        &amp;lt;span class=&amp;quot;hover-tooltip&amp;quot;&amp;gt;@bugs bugs&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;      &amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;      &amp;lt;div&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;        &amp;lt;span class=&amp;quot;hover-tooltip&amp;quot;&amp;gt;$@costs&lt;/span&gt;&lt;span class="si"&gt;{0.00}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;      &amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="s2"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;HoverTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tooltips&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;hover_html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;It may look really odd to have HTML embedded within your Python application,
but that's how we specify what the hover tool should display. We use
&lt;code&gt;$x&lt;/code&gt; to show the bar's x axis, &lt;code&gt;@bugs&lt;/code&gt; to show the "bugs" field from our
data source, and &lt;code&gt;$@costs{0.00}&lt;/code&gt; to show the "costs" field formatted as
a dollar amount with exactly 2 decimal places.&lt;/p&gt;
&lt;p&gt;Make sure you changed &lt;code&gt;return None&lt;/code&gt; to &lt;code&gt;return HoverTool(tooltips=hover_html)&lt;/code&gt;
so we can see the results of our new function in the graph.&lt;/p&gt;
&lt;p&gt;Head back to the browser and reload the 
&lt;a href="http://localhost:5000/128"&gt;localhost:5000/128/&lt;/a&gt; page.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170526-bar-charts-bokeh-flask/chart-example-128-hover-tool.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Responsive Bokeh bar chart with 128 bars and showing the hover tool."&gt;&lt;/p&gt;
&lt;p&gt;Nice work! Try playing around with the number of bars in the URL and the
window size to see what the graph looks like under different conditions.&lt;/p&gt;
&lt;p&gt;The chart gets crowded with more than 100 or so bars, but you can give
it a try with whatever number of bars you want. Here is what an 
impractical amount of 50,000 bars looks like just for the heck of it:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170526-bar-charts-bokeh-flask/chart-example-50000.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Responsive Bokeh bar chart with 50000 bars."&gt;&lt;/p&gt;
&lt;p&gt;Yea, we may need to do some additional work to display more than a few 
hundred bars at a time.&lt;/p&gt;
&lt;h2&gt;What's next?&lt;/h2&gt;
&lt;p&gt;You just created a nifty configurable bar chart in Bokeh. Next you can 
modify the color scheme, change the input data source, try to create other 
types of charts or solve how to display very large numbers of bars.&lt;/p&gt;
&lt;p&gt;There is a lot more than Bokeh can do, so be sure to check out the 
&lt;a href="http://bokeh.pydata.org/en/latest/"&gt;official project documentation&lt;/a&gt; , 
&lt;a href="https://github.com/bokeh/bokeh"&gt;GitHub repository&lt;/a&gt;, 
the &lt;a href="/bokeh.html"&gt;Full Stack Python Bokeh page&lt;/a&gt; or take a look at 
&lt;a href="/table-of-contents.html"&gt;other topics on Full Stack Python&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Questions? Let me know via 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/issues"&gt;a GitHub issue ticket on the Full Stack Python repository&lt;/a&gt;, 
on Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See something wrong in this blog post? Fork
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/170526-bar-charts-bokeh.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Sun, 30 Jul 2017 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2017-05-26:blog/responsive-bar-charts-bokeh-flask-python-3.html</guid></item><item><title>How to Become A Successful Self-Taught Software Developer</title><link>https://www.fullstackpython.com/blog/become-successful-self-taught-software-developer.html</link><description>&lt;p&gt;I received the following question via email from someone spending
significant effort learning how to code in anticipation of obtaining 
full-time job with those skills. The question is also frequently 
asked by university students and coding bootcamp graduates. &lt;/p&gt;
&lt;p&gt;This post provides my current answer on how get your first full-time job
as a software developer. My answer assumes that the definition 
of "successful path" for a self-taught developer is getting a 
full-time position after investing so much time learning to code.&lt;/p&gt;
&lt;p&gt;Note though that as I describe in my answer below, I took 
&lt;a href="http://www.mattmakai.com/matt-makai-resume.pdf"&gt;a more "traditional" route&lt;/a&gt; 
to become a professional software developer. Therefore my response should 
be only one of many that you solicit while working towards making the 
leap from self-taught to professional software developer.&lt;/p&gt;
&lt;h2&gt;(Paraphrased) Original Question&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;I'm not sure what I should learn first to become a developer. &lt;/p&gt;
&lt;p&gt;Right now the path I am on is/was: Learn basic python fundamentals -&amp;gt; 
git/github -&amp;gt; ubuntu/linux OS--&amp;gt; flask/jinja2 --&amp;gt; Bootstrap -&amp;gt; 
SQLalchemy -&amp;gt; Docker -&amp;gt; Celery -&amp;gt; Redis -&amp;gt; AWS -&amp;gt; Django?!&lt;/p&gt;
&lt;p&gt;I don't know where JS / Angular2 / ECMAscript6 / HTML / CSS all fit 
into this...&lt;/p&gt;
&lt;p&gt;What is the ideal path to becoming a successful self-taught developer
so I can eventually get a job as a software developer?"&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;My tl;dr answer&lt;/h3&gt;
&lt;p&gt;Go very deep in one area you really enjoy working after you learn the 
fundamentals and get a broad overview of the language's ecosystem. Leverage
your depth in your targeted expertise area when you find teams that need
that skill to land your first full-time job.&lt;/p&gt;
&lt;h3&gt;Answer Context&lt;/h3&gt;
&lt;p&gt;Figuring out what order to go in when learning is definitely one of the 
trickiest problems for self-guided learners. I'm not sure my answer to your 
question is the best one that you can get because for better or worse I 
took four years of computer science (CS) in high school, followed by 
undergrad CS &amp;amp; grad school CS (while working as a full-time developer). 
That route seems like the "traditional developer" background. However, I 
will do my best to give an answer. You are definitely not the only person 
who faces this issue.&lt;/p&gt;
&lt;p&gt;I typically see self-taught and developer bootcamp grads feel like to 
get a job they have to learn everything from the 
&lt;a href="/databases.html"&gt;database backend&lt;/a&gt; up through the 
&lt;a href="/web-frameworks.html"&gt;web frameworks&lt;/a&gt; to every new 
&lt;a href="/javascript.html"&gt;JavaScript&lt;/a&gt; frontend framework that comes out,
but that's definitely not true. When you land that first full-time 
developer gig it will be because a development team sees you have a 
particular skill that their team lacks and they need help with on their
project(s).&lt;/p&gt;
&lt;h3&gt;Going deep&lt;/h3&gt;
&lt;p&gt;If you find yourself coding front-end stuff but wishing you could get 
back to optimizing the database, you should focus on going much, much 
deeper in database optimization. Learn as much as you can about SQL, 
DDLs, DMLs, &lt;a href="/object-relational-mappers-orms.html"&gt;ORMs&lt;/a&gt;, 
&lt;a href="/postgresql.html"&gt;PostgreSQL&lt;/a&gt;, database testing and performance tuning. 
Constantly go deeper. Spend most of your time coding but when possible also 
teach others what you're learning. Some folks prefer to teach by writing blog 
posts. Other people enjoy giving tutorials at a meetup. You also mentor
others in-person or remote on video chats who are also new to software 
development. &lt;/p&gt;
&lt;p&gt;By teaching others you are not being purely altruistic: explaining 
programming and answering others' questions will reinforce in your own mind 
what you have learned and where your gaps remain based on the questions. 
Experiment with code to learn more and continue to go deeper. Create a
feedback loop where you code, learn, write and find new unexplored veins 
to learn more in that area.&lt;/p&gt;
&lt;p&gt;You should be ready to start job hunting once you have a good feedback loop 
where you are digging into your favorite subject area and are teaching it to 
others in some way.&lt;/p&gt;
&lt;h3&gt;Job searching&lt;/h3&gt;
&lt;p&gt;When you've gone deep in your subject, search for jobs that have a bit 
of a full-stack flavor with an emphasis on your specialty. Reach out via 
email to developers on the team or the hiring managers. Ask them for advice
on what skills successful developers on their teams have an what unsuccessful
candidates were lacking for their positions. Use their answers as data points
for what you may still need to learn when their responses are relevant to
the area you're going deep in. &lt;/p&gt;
&lt;p&gt;When you feel you are ready, see if you can grab lunch or video chat with 
developers on those teams to learn more about their work. If that goes well, 
ask them if they'd refer you into the interview queue. Referrals will get you 
much further than applying through a human resources resume collection 
system. &lt;/p&gt;
&lt;p&gt;Look for both software development junior roles and technical support 
roles, if the technical support roles are at software-focused companies. 
For example, &lt;a href="https://www.twilio.com/company/jobs"&gt;Twilio's Support team&lt;/a&gt; 
often hires folks who have limited development experience but over time they 
can learn how to debug coding issues based on all the support tickets they 
have to answer (along with continued self-paced learning). &lt;/p&gt;
&lt;h3&gt;Interviewing and working tips&lt;/h3&gt;
&lt;p&gt;Enthusiasm is crucial for obtaining and doing well in your first few jobs.
In hindsight, a lot of the enterprise software I worked on right out of 
college was horrible, but it was all new to me so I soaked up as much
knowledge as possible while asking the tech leads and architects around
me a ton of questions. Enjoy climbing steep learning curves.&lt;/p&gt;
&lt;p&gt;Keep your cynicism and any "I'm better than this" attitude in check 
because companies have a ton of unexciting grunt work that needs to 
get done. The grunt work will teach you how to become a better software 
developer.&lt;/p&gt;
&lt;p&gt;While looking for your first position, always be working on dozens of 
potential opportunities and do not pin your hopes up on one specific 
job. The goal is to get your first development gig that will help you 
continue to learn, not to land your dream job. The dream job comes later 
when you actually have enough experience to know what your dream job looks 
like!&lt;/p&gt;
&lt;p&gt;You will eventually land your first development gig. Then you will have
to constantly keep learning and the great part is that you'll get paid for
it.&lt;/p&gt;
&lt;h3&gt;Feedback&lt;/h3&gt;
&lt;p&gt;What other questions can I answer and 
&lt;a href="/table-of-contents.html"&gt;what additional topics&lt;/a&gt; can I add to 
Full Stack Python that would be immensely helpful to new folks who are 
struggling to become self-taught developers?&lt;/p&gt;
&lt;p&gt;Let me know via 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/issues"&gt;a GitHub issue ticket on the Full Stack Python repository&lt;/a&gt;, 
on Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;How should I improve this blog post? Fork 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/170514-self-taught-developer-path.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Sun, 14 May 2017 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2017-05-14:blog/become-successful-self-taught-software-developer.html</guid></item><item><title>How to Create Your First Python 3.6 AWS Lambda Function</title><link>https://www.fullstackpython.com/blog/aws-lambda-python-3-6.html</link><description>&lt;p&gt;&lt;a href="/aws-lambda.html"&gt;Amazon Web Services (AWS) Lambda&lt;/a&gt;
provides a usage-based compute service for running Python code in response 
to developer-defined events. For example, if an inbound HTTP POST
comes in to API Gateway or a new file is uploaded to 
&lt;a href="https://aws.amazon.com/s3/"&gt;AWS S3&lt;/a&gt; then AWS Lambda can execute a function
to respond to that API call or manipulate the file on S3.&lt;/p&gt;
&lt;p&gt;AWS Lambdas are not related to the Python languages' &lt;code&gt;lambda&lt;/code&gt; expressions,
which are used to create anonymous functions. The AWS Lambda name just 
happens to collide with the the &lt;code&gt;lambda&lt;/code&gt; keyword's name.&lt;/p&gt;
&lt;p&gt;Let's learn how to quickly write and run a Lambda function to execute 
basic Python 3.6 code which uses environment variables as input.
This code, which is also &lt;a href="https://github.com/fullstackpython/blog-code-examples"&gt;available on GitHub under the blog-post-examples repository&lt;/a&gt; can be 
changed so that you can build much more complicated Python programs.&lt;/p&gt;
&lt;h2&gt;Our Tools&lt;/h2&gt;
&lt;p&gt;No local &lt;a href="/development-environments.html"&gt;development environment&lt;/a&gt; tools 
are required for this tutorial, other than a web browser. All the work will
happen on AWS via their Console. &lt;/p&gt;
&lt;p&gt;These steps can also be completed from the command line via the 
&lt;a href="https://boto3.readthedocs.io/en/latest/"&gt;boto3&lt;/a&gt; library, but we won't 
cover that in this post.&lt;/p&gt;
&lt;p&gt;If using Python 2 is still your jam rather than Python 3, take a look at
&lt;a href="/blog/aws-lambda-python-2-7.html"&gt;this other post which shows how to execute Python 2.7 code on AWS Lambda&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;First Steps with AWS Lambda&lt;/h2&gt;
&lt;p&gt;Sign up for a new &lt;a href="https://aws.amazon.com/"&gt;Amazon Web Services account&lt;/a&gt;,
which provides a generous free tier, or use your existing AWS account.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170429-aws-lambda-python-3-6/aws-amazon-com.jpg" width="100%" class="shot outl rnd" alt="AWS Lambda landing and sign in screen."&gt;&lt;/p&gt;
&lt;p&gt;After signing up a few tutorials may pop up, but skip past them and
go to the main Console. AWS has tons of services, with more being added
every month, so using the search box is the best way to get around. 
Select the search text box, enter "lambda" and select "Lambda" to get to
the Lambda starting page.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170429-aws-lambda-python-3-6/search-for-lambda.jpg" width="100%" class="shot rnd outl" alt="Search for lambda in the dashboard text box."&gt;&lt;/p&gt;
&lt;p&gt;Click the "Create a Lambda function" button. The "Select Blueprint" page
will appear.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170429-aws-lambda-python-3-6/select-blueprint.jpg" width="100%" class="shot rnd outl" alt="The Select Blueprint Lambda screen."&gt;&lt;/p&gt;
&lt;p&gt;Select "Blank Function" and the "Configure triggers" page will come up. 
It was non-obvious to me at first, but you don't actually need to configure a 
trigger to move on. A trigger is how the Lambda function typically knows 
when to execute based on an event from another AWS service such as 
&lt;a href="https://aws.amazon.com/api-gateway/"&gt;API Gateway&lt;/a&gt; or 
&lt;a href="https://aws.amazon.com/cloudwatch/"&gt;Cloudwatch&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170429-aws-lambda-python-3-6/configure-triggers.jpg" width="100%" class="shot rnd outl" alt="Configure Lambda trigger screen."&gt;&lt;/p&gt;
&lt;p&gt;We won't configure a trigger for this function because we can manually 
kick off the Lambda to test it when we are finished configuring it. Leave 
the trigger icon blank and click the "Next" button to move along. &lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170429-aws-lambda-python-3-6/blank-lambda.jpg" width="100%" class="shot outl rnd" alt="The Lambda configuration screen."&gt;&lt;/p&gt;
&lt;p&gt;Next we get to the "Configure function" screen where we can finally write
some code!&lt;/p&gt;
&lt;h2&gt;Python Code for Our Lambda Function&lt;/h2&gt;
&lt;p&gt;Enter a name for the Lambda function, such as "python_3_6_lambda_test",
as well as a description. A description is optional but it is useful
when you have a dozens or hundreds of different Lambda functions and
need to keep them straight. In the Runtime drop-down, select Python 3.6 for 
the programming language.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170429-aws-lambda-python-3-6/python-3-6-lambda.jpg" width="100%" class="shot outl rnd" alt="Enter a name, description and use Python 3.6 for the Lambda."&gt;&lt;/p&gt;
&lt;p&gt;Beneath the Runtime drop-down there is a large text box for code, 
prepopulated with a &lt;code&gt;lambda_handler&lt;/code&gt; function definition. The 
"Code entry type" drop-down can also be changed to allow uploading a ZIP
file or inputing a file from an S3 bucket. For our simple first
Lambda function we will stick to the "Edit code inline" option. Copy or type 
in the following code, replacing what is already in the text box. This
code is also available on &lt;a href="https://github.com/fullstackpython/blog-code-examples/blob/master/aws-lambda-python-3-6/"&gt;this open source GitHub repository&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;what_to_print&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;what_to_print&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;how_many_times&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;how_many_times&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c1"&gt;# make sure what_to_print and how_many_times values exist&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;what_to_print&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;how_many_times&lt;/span&gt; &lt;span class="o"&gt;&amp;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;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&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="n"&gt;how_many_times&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="c1"&gt;# formatted string literals are new in Python 3.6&lt;/span&gt;
            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;what_to_print: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;what_to_print&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&amp;quot;&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;what_to_print&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The code above contains a required &lt;code&gt;lambda_handler&lt;/code&gt; function, which is 
AWS Lambda's defined hook so it knows where to begin execution. Think of 
&lt;code&gt;lambda_handler&lt;/code&gt; as a &lt;code&gt;main&lt;/code&gt; function, like the&lt;br /&gt;
&lt;code&gt;if __name__ == "__main__":&lt;/code&gt; conditional line commonly used in Python files 
to ensure a block of code is executed when a script is run from the 
command line.&lt;/p&gt;
&lt;p&gt;The Python code expects two environment variables that are read by the
&lt;code&gt;os&lt;/code&gt; module with the &lt;code&gt;environ.get&lt;/code&gt; function. With the &lt;code&gt;what_to_print&lt;/code&gt; and
&lt;code&gt;how_many_times&lt;/code&gt; variables set by the environment variables, our code then
prints a message zero or more times, based on the amount defined in 
the &lt;code&gt;how_many_times&lt;/code&gt; variable. If a message is printed at least once then 
the function returns the &lt;code&gt;what_to_print&lt;/code&gt; string, if nothing is printed 
then &lt;code&gt;None&lt;/code&gt; is returned.&lt;/p&gt;
&lt;p&gt;Below the code input text box on this function configuration screen there 
is a section to set environment variable key-value pairs.&lt;/p&gt;
&lt;p&gt;Enter the keys named &lt;code&gt;what_to_print&lt;/code&gt; and &lt;code&gt;how_many_times&lt;/code&gt; then enter their 
values. Use a string message for &lt;code&gt;what_to_print&lt;/code&gt;'s value and an integer 
whole number above 0 for &lt;code&gt;how_many_times&lt;/code&gt;. Our Python code's error handling
is not very robust so a value other than a number in the &lt;code&gt;how_many_times&lt;/code&gt;
variable will cause the script to throw an error when it is executed due
to the forced casting of &lt;code&gt;how_many_times&lt;/code&gt; via the &lt;code&gt;int()&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170429-aws-lambda-python-3-6/environment-variables.jpg" width="100%" class="shot rnd outl" alt="Section to set environment variables for the Lambda function."&gt;&lt;/p&gt;
&lt;p&gt;The Python 3.6 code and the environment variables are now in place. We 
just need to handle a few more AWS-specific settings before we can test the 
Lambda function.&lt;/p&gt;
&lt;h2&gt;Executing our Lambda Function&lt;/h2&gt;
&lt;p&gt;Scroll past the environment variables to the 
"Lambda function handler and role" section, which contains a few more 
required function configuration items. &lt;/p&gt;
&lt;p&gt;Keep the default handler set to &lt;code&gt;lambda_function.lambda_handler&lt;/code&gt;. Select 
"Create a new Role from template(s)" from the drop-down then for the
"Role name" field enter "dynamodb_access". Under "Policy templates" 
select the "Simple Microservice permissions". &lt;/p&gt;
&lt;p&gt;The "Simple Microservice permissions" allows our Lambda to access
&lt;a href="https://aws.amazon.com/dynamodb/"&gt;AWS DynamoDB&lt;/a&gt;. We will not use DynamoDB in 
this tutorial but the service is commonly used either as permanent or 
temporary storage for Lambda functions.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170429-aws-lambda-python-3-6/lambda-handler-and-role.jpg" width="100%" class="shot rnd outl" alt="For the final configuration, keep the default handler, create a new role from a template for Simple Microservice permissions and save it with a unique name."&gt;&lt;/p&gt;
&lt;p&gt;Our code and configuration is in place so click the "Next" button
at the bottom right corner of the page.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170429-aws-lambda-python-3-6/review-lambda.jpg" width="100%" class="shot rnd outl" alt="Review Lambda configuration."&gt;&lt;/p&gt;
&lt;p&gt;The review screen shows us our configuration settings to make sure we 
selected the appropriate values for our new Lambda function. Scroll down
press "Create function".&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170429-aws-lambda-python-3-6/create-function.jpg" width="100%" class="shot rnd outl" alt="Click the create function button to continue."&gt;&lt;/p&gt;
&lt;p&gt;Success message should appear on the next page below the "Test" button.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170429-aws-lambda-python-3-6/test.jpg" width="100%" class="shot outl rnd" alt="Test button on the execution screen."&gt;&lt;/p&gt;
&lt;p&gt;Click the "Test" button to execute the Lambda. Lambda will prompt us for
some data to simulate an event that would kick off our function. Select
the "Hello World" sample event template, which contains some keys but our
Lambda will not use that in its execution. Click the "Save and test" button
at the bottom of the modal.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170429-aws-lambda-python-3-6/sample-event-template.jpg" width="100%" class="shot outl rnd" alt="Sample event template for Lambda execution."&gt;&lt;/p&gt;
&lt;p&gt;Scroll down to the "Execution result" section where we can see our output.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170429-aws-lambda-python-3-6/execution-results.jpg" width="100%" class="shot outl rnd" alt="Results from executing our new Lambda function."&gt;&lt;/p&gt;
&lt;p&gt;The log output shows us the return value of our function, which in this 
execution was the string message from &lt;code&gt;what_to_print&lt;/code&gt;. We can also see
our print function produced output five times as expected based on the
amount set in the &lt;code&gt;how_many_times&lt;/code&gt; environment variable.&lt;/p&gt;
&lt;h2&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;You just configured, coded and executed your first Python 3.6 AWS Lambda
function! The real power of Lambda comes in when you use triggers to
your Lambda function so it executes based on events that happen.
We will take a look at that in the next tutorial.&lt;/p&gt;
&lt;p&gt;View the &lt;a href="/aws-lambda.html"&gt;AWS Lambda Full Stack Python page&lt;/a&gt; for additional 
examples and tutorials that other folks have shared for Lambda with Python.&lt;/p&gt;
&lt;p&gt;Questions? Contact me via Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;. I am also on GitHub with
the username &lt;a href="https://github.com/mattmakai"&gt;mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Something wrong with this post? Fork 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/170429-python-3-6-aws-lambda.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Wed, 25 Apr 2018 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2017-04-29:blog/aws-lambda-python-3-6.html</guid></item><item><title>Getting Started with AWS Lambda &amp; Python 2.7</title><link>https://www.fullstackpython.com/blog/aws-lambda-python-2-7.html</link><description>&lt;p&gt;&lt;a href="/aws-lambda.html"&gt;Amazon Web Services (AWS) Lambda&lt;/a&gt;
is a "serverless" compute service that executes arbitrary Python code in 
response to developer-defined events, such as inbound API calls or file 
uploads to &lt;a href="https://aws.amazon.com/s3/"&gt;AWS S3&lt;/a&gt;. Note that AWS Lambda has 
nothing to do with the &lt;code&gt;lambda&lt;/code&gt; keyword in Python that is used to create 
anonymous functions, it's just the product name that happens to collide 
with an existing Python language feature name.&lt;/p&gt;
&lt;p&gt;In this tutorial we'll learn how to quickly write and run a Lambda 
function that executes some simple Python 2.7 code and handles environment
variables. The code can then be modified to build far more complicated 
Python applications.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note&lt;/em&gt;: AWS 
&lt;a href="https://aws.amazon.com/blogs/compute/announcing-end-of-support-for-python-2-7-in-aws-lambda/"&gt;ended support for Python 2.7 Lambda functions in 2021&lt;/a&gt;
and Python 2.7 no longer receives support so you should really be using
&lt;a href="https://aws.amazon.com/about-aws/whats-new/2019/11/aws-lambda-now-supports-python-3-8/"&gt;Python 3.8 or above&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Tools We Need&lt;/h2&gt;
&lt;p&gt;We do not need any local development environment tools to get through
this walkthrough other than a web browser because all the work will
happen on AWS.&lt;/p&gt;
&lt;p&gt;Grab a new free tier &lt;a href="https://aws.amazon.com/"&gt;Amazon Web Services account&lt;/a&gt; 
or use your existing AWS account.&lt;/p&gt;
&lt;h2&gt;First Steps with Lambda&lt;/h2&gt;
&lt;p&gt;Head to the 
&lt;a href="https://aws.amazon.com/lambda/"&gt;AWS Lambda landing page&lt;/a&gt; in your 
web browser. Sign into your account, or sign up for a new account which
comes with a free tier so you don't have to pay.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170428-aws-lambda-python-2-7/aws-amazon-com.jpg" width="100%" class="shot rnd outl" alt="AWS Lambda landing page."&gt;&lt;/p&gt;
&lt;p&gt;If you're not taken directly to the 
&lt;a href="https://console.aws.amazon.com/lambda/home"&gt;Lambda Console page&lt;/a&gt; after
logging in you'll see the main Console. AWS has a ridiculous number of
services (that seems to expand every week) so the best way to get around 
is to select the search text box and search for "lambda" as shown in the 
following screenshot.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170428-aws-lambda-python-2-7/search-for-lambda.jpg" width="100%" class="shot rnd outl" alt="Search for lambda in the dashboard text box."&gt;&lt;/p&gt;
&lt;p&gt;Press the "Create a Lambda function" button and you'll see the 
"Select Blueprint" page.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170428-aws-lambda-python-2-7/select-blueprint.jpg" width="100%" class="shot rnd outl" alt="The select blueprint Lambda screen, where you should select Blank Function."&gt;&lt;/p&gt;
&lt;p&gt;Choose "Blank Function". The next screen gives the option to select a
"trigger", which is how the Lambda function gets executed. A trigger is
some event that is integrated with other AWS services and can be exposed
externally via an API or device such as Alexa.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170428-aws-lambda-python-2-7/configure-triggers.jpg" width="100%" class="shot rnd outl" alt="Configure trigger screen, which we will not use for now because we will manually kick off our Lambda."&gt;&lt;/p&gt;
&lt;p&gt;However, we aren't going to set up a trigger for this function because 
we can manually test the Lambda later before connecting it to a trigger.
Leave the trigger icon blank and click the "Next" button to move along 
to the next screen.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170428-aws-lambda-python-2-7/blank-lambda.jpg" width="100%" class="shot rnd outl" alt="Blank Lambda configuration screen."&gt;&lt;/p&gt;
&lt;p&gt;Now we're on the screen where we can enter our specific configuration
and code for our new Lambda.&lt;/p&gt;
&lt;h2&gt;Writing Our Python Code&lt;/h2&gt;
&lt;p&gt;Start by entering a name for your Lambda function, such as "my_first_python_lambda" and a description. The description field is optional but it's handy
when you start using Lambda regularly to keep all your functions straight. 
In the Runtime drop-down, select Python 2.7 as the execution language.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170428-aws-lambda-python-2-7/first-python-lambda.jpg" width="100%" class="shot rnd outl" alt="Enter a name, description and select Python 2.7 on the Lambda configuration screen."&gt;&lt;/p&gt;
&lt;p&gt;Below the Runtime drop-down you'll see a large text box for writing code.
We can also choose to upload a ZIP file with our Python application which
is handy for more than simple test Lambdas. However, for our simple starter
Lambda application you can copy or type in the following code 
(&lt;a href="https://github.com/fullstackpython/blog-code-examples/blob/master/aws-lambda-python-2-7/lambda.py"&gt;or copy it from this GitHub repo&lt;/a&gt;). 
Make sure to replace what's already in the text box.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;what_to_print&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;what_to_print&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;how_many_times&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;how_many_times&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c1"&gt;# make sure what_to_print and how_many_times values exist&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;what_to_print&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;how_many_times&lt;/span&gt; &lt;span class="o"&gt;&amp;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;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&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="n"&gt;how_many_times&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;what_to_print&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;what_to_print&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The above code has the required &lt;code&gt;lambda_handler&lt;/code&gt; function definition
that provides a hook for the Lambda service to know where to begin executing
the Python code. Think of &lt;code&gt;lambda_handler&lt;/code&gt; as a &lt;code&gt;main&lt;/code&gt; function when you're
using this service.&lt;/p&gt;
&lt;p&gt;Our Python code expects and reads two environment variables and then the
code prints a message zero to many times, based on the amount defined in 
the &lt;code&gt;how_many_times&lt;/code&gt; variable. If a message is printed then the function 
returns the &lt;code&gt;what_to_print&lt;/code&gt; string, if nothing is printed then &lt;code&gt;None&lt;/code&gt; is 
returned.&lt;/p&gt;
&lt;p&gt;Just below the code input text box there are environment variable key-value
pairs that can be set. Our code will use two environment variables, named
&lt;code&gt;what_to_print&lt;/code&gt; and &lt;code&gt;how_many_times&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;Enter the keys named &lt;code&gt;what_to_print&lt;/code&gt; and &lt;code&gt;how_many_times&lt;/code&gt; then enter their 
values. Use a string message for &lt;code&gt;what_to_print&lt;/code&gt;'s value and an integer 
whole number above 0 for &lt;code&gt;how_many_times&lt;/code&gt;. Our Python code's error handling
is not very robust so a value other than a number in the &lt;code&gt;how_many_times&lt;/code&gt;
variable will cause the script to throw an error when it is executed.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170428-aws-lambda-python-2-7/environment-variables.jpg" width="100%" class="shot rnd outl" alt="Enter the exact keys of what_to_print and how_many_times along with corresponding values as environment variables."&gt;&lt;/p&gt;
&lt;p&gt;Our code and environment variables are in place and we just need to set
a few more AWS-specific settings before we can test the Lambda function.&lt;/p&gt;
&lt;h2&gt;Executing the Lambda&lt;/h2&gt;
&lt;p&gt;Scroll down below the environment variables to the 
"Lambda function handler and role" section. This section contains the last 
few required configuration items. Keep the default handler, which should 
be &lt;code&gt;lambda_function.lambda_handler&lt;/code&gt;. Select 
"Create a new Role from template(s)" from the drop-down then for the
"Role name" field enter "dynamodb_permissions". Under "Policy templates" 
select the "Simple Microservice permissions". &lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170428-aws-lambda-python-2-7/lambda-handler-and-role.jpg" width="100%" class="shot rnd outl" alt="For the final configuration, keep the default handler, create a new role from a template for Simple Microservice permissions and save it with a unique name."&gt;&lt;/p&gt;
&lt;p&gt;The "Simple Microservice permissions" gives our Lambda access to 
&lt;a href="https://aws.amazon.com/dynamodb/"&gt;AWS DynamoDB&lt;/a&gt;. We won't use DynamoDB in 
this tutorial but it's super useful as either permanent or temporary 
storage when working with Lambda.&lt;/p&gt;
&lt;p&gt;Now that our code and configuration is in place, click the "Next" button
at the bottom right corner of the page.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170428-aws-lambda-python-2-7/review-lambda.jpg" width="100%" class="shot rnd outl" alt="We can review the values set during our configuration."&gt;&lt;/p&gt;
&lt;p&gt;The review screen will show us our configuration settings. Scroll down
to the bottom and click the "Create function" button to continue.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170428-aws-lambda-python-2-7/create-function.jpg" width="100%" class="shot rnd outl" alt="Click the create function button to continue."&gt;&lt;/p&gt;
&lt;p&gt;We should see a success message on the next page just below the 
"Save and test" button.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170428-aws-lambda-python-2-7/save-and-test.jpg" width="100%" class="shot rnd outl" alt="Save and test button."&gt;&lt;/p&gt;
&lt;p&gt;Press the "Test" button to execute the Lambda. Lambda prompts us for
some data to simulate an event that would trigger our function. Select
the "Hello World" sample event template, which contains some example keys. 
Our Lambda will not those keys in its execution so it does not matter what
they are. Click the "Save and test" button at the bottom of the modal.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170428-aws-lambda-python-2-7/sample-event-template.jpg" width="100%" class="shot rnd outl" alt="Sample event template for our Lambda execution."&gt;&lt;/p&gt;
&lt;p&gt;Scroll down to the "Execution result" section where we can see our output.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170428-aws-lambda-python-2-7/execution-results.jpg" width="100%" class="" alt="Execution results from running our Lambda function."&gt;&lt;/p&gt;
&lt;p&gt;We get the log output that shows us the return value of our function. In
this case it is the string message from &lt;code&gt;what_to_print&lt;/code&gt;. We can also see
down below that our print function produced output five times. &lt;/p&gt;
&lt;h2&gt;What's Next?&lt;/h2&gt;
&lt;p&gt;Awesome, you just configured, wrote and executed your first Python 2.7
code on AWS Lambda! The real power of Lambda comes in when you connect a
trigger to it so your code executes based on events. We'll take a look
at that in the next tutorial.&lt;/p&gt;
&lt;p&gt;What else can you do with Python and Lambda? Take a look at the 
&lt;a href="/aws-lambda.html"&gt;AWS Lambda&lt;/a&gt; page for more examples and tutorials. &lt;/p&gt;
&lt;p&gt;Questions? Contact me via Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;. I am also on GitHub with
the username &lt;a href="https://github.com/mattmakai"&gt;mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Something wrong with this post? Fork 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/170428-python-2-7-aws-lambda.markdown"&gt;this page's source on GitHub&lt;/a&gt;.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Tue, 30 Mar 2021 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2017-04-28:blog/aws-lambda-python-2-7.html</guid></item><item><title>How to Choose the Right DevOps Tools for You and Your Team</title><link>https://www.fullstackpython.com/blog/choose-right-devops-tools.html</link><description>&lt;p&gt;This blog post contains a loose transcript along with the slides and 
additional resources from my technical talk that will be given at &lt;a href="https://www.meetup.com/DC-continuous-delivery/"&gt;DC Continuous Delivery&lt;/a&gt;
within the next couple of months.&lt;/p&gt;
&lt;p&gt;Additional resources to learn more about &lt;a href="/deployments.html"&gt;deployments&lt;/a&gt;,
&lt;a href="/configuration-management.html"&gt;configuration management&lt;/a&gt; and 
&lt;a href="/devops.html"&gt;DevOps&lt;/a&gt; are listed at the end of the post.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/title-slide.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Title slide for technical talk."&gt;&lt;/p&gt;
&lt;p&gt;Hey folks, my name is Matt Makai. I'm a 
&lt;a href="https://www.twilio.com/blog/2014/02/introducing-developer-evangelist-matt-makai.html"&gt;Developer Evangelist with Twilio&lt;/a&gt;
and the creator of &lt;a href="https://www.fullstackpython.com/"&gt;Full Stack Python&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/python-swift-love.jpg" width="100%" class="technical-diagram img-rounded" alt="Python and Swift logos with the heart eyes emoji."&gt;&lt;/p&gt;
&lt;p&gt;Over the past couple of years I've been coding mostly in Python and Swift. 
I bring that up because the way we build, deploy and operate applications in 
either ecosystem is different. It would not make sense to forcefully recommend 
a single way to work in your own ecosystem if it is different than the ones I 
work in.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/java-only.jpg" width="100%" class="technical-diagram img-rounded" alt="Java programming language logo."&gt;&lt;/p&gt;
&lt;p&gt;I used to do a ton of Java development. That's how I started my professional
career before I moved mostly into Python and Swift.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/dark-ages.jpg" width="100%" class="technical-diagram img-rounded" alt="2004, the dark ages of software development?"&gt;&lt;/p&gt;
&lt;p&gt;Back in my own software development dark ages of 2004, I learned about a
concept that got me interested in DevOps before it was called DevOps: 
&lt;a href="/source-control.html"&gt;source control&lt;/a&gt;, also known as version control. We don't 
talk much about source control being a DevOps tool, but it really
is the foundational layer for everything you want to automate with code.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/different-versions.jpg" width="100%" class="technical-diagram img-rounded" alt="Diff two commits on GitHub."&gt;&lt;/p&gt;
&lt;p&gt;Nowadays we have amazing open source distributed version control systems
and beautiful web application front ends to visualize our code changes over
time. Yet there is still a small percentage of developers who don't use source
control.&lt;/p&gt;
&lt;p&gt;It might seem crazy but I know developers at Fortune 500 companies that still
do not use source control! How do you automate building, deploying, testing
and operating your application if you don't even have your files versioned?&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/pat-on-back.jpg" width="100%" class="technical-diagram img-rounded" alt="Give yourself a pat on the back."&gt;&lt;/p&gt;
&lt;p&gt;Let's start off this discussion of DevOps tools with a pat on your own back
if you already use source control. Nice work! We've come a long way as an
industry in the last couple of decades when source control was an exotic 
concept for most developers.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/git-logo.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Git logo."&gt;&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/question-1.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Do you know a developer who strongly recommends a tool after 4+ years?"&gt;&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/question-2.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="What is the difference between a concept and an implementation?"&gt;&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/devops-1-layer.png" width="100%" class="technical-diagram img-rounded" alt="Source control (version control) as bottom layer in DevOps."&gt;&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/question-3.png" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Question 3: On a 0 (lowest) -&gt; 10 scale, how amenable is your organization to improving the technical environment?"&amp;gt;&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/question-4.png" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Question 4: How many people on your team get stoked about making incremental fixes to your technical environment?"&gt;&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/devops-2-layers.png" width="100%" class="technical-diagram img-rounded" alt="CI, automated tests and app dependencies as layer 2 in DevOps."&gt;&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/continuous-integration-implementations.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Open source and hosted versions of CI, such as Jenkins, GoCD, and StriderCI, along with CircleCI, Travis CI and CodeBuild."&gt;&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/test-automation.png" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Test automation concepts and their implementations in Python ecosystem as examples."&gt;&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/bash.png" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="$bash."&gt;&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/python-fabric-logo.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Python Fabric library logo."&gt;&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/ansible-logo.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Ansible logo."&gt;&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/devops-3-layers.png" width="100%" class="technical-diagram img-rounded" alt="Configuration management and automated deployments in layer 3 of DevOps."&gt;&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/configuration-management-tools.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Configuration management implementations such as Ansible, Chef, Puppet and SaltStack."&gt;&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/ansible-commands.png" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Example for Ansible YAML command to install packages through apt."&gt;&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/question-5.png" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="How many times per day does your team deploy to test? How about production?"&gt;&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/question-6.png" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="How many times per day do you want to deploy to test? To production?"&gt;&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/question-7.png" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="What are the top 5 specific impediments to completing automating your deployments?"&gt;&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/question-8.png" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Who on your team gets excited about continuous delivery?"&gt;&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/question-9.png" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Who on your team is responsible for improving automated deployments and continuous delivery?"&gt;&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/django-logo.jpg" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Django logo."&gt;&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/devops-4-layers.png" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Monitoring, logging and measuring in layer 4 of DevOps."&gt;&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/question-10.png" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="What metrics do you collect that feed into every sprint?"&gt;&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/question-11.png" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="How many days would it take to put a new code library into production?"&gt;&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/deploys-2016.jpg" width="100%" class="technical-diagram img-rounded" alt="6,643 deploys per year for Twilio in 2015."&gt;&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/question-12.png" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="How much money, if any, can you spend to jump start monitoring your environment?"&gt;&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/devops-4-layers.png" width="100%" class="technical-diagram img-rounded" alt="Repeat monitoring, loggin and measuring in 4 layer DevOps slide."&gt;&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170227-choose-devops-tools/contact-info.png" width="100%" class="technical-diagram img-rounded" style="border: 1px solid #aaa" alt="Contact info end slide."&gt;&lt;/p&gt;
&lt;p&gt;My name is Matt Makai and I'm a Developer Evangelist with Twilio, a Python
and Swift developer, as well as the author of 
&lt;a href="https://www.fullstackpython.com/"&gt;Full Stack Python&lt;/a&gt;. You can get in
touch with me via these channels. Thank you!&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Wed, 01 Mar 2017 00:00:00 -0500</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2017-02-27:blog/choose-right-devops-tools.html</guid></item><item><title>Creating SSH Keys on macOS Sierra</title><link>https://www.fullstackpython.com/blog/ssh-keys-macos-sierra.html</link><description>&lt;p&gt;&lt;a href="/deployment.html"&gt;Deploying&lt;/a&gt; Python applications typically requires
SSH keys. An SSH key has both a public and a private key file. You can 
use the private key to authenticate when syncing remote &lt;a href="/git.html"&gt;Git&lt;/a&gt; 
repositories, connect to remote &lt;a href="/servers.html"&gt;servers&lt;/a&gt; and automate 
your application's deployments via 
&lt;a href="/configuration-management.html"&gt;configuration management&lt;/a&gt; tools like 
Ansible. Let's learn how to generate SSH key pairs on 
&lt;a href="http://www.apple.com/macos/sierra/"&gt;macOS Sierra&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Generating New Keys&lt;/h2&gt;
&lt;p&gt;Bring up a new terminal window on macOS by going into Applications/Utilities
and opening "Terminal".&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170220-ssh-keys-macos/new-terminal.jpg" width="100%" class="technical-diagram img-rounded" alt="New macOS terminal window."&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;ssh-keygen&lt;/code&gt; command provides an interactive command line interface for
generating both the public and private keys. Invoke &lt;code&gt;ssh-keygen&lt;/code&gt; with the
following &lt;code&gt;-t&lt;/code&gt; and &lt;code&gt;-b&lt;/code&gt; arguments to ensure we get a 4096 bit RSA key. Note 
that you &lt;em&gt;must&lt;/em&gt; use a key with 2048 or more bits in macOS Sierra or the
system will not allow you to connect to servers with it.&lt;/p&gt;
&lt;p&gt;Optionally, you can also specify your email address with &lt;code&gt;-C&lt;/code&gt; (otherwise 
one will be generated off your current macOS account):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;ssh-keygen -t rsa -b &lt;span class="m"&gt;4096&lt;/span&gt; -C my.email.address@company.com
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The first prompt you will see asks where to save the key. However, there are
actually two files that will be generated: the public key and the private 
key. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Generating public/private rsa key pair.
Enter file &lt;span class="k"&gt;in&lt;/span&gt; which to save the key &lt;span class="o"&gt;(&lt;/span&gt;/Users/matt/.ssh/id_rsa&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This prompt refers to the private key and whatever you enter will also
generate a second file for the public key that has the same name and &lt;code&gt;.pub&lt;/code&gt; 
appended.&lt;/p&gt;
&lt;p&gt;If you already have a key then specify a new filename. I use many
SSH keys so I oftne name them "test-deploy", "prod-deploy", "ci-server"
along with a unique project name. Naming is one of those hard computer 
science problems, so take some time to come up with a system that works for
you!&lt;/p&gt;
&lt;p&gt;Next you will see a prompt for an optional passphrase:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Enter passphrase &lt;span class="o"&gt;(&lt;/span&gt;empty &lt;span class="k"&gt;for&lt;/span&gt; no passphrase&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Whether or not you want a passphrase depends on how you will use the key.
The system will ask you for the passphrase whenever you use the SSH key,
although 
&lt;a href="http://apple.stackexchange.com/questions/254468/macos-sierra-doesn-t-seem-to-remember-ssh-keys-between-reboots"&gt;macOS can store the passphrase in your system Keychain&lt;/a&gt; 
after the first time you enter it. However, if you are automating deployments 
with a &lt;a href="/continuous-integration.html"&gt;continuous integration&lt;/a&gt; server like
&lt;a href="/jenkins.html"&gt;Jenkins&lt;/a&gt; then you will not want a passphrase.&lt;/p&gt;
&lt;p&gt;Note that it is impossible to recover a passphrase if it is lost. Keep 
that passphrase safe and secure because otherwise a completely new key would 
have to be generated.&lt;/p&gt;
&lt;p&gt;Enter the passphrase (or just press enter to not have a passphrase) twice.
You'll see some output like the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Enter passphrase &lt;span class="o"&gt;(&lt;/span&gt;empty &lt;span class="k"&gt;for&lt;/span&gt; no passphrase&lt;span class="o"&gt;)&lt;/span&gt;: 
Enter same passphrase again: 
Your identification has been saved &lt;span class="k"&gt;in&lt;/span&gt; /Users/matt/.ssh/deploy_prod.
Your public key has been saved &lt;span class="k"&gt;in&lt;/span&gt; /Users/matt/.ssh/deploy_prod.pub.
The key fingerprint is:
SHA256:UnRGH/nzYzxUFS9jjd0wOl1ScFGKgW3pU60sSxGnyHo matthew.makai@gmail.com
The key&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;s randomart image is:
+---&lt;span class="o"&gt;[&lt;/span&gt;RSA &lt;span class="m"&gt;4096&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;----+
&lt;span class="p"&gt;|&lt;/span&gt;        ..+o++**@&lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="p"&gt;|&lt;/span&gt;       . +.o*O.@&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="p"&gt;|&lt;/span&gt;        . oo*&lt;span class="o"&gt;=&lt;/span&gt;B.*&lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="p"&gt;|&lt;/span&gt;       . .  &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;o&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;+ &lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="p"&gt;|&lt;/span&gt;      . S E. +oo &lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="p"&gt;|&lt;/span&gt;       . .  .  &lt;span class="o"&gt;=&lt;/span&gt;.&lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="p"&gt;|&lt;/span&gt;              . o&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="p"&gt;|&lt;/span&gt;                 &lt;span class="p"&gt;|&lt;/span&gt;
+----&lt;span class="o"&gt;[&lt;/span&gt;SHA256&lt;span class="o"&gt;]&lt;/span&gt;-----+
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Your SSH key is ready to use!&lt;/p&gt;
&lt;h2&gt;What now?&lt;/h2&gt;
&lt;p&gt;Now that you have your public and private keys, I recommend building and
deploying some &lt;a href="/web-development.html"&gt;Python web apps&lt;/a&gt; such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/blog/build-first-slack-bot-python.html"&gt;Building your first Slack bot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/blog/send-mms-picture-messages-python.html"&gt;Sending picture or video messages via a REST API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/blog/dial-outbound-phone-calls-python-bottle.html"&gt;Dialing outbound phone calls&lt;/a&gt;
  with the &lt;a href="/bottle.html"&gt;Bottle&lt;/a&gt; web framework&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Additional &lt;code&gt;ssh-keygen&lt;/code&gt; command resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://testequals.com/2016/09/09/macos-sierra-10-12-ssh-keys/"&gt;SSH keys on macOS Sierra&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://help.github.com/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent/"&gt;Generating a new SSH key and adding it to the ssh-agent&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Questions? Contact me via Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;. I'm also on GitHub with
the username &lt;a href="https://github.com/mattmakai"&gt;mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See something wrong in this post? Fork 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/170220-create-ssh-keys-macos.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Fri, 28 Apr 2017 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2017-02-20:blog/ssh-keys-macos-sierra.html</guid></item><item><title>Creating SSH Keys on Ubuntu Linux 16.04 LTS</title><link>https://www.fullstackpython.com/blog/ssh-keys-ubuntu-linux.html</link><description>&lt;p&gt;SSH keys are a necessity for Python development when you are working with
&lt;a href="/git.html"&gt;Git&lt;/a&gt;, connecting to remote servers and automating your
&lt;a href="/deployment.html"&gt;deployments&lt;/a&gt;. Let's walk through how to generate SSH
key pairs, which contain both a public and a private key within a single 
pair, on Ubuntu Linux.&lt;/p&gt;
&lt;h2&gt;Generating the Public and Private Keys&lt;/h2&gt;
&lt;p&gt;Open up a new terminal window in Ubuntu like we see in the following 
screenshot.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/170214-ssh-keys-ubuntu/new-ubuntu-terminal.jpg" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;ssh-keygen&lt;/code&gt; command provides an interactive command line interface for
generating both the public and private keys. Invoke &lt;code&gt;ssh-keygen&lt;/code&gt; with the
following &lt;code&gt;-t&lt;/code&gt; and &lt;code&gt;-b&lt;/code&gt; arguments to ensure we get a 4096 bit RSA key.
Optionally, you can also specify your email address with &lt;code&gt;-C&lt;/code&gt; (otherwise 
one will be generated off your current Linux account):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;ssh-keygen -o -t rsa -b &lt;span class="m"&gt;4096&lt;/span&gt; -C my.email.address@company.com
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;(Note: the &lt;code&gt;-o&lt;/code&gt; option was introduced in 2014; if this command fails for you, simply remove the &lt;code&gt;-o&lt;/code&gt; option)&lt;/p&gt;
&lt;p&gt;The first prompt you will see asks where to save the key. However, there are
actually two files that will be generated: the public key and the private 
key. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Generating public/private rsa key pair.
Enter file &lt;span class="k"&gt;in&lt;/span&gt; which to save the key &lt;span class="o"&gt;(&lt;/span&gt;/home/matt/.ssh/id_rsa&lt;span class="o"&gt;)&lt;/span&gt;: 
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This prompt refers to the private key and whatever you enter will also
generate a second file for the public key that has the same name and &lt;code&gt;.pub&lt;/code&gt; 
appended.&lt;/p&gt;
&lt;p&gt;If you already have a key, you should specify a new filename. I use many
SSH keys so I typically name them "test-deploy", "prod-deploy", "ci-server"
along with a unique project name. Naming is one of those hard computer 
science problems, so take some time to come up with a system that works for
you and the development team you work with!&lt;/p&gt;
&lt;p&gt;Next you will see a prompt for an optional passphrase:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Enter passphrase &lt;span class="o"&gt;(&lt;/span&gt;empty &lt;span class="k"&gt;for&lt;/span&gt; no passphrase&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Whether or not you want a passphrase depends on how you will use the key.
The system will ask you for the passphrase whenever you use the SSH key
so it is more secure.
However, if you are automating deployments with a 
&lt;a href="/continuous-integration.html"&gt;continuous integration&lt;/a&gt; server like
&lt;a href="/jenkins.html"&gt;Jenkins&lt;/a&gt; then you will not want a passphrase.&lt;/p&gt;
&lt;p&gt;Be aware that it is impossible to recover a passphrase if it is lost. Keep 
that passphrase safe and secure because otherwise a completely new key would 
have to be generated.&lt;/p&gt;
&lt;p&gt;Enter the passphrase (or just press enter to not have a passphrase) twice.
You'll see some output like the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Your identification has been saved &lt;span class="k"&gt;in&lt;/span&gt; /home/matt/.ssh/prod_deploy.
Your public key has been saved &lt;span class="k"&gt;in&lt;/span&gt; /home/matt/.ssh/prod_deploy.pub.
The key fingerprint is:
SHA256:xoCWgk40XfM5mruZQNCVoBKXZ4d0gn09ivVENacb7xw matt@ubuntu
The key&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;s randomart image is:
+---&lt;span class="o"&gt;[&lt;/span&gt;RSA &lt;span class="m"&gt;2048&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;----+
&lt;span class="p"&gt;|&lt;/span&gt;.oo*&lt;span class="o"&gt;==&lt;/span&gt;oo..o .    &lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="p"&gt;|&lt;/span&gt;.+*.*** &lt;span class="o"&gt;=&lt;/span&gt;  +     &lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="p"&gt;|&lt;/span&gt;o+.++&lt;span class="o"&gt;=&lt;/span&gt;.B .o      &lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="p"&gt;|&lt;/span&gt;+ .o. +oo  +     &lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="p"&gt;|&lt;/span&gt; . . o  S . E    &lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="p"&gt;|&lt;/span&gt;  .   ..   o .   &lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="p"&gt;|&lt;/span&gt;   . .      o    &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="p"&gt;|&lt;/span&gt;     +           &lt;span class="p"&gt;|&lt;/span&gt;
+----&lt;span class="o"&gt;[&lt;/span&gt;SHA256&lt;span class="o"&gt;]&lt;/span&gt;-----+
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Your SSH key is now generated and ready to use!&lt;/p&gt;
&lt;h2&gt;What now?&lt;/h2&gt;
&lt;p&gt;Now that you have your public and private keys, I recommend setting
up a &lt;a href="/development-environments.html"&gt;Python development environment&lt;/a&gt; with 
one of the following tutorials so you can start coding:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/blog/python-3-django-gunicorn-ubuntu-1604-xenial-xerus.html"&gt;Setting up Python 3, Django and Gunicorn on Ubuntu 16.04 LTS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/blog/install-redis-use-python-3-ubuntu-1604.html"&gt;How to Use Redis with Python 3 and redis-py on Ubuntu 16.04&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/blog/postgresql-python-3-psycopg2-ubuntu-1604.html"&gt;Setting up PostgreSQL with Python 3 and psycopg on Ubuntu 16.04&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Additional &lt;code&gt;ssh-keygen&lt;/code&gt; command resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://manpages.ubuntu.com/manpages/xenial/man1/ssh-keygen.1.html"&gt;ubuntu manuals ssh-keygen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://help.github.com/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent/"&gt;Generating a new SSH key and adding it to the ssh-agent&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Questions? Contact me via Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;. I'm also on GitHub with
the username &lt;a href="https://github.com/mattmakai"&gt;mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See something wrong in this post? Fork 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/170214-create-ssh-keys-ubuntu.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Fri, 28 Apr 2017 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2017-02-14:blog/ssh-keys-ubuntu-linux.html</guid></item><item><title>How to Make Phone Calls in Python</title><link>https://www.fullstackpython.com/blog/make-phone-calls-python.html</link><description>&lt;p&gt;Good old-fashioned phone calls remain one of the best forms of communication
despite the slew of new smartphone apps that have popped up over the past
several years. With just a few lines of Python code plus a 
&lt;a href="/application-programming-interfaces.html"&gt;web application programming interface&lt;/a&gt; 
we can make and receive phone calls from any application. &lt;/p&gt;
&lt;p&gt;Our example calls will say a snippet of text and put all incoming callers 
into a recorded conference call. You can modify the instructions using 
&lt;a href="https://www.twilio.com/docs/api/twiml"&gt;Twilio's TwiML verbs&lt;/a&gt; when you 
perform different actions in your own application's phone calls.&lt;/p&gt;
&lt;h2&gt;Our Tools&lt;/h2&gt;
&lt;p&gt;You should have either &lt;a href="/python-2-or-3.html"&gt;Python 2 or 3&lt;/a&gt; installed to
build this application. Throughout the post we will also use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pip.pypa.io/en/stable/"&gt;pip&lt;/a&gt; and 
  &lt;a href="https://virtualenv.pypa.io/en/latest/"&gt;virtualenv&lt;/a&gt; to handle
  &lt;a href="/application-dependencies.html"&gt;application dependencies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A free &lt;a href="https://www.twilio.com/try-twilio"&gt;Twilio account&lt;/a&gt; to use their 
  &lt;a href="https://www.twilio.com/docs/api/rest/making-calls"&gt;phone calling web API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Twilio's 
  &lt;a href="https://www.twilio.com/docs/libraries/python"&gt;Python helper library&lt;/a&gt;,
  version 5.7.0, which is 
  &lt;a href="https://pypi.org/project/twilio"&gt;available on PyPI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can snag all the open source code for this tutorial in the 
&lt;a href="https://github.com/mattmakai/python-twilio-example-apps/tree/master/no-framework/phone-calls"&gt;python-twilio-example-apps&lt;/a&gt;
GitHub repository under the 
&lt;a href="https://github.com/mattmakai/python-twilio-example-apps/tree/master/no-framework/phone-calls"&gt;no-framework/phone-calls&lt;/a&gt; directory.
Use and copy the code for your own applications. Everything in that 
repository and in this blog post are open source under the MIT license.&lt;/p&gt;
&lt;h2&gt;Install App Dependencies&lt;/h2&gt;
&lt;p&gt;Our application will use the &lt;a href="/twilio.html"&gt;Twilio&lt;/a&gt; 
&lt;a href="https://www.twilio.com/docs/libraries/python"&gt;Python helper library&lt;/a&gt;
to create an HTTP POST request to Twilio's API. The Twilio helper library is 
installable from &lt;a href="https://pypi.python.org/pypi"&gt;PyPI&lt;/a&gt; into a virtual 
environment. Open your terminal and use the &lt;code&gt;virtualenv&lt;/code&gt; command to create 
a new virtualenv:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;virtualenv phoneapp
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Invoke the &lt;code&gt;activate&lt;/code&gt; script within the virtualenv &lt;code&gt;bin/&lt;/code&gt; directory to make 
this virtualenv the active Python executable. Note that you will need to 
perform this step in every terminal window that you want the virtualenv to 
be active.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;source phoneapp/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The command prompt will change after activating the virtualenv
to something like &lt;code&gt;(phoneapp) $&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;Next use the &lt;code&gt;pip&lt;/code&gt; command to install the 
&lt;a href="https://www.twilio.com/docs/libraries/python"&gt;Twilio Python&lt;/a&gt; package
into the virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install twilio==5.7.0
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We will have the required dependency ready for project as soon as the 
installation script finishes. Now we can write and execute Python code to 
dial phone numbers.&lt;/p&gt;
&lt;h2&gt;Our Python Script&lt;/h2&gt;
&lt;p&gt;Create a new file named &lt;code&gt;phone_calls.py&lt;/code&gt; and copy or type in the following
lines of code.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;twilio.rest&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TwilioRestClient&lt;/span&gt;


&lt;span class="c1"&gt;# Twilio phone number goes here. Grab one at https://twilio.com/try-twilio&lt;/span&gt;
&lt;span class="c1"&gt;# and use the E.164 format, for example: &amp;quot;+12025551234&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;TWILIO_PHONE_NUMBER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# list of one or more phone numbers to dial, in &amp;quot;+19732644210&amp;quot; format&lt;/span&gt;
&lt;span class="n"&gt;DIAL_NUMBERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,]&lt;/span&gt;

&lt;span class="c1"&gt;# URL location of TwiML instructions for how to handle the phone call&lt;/span&gt;
&lt;span class="n"&gt;TWIML_INSTRUCTIONS_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; \
  &lt;span class="s2"&gt;&amp;quot;http://static.fullstackpython.com/phone-calls-python.xml&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# replace the placeholder values with your Account SID and Auth Token&lt;/span&gt;
&lt;span class="c1"&gt;# found on the Twilio Console: https://www.twilio.com/console&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TwilioRestClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ACxxxxxxxxxx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;yyyyyyyyyy&amp;quot;&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;dial_numbers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbers_list&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Dials one or more phone numbers from a Twilio phone number.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;numbers_list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Dialing &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# set the method to &amp;quot;GET&amp;quot; from default POST because Amazon S3 only&lt;/span&gt;
        &lt;span class="c1"&gt;# serves GET requests on files. Typically POST would be used for apps&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calls&lt;/span&gt;&lt;span class="o"&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;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;from_&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;TWILIO_PHONE_NUMBER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;TWIML_INSTRUCTIONS_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GET&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;dial_numbers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DIAL_NUMBERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;There are a few lines that you need to modify in this application before it
will run. First, insert one or more phone numbers you wish to dial into the 
DIAL_NUMBERS list. Each one should be a string, separated by a comma. For
example, &lt;code&gt;DIAL_NUMBERS = ["+12025551234", "+14155559876", "+19735551234"]&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next, &lt;code&gt;TWILIO_PHONE_NUMBER&lt;/code&gt; and the Account SID and Authentication Token,
found on the &lt;code&gt;client = TwilioRestClient("ACxxxxxxxxxx", "yyyyyyyyyy")&lt;/code&gt; 
line, need to be set. We can get these values from the 
&lt;a href="https://www.twilio.com/console"&gt;Twilio Console&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In your web browser go to the
&lt;a href="https://www.twilio.com/try-twilio"&gt;Twilio website and sign up for a free account&lt;/a&gt; 
or sign into your existing Twilio account.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/161123-python-phone-calls/try-twilio.png" width="100%" class="technical-diagram img-rounded" alt="Twilio sign up screen."&gt;&lt;/p&gt;
&lt;p&gt;Copy the Account SID and Auth Token from the Twilio Console and paste them 
into your application's code: &lt;/p&gt;
&lt;p&gt;&lt;img src="/img/161123-python-phone-calls/twilio-console-tokens.png" width="100%" class="technical-diagram img-rounded" alt="Obtain the Account SID and Auth Token from the Twilio Console."&gt;&lt;/p&gt;
&lt;p&gt;The Twilio trial account allows you to dial and receive phone calls to 
your own validated phone number. To handle calls from any phone 
number then you need to upgrade your account (hit the upgrade button on the
top navigation bar). &lt;/p&gt;
&lt;p&gt;Once you are signed into your Twilio account, go to the 
&lt;a href="https://www.twilio.com/console/phone-numbers"&gt;manage phone numbers screen&lt;/a&gt;.
On this screen you can 
&lt;a href="https://www.twilio.com/console/phone-numbers/search"&gt;buy one or more phone numbers&lt;/a&gt;
or click on an existing phone number in your account to configure it.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/161123-python-phone-calls/manage-numbers.jpg" width="100%" class="technical-diagram img-rounded" alt="Manage phone numbers screen."&gt;&lt;/p&gt;
&lt;p&gt;After clicking on a number you will reach the phone number configuration
screen. Paste in the URL with TwiML instructions and change the dropdown from
"HTTP POST" to "HTTP GET". In this post we'll use 
&lt;code&gt;http://static.fullstackpython.com/phone-calls-python.xml&lt;/code&gt;, but that URL 
can be more than just a static XML file. &lt;/p&gt;
&lt;p&gt;&lt;img src="/img/161123-python-phone-calls/twiml-url-number-screen.jpg" width="100%" class="technical-diagram img-rounded" alt="Twilio phone number configuration screen."&gt;&lt;/p&gt;
&lt;p&gt;The power of Twilio really comes in when that URL is handled by your web 
application so it can dynamically respond with TwiML instructions based on 
the incoming caller number or other properties stored in your database.&lt;/p&gt;
&lt;p&gt;Under the Voice webhook, paste in 
&lt;code&gt;http://static.fullstackpython.com/phone-calls-python.xml&lt;/code&gt; and change the
drop-down to the right from "HTTP POST" to "HTTP GET". Click the "Save" 
button at the bottom of the screen.&lt;/p&gt;
&lt;p&gt;Now try calling your phone number. You should hear the snippet of text
read by the Alice voice and then you will be placed into a conference call.
If no one else calls the number then hold music should be playing.&lt;/p&gt;
&lt;h2&gt;Making Phone Calls&lt;/h2&gt;
&lt;p&gt;We just handled inbound phone calls to our phone number. Now it's time to 
dial outbound phone calls. Make sure your &lt;code&gt;phone_calls.py&lt;/code&gt; file is saved 
and that your virtualenv is still activated and then execute the script:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python phone_calls.py
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;In a moment all the phone numbers you write in the &lt;code&gt;DIAL_NUMBERS&lt;/code&gt; list
should light up with calls. Anyone that answers will hear our message read
by the "Alice" voice and then they'll be placed together into a recorded 
conference call, just like when someone dials into the number. &lt;/p&gt;
&lt;p&gt;Here is my inbound phone call:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/161123-python-phone-calls/inbound-call.png" width="100%" class="technical-diagram img-rounded" alt="Receiving an incoming phone call on the iPhone."&gt;&lt;/p&gt;
&lt;p&gt;Not bad for just a few lines of Python code!&lt;/p&gt;
&lt;h2&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;Now that we know how to make and receive phone calls from a Twilio number 
that follows programmatic instructions we can do a whole lot more in our
applications. Next you can use one of these tutorials to do more with 
your phone number:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.twilio.com/blog/2016/05/add-phone-calling-slack-python.html"&gt;Build a phone-calling Slack bot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.twilio.com/docs/tutorials/walkthrough/masked-numbers/python/flask"&gt;Mask phone numbers for anonymous communication&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.twilio.com/docs/tutorials/walkthrough/call-tracking/python/django"&gt;Add call tracking to see metrics for phone calls&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Questions? Contact me via Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;. I'm also on GitHub as
&lt;a href="https://github.com/mattmakai"&gt;mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See something wrong in this post? Fork 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/161123-make-phone-calls.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Sat, 22 Jul 2017 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2016-11-23:blog/make-phone-calls-python.html</guid></item><item><title>Dialing Outbound Phone Calls with a Bottle Web App</title><link>https://www.fullstackpython.com/blog/dial-outbound-phone-calls-python-bottle.html</link><description>&lt;p&gt;Python web apps built with the &lt;a href="/bottle.html"&gt;Bottle web framework&lt;/a&gt; can 
&lt;a href="/blog/send-sms-text-messages-python.html"&gt;send&lt;/a&gt; and 
&lt;a href="/blog/reply-sms-text-messages-python-bottle.html"&gt;receive SMS text messages&lt;/a&gt;.
In this tutorial we will go beyond texting and learn how to dial outbound 
phone calls. The calls will read a snippet of text then play an MP3 file,
but they can then be easily modified to create conference lines and many
other voice features in your Python web apps.&lt;/p&gt;
&lt;h2&gt;Tools We Need&lt;/h2&gt;
&lt;p&gt;You should have either &lt;a href="/python-2-or-3.html"&gt;Python 2 or 3&lt;/a&gt; installed to
create your Bottle app, although Python 3 is recommended for new 
applications. We also need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pip.pypa.io/en/stable/"&gt;pip&lt;/a&gt; and 
  &lt;a href="https://virtualenv.pypa.io/en/latest/"&gt;virtualenv&lt;/a&gt; to handle
  &lt;a href="/application-dependencies.html"&gt;application dependencies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ngrok.com/"&gt;Ngrok&lt;/a&gt; for localhost tunneling to our Bottle
  application while it's running on our local development environment&lt;/li&gt;
&lt;li&gt;&lt;a href="/bottle.html"&gt;Bottle&lt;/a&gt; web framework&lt;/li&gt;
&lt;li&gt;Free &lt;a href="https://www.twilio.com/try-twilio"&gt;Twilio account&lt;/a&gt; to use their 
  &lt;a href="https://www.twilio.com/docs/api/rest/making-calls"&gt;phone calling web API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Twilio's 
  &lt;a href="https://www.twilio.com/docs/libraries/python"&gt;Python helper library&lt;/a&gt;,
  which is &lt;a href="https://github.com/twilio/twilio-python"&gt;open source on GitHub&lt;/a&gt; 
  and &lt;a href="https://pypi.org/project/twilio"&gt;available for download from PyPI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Take a look at 
&lt;a href="/blog/python-3-bottle-gunicorn-ubuntu-1604-xenial-xerus.html"&gt;this guide on setting up Python 3, Bottle and Gunicorn on Ubuntu 16.04 LTS&lt;/a&gt;
if you need help getting your 
&lt;a href="/development-environments.html"&gt;development environment&lt;/a&gt; 
configured before continuing on through the remainder of this tutorial.&lt;/p&gt;
&lt;p&gt;You can snag all the open source code for this tutorial in the 
&lt;a href="https://github.com/mattmakai/python-bottle-phone"&gt;python-bottle-phone&lt;/a&gt;
GitHub repository under the 
&lt;a href="https://github.com/mattmakai/python-bottle-phone/tree/master/outbound-calls"&gt;outbound directory&lt;/a&gt;. 
Use and copy the code however you want - it's all open source under the 
MIT license.&lt;/p&gt;
&lt;h2&gt;Installing Our Application Dependencies&lt;/h2&gt;
&lt;p&gt;Our Bottle app needs a helper code library to make it easy to dial outbound
phone calls. Bottle and the Twilio helper library are installable from 
&lt;a href="https://pypi.python.org/pypi"&gt;PyPI&lt;/a&gt; into a virtualenv. Open your terminal 
and use the &lt;code&gt;virtualenv&lt;/code&gt; command to create a new virtualenv:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;virtualenv bottlephone
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Use the &lt;code&gt;activate&lt;/code&gt; script within the virtualenv, which makes this virtualenv
the active Python installation. Note that you need to do this in every 
terminal window that you want this virtualenv to be used.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;source bottlephone/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The command prompt will change after activating the virtualenv
to something like &lt;code&gt;(bottlephone) $&lt;/code&gt;. Here is a screenshot of what my
environment looked like when I used the &lt;code&gt;activate&lt;/code&gt; script.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160830-phone-calls-bottle/activate-virtualenv.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Next use the &lt;code&gt;pip&lt;/code&gt; command to install the &lt;a href="/bottle.html"&gt;Bottle&lt;/a&gt; and 
&lt;a href="https://www.twilio.com/docs/libraries/python"&gt;Twilio Python&lt;/a&gt; packages
into your virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install bottle twilio==5.7.0
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;After the installation script finishes, we will have the required 
dependencies to build our app. Time to write some Python code to dial 
outbound phone calls.&lt;/p&gt;
&lt;h2&gt;Bottle and Twilio&lt;/h2&gt;
&lt;p&gt;Our simple Bottle web app will have three routes: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/&lt;/code&gt; - returns a text string to let us know our Bottle app is running&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/twiml&lt;/code&gt; - responds with &lt;a href="https://www.twilio.com/docs/api/twiml"&gt;TwiML&lt;/a&gt; 
  (a simple subset of XML) that instructs Twilio what to do when someone
  picks up the call to them from our Bottle web app&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/dial-phone/&amp;lt;outbound_phone_number&amp;gt;&lt;/code&gt;, where "outbound_phone_number" is
  a phone number in the format "+12025551234" - this route uses the Twilio
  helper library to send a POST request to the Twilio Voice API to dial a
  phone  call&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We can build the structure of our Bottle app and the first route right now.
Create a new file named &lt;code&gt;app.py&lt;/code&gt; with the following contents to start our
app.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;bottle&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;bottle&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;twilio&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;twiml&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;twilio.rest&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TwilioRestClient&lt;/span&gt;


&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bottle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_app&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# plug in account SID and auth token here if they are not already exposed as&lt;/span&gt;
&lt;span class="c1"&gt;# environment variables&lt;/span&gt;
&lt;span class="n"&gt;twilio_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TwilioRestClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;TWILIO_NUMBER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;TWILIO_NUMBER&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;+12025551234&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;NGROK_BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;NGROK_BASE_URL&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://c6c6d4e8.ngrok.io&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&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;index&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    Returns a standard text response to show the app is up and running.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Bottle app running!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;127.0.0.1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reloader&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Make sure you are in the directory where you created the above &lt;code&gt;app.py&lt;/code&gt;
file. Run the app via the Bottle development server with the following 
command. Make sure your virtualenv is still activated so our code can rely 
on the Bottle code library.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python app.py
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We should see a successful development server start up like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bottlephone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;matt&lt;/span&gt;&lt;span class="nv"&gt;@ubuntu&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="n"&gt;bottlephone&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="n"&gt;Bottle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v0&lt;/span&gt;&lt;span class="mf"&gt;.12.9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;starting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;up&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;using&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;WSGIRefServer&lt;/span&gt;&lt;span class="p"&gt;())...&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;Listening&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="mf"&gt;127.0.0.1&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;Hit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Ctrl&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Here is what the development server message looks like in my environment 
on Ubuntu:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160830-phone-calls-bottle/bottle-app-running.png" width="100%" class="technical-diagram img-rounded" alt="Successfully starting the Bottle development server from the command line."&gt;&lt;/p&gt;
&lt;p&gt;Let's test out the app by going to "localhost:8000"
in the web browser. We should get a simple success message that the app
is running and responding to requests.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160830-phone-calls-bottle/bottle-app-web-browser.png" width="100%" class="technical-diagram img-rounded" alt="Simple success message in the web browser that the Bottle app is running."&gt;&lt;/p&gt;
&lt;p&gt;Next we need to obtain a phone number that our Bottle app can use to 
call other phone numbers.&lt;/p&gt;
&lt;h2&gt;Obtain a Phone Number&lt;/h2&gt;
&lt;p&gt;Our basic Bottle web app is running but what we really want to do is dial
outbound calls - which will be handled by &lt;a href="https://www.twilio.com/"&gt;Twilio&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In your web browser go to the
&lt;a href="https://www.twilio.com/try-twilio"&gt;Twilio website and sign up for a free account&lt;/a&gt;. 
You can also sign into your existing Twilio account if you already have one.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160830-phone-calls-bottle/try-twilio.png" width="100%" class="technical-diagram img-rounded" alt="Twilio sign up screen."&gt;&lt;/p&gt;
&lt;p&gt;The Twilio trial account allows you to dial and receive phone calls to 
your own validated phone number. To dial and receive calls from any phone 
number then you need to upgrade your account (hit the upgrade button on the
top navigation bar to do that). Trial accounts are great for initial 
development before your application goes live but upgraded accounts are where
the real power comes in.&lt;/p&gt;
&lt;p&gt;Once you are signed into your Twilio account, go to the 
&lt;a href="https://www.twilio.com/console/phone-numbers"&gt;manage phone numbers screen&lt;/a&gt;.
On this screen you can 
&lt;a href="https://www.twilio.com/console/phone-numbers/search"&gt;buy one or more phone numbers&lt;/a&gt;
or click on an existing phone number in your account to configure it.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160830-phone-calls-bottle/manage-phone-numbers.png" width="100%" class="technical-diagram img-rounded" alt="Manage phone numbers screen."&gt;&lt;/p&gt;
&lt;p&gt;There is nothing for us to configure right now on the phone number 
configuration page because we are making outbound phone calls for this 
tutorial. Now that we have a phone number in hand, let's add the final bit
of code to our Bottle app to get this app working.&lt;/p&gt;
&lt;h2&gt;Making Phone Calls&lt;/h2&gt;
&lt;p&gt;We need to add two new routes to our Bottle app so it can dial outbound
phone calls. Modify your existing app.py file with the two new functions
below, &lt;code&gt;twiml_response&lt;/code&gt; and &lt;code&gt;outbound_call&lt;/code&gt;. None of the other code in
this file needs to change other than adding those two new functions to
what we wrote in the previous section.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;bottle&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;bottle&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;twilio&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;twiml&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;twilio.rest&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TwilioRestClient&lt;/span&gt;


&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bottle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_app&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# plug in account SID and auth token here if they are not already exposed as&lt;/span&gt;
&lt;span class="c1"&gt;# environment variables&lt;/span&gt;
&lt;span class="n"&gt;twilio_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TwilioRestClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# add your Twilio phone number here&lt;/span&gt;
&lt;span class="n"&gt;TWILIO_NUMBER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;TWILIO_NUMBER&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;+16093002984&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# plug in your Ngrok Forwarding URL - we&amp;#39;ll set it up in a minute&lt;/span&gt;
&lt;span class="n"&gt;NGROK_BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;NGROK_BASE_URL&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://c6c6d4e8.ngrok.io&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&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;index&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    Returns a standard text response to show the app is up and running.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Bottle app running!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/twiml&amp;#39;&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;twiml_response&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    Provides TwiML instructions in response to a Twilio POST webhook&lt;/span&gt;
&lt;span class="sd"&gt;    event so that Twilio knows how to handle the outbound phone call&lt;/span&gt;
&lt;span class="sd"&gt;    when someone picks up the phone.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;twiml&lt;/span&gt;&lt;span class="o"&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;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Sweet, this phone call is answered by your Bottle app!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://api.twilio.com/cowbell.mp3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&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;Response&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;response&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="nd"&gt;@route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/dial-phone/&amp;lt;outbound_phone_number&amp;gt;&amp;#39;&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;outbound_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outbound_phone_number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    Uses the Twilio Python helper library to send a POST request to&lt;/span&gt;
&lt;span class="sd"&gt;    Twilio telling it to dial an outbound phone call from our specific&lt;/span&gt;
&lt;span class="sd"&gt;    Twilio phone number (that phone number must be owned by our Twilio &lt;/span&gt;
&lt;span class="sd"&gt;    account).&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="c1"&gt;# the url must match the Ngrok Forwarding URL plus the route defined in&lt;/span&gt;
    &lt;span class="c1"&gt;# the previous function that responds with TwiML instructions&lt;/span&gt;
    &lt;span class="n"&gt;twilio_client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calls&lt;/span&gt;&lt;span class="o"&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;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;outbound_phone_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                               &lt;span class="n"&gt;from_&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;BLOG_POST_NUMBER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                               &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;NGROK_BASE_URL&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/twiml&amp;#39;&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;phone call placed to &amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;outbound_phone_number&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;!&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;127.0.0.1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reloader&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;There is just one problem with our current setup if you're developing on
a local environment: Twilio won't be able to reach that &lt;code&gt;/twiml&lt;/code&gt; route. 
We need to deploy our app to a reachable server, or just use a localhost 
tunneling tool like &lt;a href="https://ngrok.com"&gt;Ngrok&lt;/a&gt;. Ngrok provides an external
URL that connects to a port running on your machine. 
&lt;a href="https://ngrok.com/download"&gt;Download and install the Ngrok application&lt;/a&gt; 
that is appropriate for your operating system.&lt;/p&gt;
&lt;p&gt;We run Ngrok locally and expose our Bottle app that is running on 
port 8000. Run this command within the directory where the Ngrok executable is
located.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;./ngrok http 8000
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Ngrok will start up and provide us with a Forwarding URL, with both HTTP
and HTTPS versions.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160830-phone-calls-bottle/start-ngrok.png" width="100%" class="technical-diagram img-rounded" alt="Ngrok started and running to serve as a localhost tunnel."&gt;&lt;/p&gt;
&lt;p&gt;We can use the Forwarding URL to instruct Twilio how to handle the outbound
phone call when someone picks up. Insert the Ngrok forwarding URL into the
&lt;code&gt;app.py&lt;/code&gt; file where &lt;code&gt;NGROK_BASE_URL&lt;/code&gt; is specified.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160830-phone-calls-bottle/access-ngrok.png" width="100%" class="technical-diagram img-rounded" alt="Paste the ngrok Forwarding URL into the Twilio webhook configuration text box."&gt;&lt;/p&gt;
&lt;p&gt;If Ngrok is useful to you, make sure to read this 
&lt;a href="https://www.twilio.com/blog/2015/09/6-awesome-reasons-to-use-ngrok-when-testing-webhooks.html"&gt;6 awesome reasons to use Ngrok when testing webhooks post&lt;/a&gt; 
to learn even more about the tool.&lt;/p&gt;
&lt;p&gt;Time to test out our app, let's give it a quick spin.&lt;/p&gt;
&lt;h2&gt;Making Phone Calls&lt;/h2&gt;
&lt;p&gt;Make sure your Bottle development server is still running or re-run it with
the &lt;code&gt;python app.py&lt;/code&gt; command in a shell where your virtualenv is still
activated.&lt;/p&gt;
&lt;p&gt;Bring up the application in a browser, this time test out the phone calling
capabilities. Go to "localhost:8000/dial-phone/my-phone-number", where 
"my-phone-number" is a number in the "+12025551234" format. For example,
here is what happens when I dialed +12023351278:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160830-phone-calls-bottle/phone-call-placed.png" width="100%" class="technical-diagram img-rounded" alt="Dialing an outbound phone call with Bottle."&gt;&lt;/p&gt;
&lt;p&gt;And here is the inbound phone call!&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160830-phone-calls-bottle/inbound-call.png" width="100%" class="technical-diagram img-rounded" alt="Receiving an incoming phone call on the iPhone."&gt;&lt;/p&gt;
&lt;p&gt;When we pick up the phone call we also see the &lt;code&gt;/twiml&lt;/code&gt; route get called via
Ngrok.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160830-phone-calls-bottle/ngrok-twiml.png" width="100%" class="technical-diagram img-rounded" alt="/twiml route being called via Ngrok."&gt;&lt;/p&gt;
&lt;p&gt;With just two routes in our Bottle app and Twilio we were able to make
outbound phone calls. Not bad!&lt;/p&gt;
&lt;h2&gt;What's next?&lt;/h2&gt;
&lt;p&gt;Sweet, we can now dial outbound phone calls to &lt;em&gt;any&lt;/em&gt; phone number from
our Bottle web application. Next you may want to try one of these tutorials 
to add even more features to your app:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Upgrade your &lt;a href="/blog/reply-sms-text-messages-python-bottle.html"&gt;Bottle app to also send and respond to text messages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Create a &lt;a href="https://www.twilio.com/blog/2016/05/add-phone-calling-slack-python.html"&gt;phone-calling Slack bot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Implement &lt;a href="https://www.twilio.com/docs/tutorials/walkthrough/call-tracking/python/django"&gt;call tracking&lt;/a&gt;
  for both inbound and outbound phone calls made through your app&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Questions? Contact me via Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;. I'm also on GitHub as
&lt;a href="https://github.com/mattmakai"&gt;mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See something wrong in this post? Fork 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/160830-phone-calls-bottle.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Fri, 16 Jun 2017 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2016-08-30:blog/dial-outbound-phone-calls-python-bottle.html</guid></item><item><title>Python for Entrepreneurs</title><link>https://www.fullstackpython.com/blog/python-entrepreneurs.html</link><description>&lt;p&gt;&lt;a href="https://www.kickstarter.com/projects/mikeckennedy/python-for-entrepreneurs-video-course"&gt;Python for Entrepreneurs&lt;/a&gt;
is a new video course by the creators of 
&lt;a href="https://talkpython.fm/"&gt;Talk Python to Me&lt;/a&gt; and 
&lt;a href="https://fullstackpython.com/"&gt;Full Stack Python&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Update&lt;/em&gt;: The Kickstarter has been funded! Michael and I are hard 
at work on the course content. Thank you to everyone who supported 
us as a backer. The course is available in early access mode on
&lt;a href="https://training.talkpython.fm"&gt;training.talkpython.fm&lt;/a&gt; until it
is fully released.&lt;/p&gt;
&lt;p&gt;We are creating this course and running a Kickstarter for it based on 
feedback that it's still too damn difficult to turn basic Python programming 
knowledge into a business to generate income as a side or full time project. 
Both Michael and I have been able to make that happen for ourselves and we 
want to share every difficult lesson we've learned through this course.&lt;/p&gt;
&lt;p&gt;The Python for Entrepreneurs videos and content will dive into building 
and deploying a real-world web application, marketing it to prospective 
customers, handling search engine optimization, making money through credit 
card payments, getting help from part-time contractors for niche tasks and 
scaling up to meet traffic demands.&lt;/p&gt;
&lt;p&gt;If this course hits the mark for what you want to do with Python,
&lt;a href="https://www.kickstarter.com/projects/mikeckennedy/python-for-entrepreneurs-video-course"&gt;check out the Kickstarter&lt;/a&gt; - we've 
set up steep discounts for early backers.&lt;/p&gt;
&lt;p&gt;If you have any questions, please reach out to 
&lt;a href="https://twitter.com/mkennedy"&gt;Michael Kennedy&lt;/a&gt; 
or me, &lt;a href="https://twitter.com/mattmakai"&gt;Matt Makai&lt;/a&gt;.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Tue, 25 Apr 2017 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2016-07-30:blog/python-entrepreneurs.html</guid></item><item><title>Setting Up Python 3, Django &amp; Gunicorn on Linux Mint 17.3</title><link>https://www.fullstackpython.com/blog/python-3-django-gunicorn-linux-mint-17.html</link><description>&lt;p&gt;Linux Mint 17.3 "Rosa" is December 2015 release of the polished and 
widely-used Linux distribution. This Mint release includes both Python 2.7 
and 3.4 by default, but in this tutorial we will download and install the 
latest Python 3.5.1 version to run our Django application. &lt;/p&gt;
&lt;p&gt;If you want to use a different Linux distribution such as 
&lt;a href="/ubuntu.html"&gt;Ubuntu&lt;/a&gt; instead of Mint, check out
&lt;a href="/blog/python-3-django-gunicorn-ubuntu-1604-xenial-xerus.html"&gt;the tutorial for Ubuntu 16.04 "Xenial Xerus"&lt;/a&gt;. If Mint is your desired 
&lt;a href="/development-environments.html"&gt;development environment&lt;/a&gt; though, let's
get started!&lt;/p&gt;
&lt;h2&gt;Tools We Need&lt;/h2&gt;
&lt;p&gt;Our setup will use several system packages and code libraries to get
up and running. Do not worry about installing these dependencies just yet, 
we will get to them as we progress through the tutorial. The tools and 
their current versions as of June 2016 are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://blog.linuxmint.com/?p=2947"&gt;Linux Mint 17.3 "Rosa"&lt;/a&gt; with the 
  default Cinnamon desktop&lt;/li&gt;
&lt;li&gt;&lt;a href="/why-use-python.html"&gt;Python&lt;/a&gt; version 
  &lt;a href="https://www.python.org/downloads/release/python-351/"&gt;3.5.1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/django.html"&gt;Django&lt;/a&gt; web framework version 
  &lt;a href="https://pypi.org/project/Django/1.9.13/"&gt;1.9.x&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/green-unicorn-gunicorn.html"&gt;Green Unicorn (Gunicorn)&lt;/a&gt; version 
  &lt;a href="http://docs.gunicorn.org/en/stable/news.html"&gt;19.6&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you are on Mac OS X or Windows, my recommendation is to use 
virtualization software such as 
&lt;a href="https://www.parallels.com/products/desktop/"&gt;Parallels&lt;/a&gt; or
&lt;a href="https://www.virtualbox.org/wiki/Downloads"&gt;VirtualBox&lt;/a&gt; with the 
&lt;a href="https://www.linuxmint.com/download.php"&gt;Linux Mint Cinnamon desktop .iso&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We should see a desktop screen like this one when we boot up the operating 
system for the first time.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160626-mint-django-gunicorn/mint-desktop.jpg" width="100%" class="technical-diagram img-rounded" alt="Linux Mint default desktop"&gt;&lt;/p&gt;
&lt;p&gt;Open up terminal to proceed with the configuration.&lt;/p&gt;
&lt;h2&gt;System Packages&lt;/h2&gt;
&lt;p&gt;We can see the Python version Linux Mint comes with, as well as where its
executable is stored.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python3 --version
which python3
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;em&gt;output&lt;/em&gt; of those two commands should be (these are not commands to run):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Python 3.4.3
/usr/bin/python3
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img src="/img/160626-mint-django-gunicorn/which-python.png" width="100%" class="technical-diagram img-rounded" alt="Output of 'python --version' and 'which python3' commands."&gt;&lt;/p&gt;
&lt;p&gt;We really want to use the latest Python release instead of the default 3.4
when starting a new Python project, so let's download and install 3.5.1 now.&lt;/p&gt;
&lt;p&gt;Run these commands in the terminal to download Python 3.5.1 source code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;cd&lt;/span&gt; &lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="n"&gt;Downloads&lt;/span&gt;
&lt;span class="n"&gt;wget&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;www&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ftp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;3.5&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;3.5&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;1.&lt;/span&gt;&lt;span class="n"&gt;tgz&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img src="/img/160626-mint-django-gunicorn/download-python.png" width="100%" class="technical-diagram img-rounded" alt="wget Python source code output."&gt;&lt;/p&gt;
&lt;p&gt;Extract the Python source code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;tar -xvf Python-3.5.1.tgz
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Linux Mint is not configured by default to build the Python source code. We
need to update our system package lists and install several packages to 
make building the Python source code possible. If you have a password on 
your user account, enter it when prompted to allow the installation to 
proceed.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo apt update
sudo apt install build-essential checkinstall
sudo apt install libreadline-gplv2-dev libncursesw5-dev libssl-dev 
sudo apt install libsqlite3-dev tk-dev libgdbm-dev libc6-dev libbz2-dev
sudo apt install python3-dev
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Once the packages are installed, we can configure and install Python from
source.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;cd Python-3.5.1
./configure
sudo make install
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Test that the installation worked properly by starting up the Python REPL:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python3.5
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If the REPL starts up properly with Python 3.5.1 in the output then we're
good to go.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160626-mint-django-gunicorn/python351-output.png" width="100%" class="technical-diagram img-rounded" alt="wget Python source code output."&gt;&lt;/p&gt;
&lt;p&gt;The basic system packages we need are now installed so we can proceed to
our Python-specific dependencies.&lt;/p&gt;
&lt;h2&gt;Virtual environment and pip&lt;/h2&gt;
&lt;p&gt;Python 3.5 comes with the virtual environment and pip applications so we 
can use them to handle our
&lt;a href="/application-dependencies.html"&gt;application dependencies&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Create a directory to store virtual environments then create a virtualenv
for our Django project.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# the tilde &amp;quot;~&amp;quot; specifies the user&amp;#39;s home directory, like /home/matt
cd ~
mkdir venvs
# specify the system python3 installation
python3.5 -m venv djangoproj
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Activate the virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;source ~/venvs/djangoproj/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Our virtual environment is properly activated when we see &lt;code&gt;(djangoproj)&lt;/code&gt;
prepended to our prompt. &lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160626-mint-django-gunicorn/venv-activated.png" width="100%" class="technical-diagram img-rounded" alt="Output from the virtualenv environment activation."&gt;&lt;/p&gt;
&lt;p&gt;Our virtualenv with Python 3.5.1 is activated so we can install whatever
dependencies we want, such as Django and Gunicorn. Our default &lt;code&gt;python&lt;/code&gt;
command is also set to use the Python 3.5.1 installation instead of the
Python 2.7 version that comes with Linux Mint.&lt;/p&gt;
&lt;h2&gt;Django and Gunicorn&lt;/h2&gt;
&lt;p&gt;Now we can install Django and Green Unicorn into our virtual environment.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install django==1.9.7 gunicorn==19.6
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If there are no errors in the pip output then that is a good sign we can 
proceed.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160626-mint-django-gunicorn/good-sign.png" width="100%" class="technical-diagram img-rounded" alt="Django and Gunicorn properly install via the pip command."&gt;&lt;/p&gt;
&lt;p&gt;Create a new Django project named &lt;code&gt;djangoproj&lt;/code&gt;, or whatever you want to name
your project. Change into the directory for the new project.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;cd ~
django-admin startproject djangoproj
cd djangoproj
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We can run Django using the development server with the 
&lt;code&gt;python manage.py runserver&lt;/code&gt; command. However, start Django up with
Gunicorn instead.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;gunicorn djangoproj.wsgi
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img src="/img/160626-mint-django-gunicorn/gunicorn-run.png" width="100%" class="technical-diagram img-rounded" alt="Result of running gunicorn djangoproj.wsgi on the command line."&gt;&lt;/p&gt;
&lt;p&gt;Awesome, we can bring up our shell project in the web browser at
the &lt;a href="http://localhost:8000"&gt;http://localhost:8000&lt;/a&gt; or 
&lt;a href="http://127.0.0.1:8000"&gt;http://127.0.0.1:8000&lt;/a&gt; address.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160626-mint-django-gunicorn/it-works.png" width="100%" class="technical-diagram img-rounded" alt="Django project running in the Firefox web browser."&gt;&lt;/p&gt;
&lt;p&gt;Now you're ready for Django development!&lt;/p&gt;
&lt;h2&gt;Ready for Development&lt;/h2&gt;
&lt;p&gt;Those are the first few steps for beginning development with 
&lt;a href="/django.html"&gt;Django&lt;/a&gt; and &lt;a href="/green-unicorn-gunicorn.html"&gt;Gunicorn&lt;/a&gt; on 
Linux Mint 17.3 "Rosa". If you need an even more in-depth walkthrough for 
deploying your Python web application to a production environment, check 
out the 
&lt;a href="http://www.deploypython.com/"&gt;Full Stack Python Guide to Deployments book&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To figure out what to do next for your Python project, read the topics 
found on the &lt;a href="/table-of-contents.html"&gt;table of contents&lt;/a&gt; page.&lt;/p&gt;
&lt;p&gt;Questions? Contact me via Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;. I'm also on GitHub with
the username &lt;a href="https://github.com/mattmakai"&gt;mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See something wrong in this post? Fork 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/160626-django-gunicorn-mint-linux-17.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Thu, 17 Jun 2021 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2016-06-26:blog/python-3-django-gunicorn-linux-mint-17.html</guid></item><item><title>Configuring Python 3, Pyramid and Gunicorn on Ubuntu 16.04</title><link>https://www.fullstackpython.com/blog/python-3-pyramid-gunicorn-ubuntu-1604-xenial-xerus.html</link><description>&lt;p&gt;&lt;a href="/ubuntu.html"&gt;Canonical's Ubuntu 16.04 Long Term Support (LTS)&lt;/a&gt; Linux
&lt;a href="/operating-systems.html"&gt;operating system&lt;/a&gt;, also known as "Xenial Xerus",
was released in April 2016. It is the first Ubuntu release to include 
&lt;a href="/python-2-or-3.html"&gt;Python 3&lt;/a&gt; instead of Python 2 as its default Python 
installation.&lt;/p&gt;
&lt;p&gt;The &lt;a href="/pyramid.html"&gt;Pyramid&lt;/a&gt; web framework has long supported Python 3.
With just a few short steps we can start a new &lt;a href="/pyramid.html"&gt;Pyramid&lt;/a&gt; 
project and run it with 
&lt;a href="/green-unicorn-gunicorn.html"&gt;Green Unicorn (Gunicorn)&lt;/a&gt; on Ubuntu 16.04.&lt;/p&gt;
&lt;h2&gt;Required Tools&lt;/h2&gt;
&lt;p&gt;Our project requires Ubuntu 16.04 plus several code libraries. You do not
need to install these tools yet - we will get to them in turn as the
walkthrough progresses. Our requirements and their current versions are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://releases.ubuntu.com/16.04/"&gt;Ubuntu 16.04 LTS (Xenial Xerus)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/why-use-python.html"&gt;Python version 3.5&lt;/a&gt; (default in Ubuntu 16.04)&lt;/li&gt;
&lt;li&gt;&lt;a href="/pyramid.html"&gt;Pyramid web framework&lt;/a&gt; version 
  &lt;a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/"&gt;1.7&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/green-unicorn-gunicorn.html"&gt;Gunicorn&lt;/a&gt; version 
  &lt;a href="http://docs.gunicorn.org/en/stable/news.html"&gt;19.6&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://docs.pylonsproject.org/projects/waitress/en/latest/"&gt;Waitress&lt;/a&gt;
  version 0.9.0&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you are developing on Mac OS X or Windows, you can use
virtualization software such
as &lt;a href="https://www.parallels.com/products/desktop/"&gt;Parallels&lt;/a&gt; or
&lt;a href="https://www.virtualbox.org/wiki/Downloads"&gt;VirtualBox&lt;/a&gt; with the 
&lt;a href="http://releases.ubuntu.com/16.04/"&gt;Ubuntu .iso file&lt;/a&gt;. Either the amd64 or
i386 version of 16.04 is fine. While creating this I used the amd64 version. &lt;/p&gt;
&lt;p&gt;A desktop screen like this one appears when you boot up Ubuntu.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160619-ubuntu-pyramid-gunicorn/ubuntu-desktop.jpg" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Open a new terminal window so we can be ready to install required system 
packages.&lt;/p&gt;
&lt;h2&gt;Install System Packages&lt;/h2&gt;
&lt;p&gt;The precise Python version can be shown using the &lt;code&gt;python&lt;/code&gt; command with the
&lt;code&gt;--version&lt;/code&gt; argument.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python3 --version
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We can also view where the &lt;code&gt;python3&lt;/code&gt; program is installed on Ubuntu using the
&lt;code&gt;which&lt;/code&gt; command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;which python3
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img src="/img/160619-ubuntu-pyramid-gunicorn/which-python.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Ubuntu requires a few system packages before we can properly install Pyramid
and Gunicorn. When we run the &lt;code&gt;apt&lt;/code&gt; command to install system packages we 
will be prompted for the superuser password. Restricted system access is 
necessary to modify files within the system folders.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo apt-get install python3-dev
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img src="/img/160619-ubuntu-pyramid-gunicorn/install-packages.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Press &lt;code&gt;y&lt;/code&gt; then return to let the system package installation run.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160619-ubuntu-pyramid-gunicorn/packages-installed.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;The required system packages are installed. We can now install the
Python-specific dependencies.&lt;/p&gt;
&lt;h2&gt;Set up a virtual environment&lt;/h2&gt;
&lt;p&gt;Create a directory for the virtual environments. Then create a new virtual environment.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# the tilde &amp;quot;~&amp;quot; specifies the user&amp;#39;s home directory, like /home/matt
cd ~
mkdir venvs
# specify the system python3 installation
/usr/bin/python3 -m venv venvs/pyramidproj
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Activate the virtual environment.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;source ~/venvs/pyramidproj/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Our prompt will change after we properly activate the virtual environment to
something like &lt;code&gt;(pyramidproj) matt@ubuntu:~$&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160619-ubuntu-pyramid-gunicorn/venv-activated.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Our virtual environment is activated with Python 3.&lt;/p&gt;
&lt;p&gt;We should update pip and venv to the latest versions in our virtual environment.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;upgrade&lt;/span&gt; &lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;setuptools&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We can install whatever dependencies we want, in our case Pyramid and Gunicorn. &lt;/p&gt;
&lt;h2&gt;Install Python Packages&lt;/h2&gt;
&lt;p&gt;We can install Pyramid, Gunicorn and Waitress into our virtual environment using 
the &lt;code&gt;pip&lt;/code&gt; command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install pip install &amp;quot;pyramid==1.7&amp;quot; gunicorn waitress
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;No errors like we see in the following screenshot is a good sign.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160619-ubuntu-pyramid-gunicorn/good-sign.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Pyramid comes with a project starter template creation tool named &lt;code&gt;pcreate&lt;/code&gt;.
Run &lt;code&gt;pcreate&lt;/code&gt; to generate the boilerplate for a new Pyramid project named
"pyramidproj".&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pcreate -s starter pyramidproj
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Use the &lt;code&gt;cd&lt;/code&gt; (change directory) command to move into the new folder.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;cd ~/pyramidproj
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;A slew of new files have been created within the "pyramidproj" directory.
These are the basic files you can customize for the web application you want
to build. A good resource for understanding and modifying these files is
to follow the 
&lt;a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/quick_tutorial/index.html"&gt;quick tutorial for Pyramid&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For now, we just want to use Gunicorn to run our starter pyramidproj app.
Install pyramidproj into your virtual environment using the &lt;code&gt;python&lt;/code&gt; command on
&lt;code&gt;setup.py&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python setup.py develop
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now we can run our app with Gunicorn. Pyramid is a 
&lt;a href="http://docs.pylonsproject.org/projects/pyramid/en/latest/api/paster.html"&gt;paste&lt;/a&gt;-compatible
framework, so we use the &lt;code&gt;--paste&lt;/code&gt; argument to run the WSGI server with
the "development.ini" configuration file. In addition, the &lt;code&gt;-b&lt;/code&gt; argument 
tells Gunicorn which port number to bind on when the server starts.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;gunicorn --paste development.ini -b :8080
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img src="/img/160619-ubuntu-pyramid-gunicorn/gunicorn-run.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Cool, we can bring up our starter Pyramid project up in the web browser at
the &lt;code&gt;localhost:8000&lt;/code&gt; or &lt;code&gt;127.0.0.1:8000&lt;/code&gt; address.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160619-ubuntu-pyramid-gunicorn/it-works.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Time to develop a full-fledged web application with &lt;a href="/pyramid.html"&gt;Pyramid&lt;/a&gt;!&lt;/p&gt;
&lt;h2&gt;Ready to Develop with Pyramid&lt;/h2&gt;
&lt;p&gt;Now you have a simple setup to develop Pyramid web apps using Gunicorn as
the &lt;a href="/wsgi-servers.html"&gt;WSGI server&lt;/a&gt; on Ubuntu 16.04. If you need a
full step-by-step tutorial to deploy your Python web application to a
production environment, check out the 
&lt;a href="http://www.deploypython.com/"&gt;Full Stack Python Guide to Deployments book&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To decide what to do next with your Python project, check out the
&lt;a href="/table-of-contents.html"&gt;Full Stack Python table of contents&lt;/a&gt; page.&lt;/p&gt;
&lt;p&gt;See something wrong in this post? Fork 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/160619-pyramid-gunicorn-ubuntu-1604.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Wed, 10 Aug 2016 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2016-06-19:blog/python-3-pyramid-gunicorn-ubuntu-1604-xenial-xerus.html</guid></item><item><title>Replying to SMS Text Messages with Python and Bottle</title><link>https://www.fullstackpython.com/blog/reply-sms-text-messages-python-bottle.html</link><description>&lt;p&gt;Python applications can 
&lt;a href="/blog/send-sms-text-messages-python.html"&gt;easily send SMS&lt;/a&gt; 
by using a &lt;a href="/application-programming-interfaces.html"&gt;web API&lt;/a&gt;. 
Web apps built with the &lt;a href="/bottle.html"&gt;Bottle&lt;/a&gt; framework can also reply
to incoming text messages by handling inbound HTTP POST webhooks. In
this post we'll quickly walk through how to set up a Bottle web app to
handle SMS data in the form of HTTP POST requests.&lt;/p&gt;
&lt;h2&gt;Tools We'll Need&lt;/h2&gt;
&lt;p&gt;This tutorial works with either &lt;a href="/python-2-or-3.html"&gt;Python 2 or 3&lt;/a&gt;, 
although Python 3 is recommended by the community for new applications. 
Install one of those two Python versions on your system to use for this
walkthrough. We also need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pip.pypa.io/en/stable/"&gt;pip&lt;/a&gt; and 
  &lt;a href="https://virtualenv.pypa.io/en/latest/"&gt;virtualenv&lt;/a&gt; to handle
  &lt;a href="/application-dependencies.html"&gt;application dependencies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/bottle.html"&gt;Bottle&lt;/a&gt; web framework&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ngrok.com/"&gt;Ngrok&lt;/a&gt; for localhost tunneling to our Bottle
  application while it's running on our local development environment&lt;/li&gt;
&lt;li&gt;Free &lt;a href="https://www.twilio.com/try-twilio"&gt;Twilio account&lt;/a&gt; to use their 
  &lt;a href="https://www.twilio.com/docs/api/rest/sending-messages"&gt;SMS web API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Open source 
  &lt;a href="https://pypi.org/project/twilio"&gt;Twilio Python helper library&lt;/a&gt;,
  version 5.7.0 or earlier&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Check out the guide on 
&lt;a href="/blog/python-3-bottle-gunicorn-ubuntu-1604-xenial-xerus.html"&gt;how to set up Python 3, Bottle and Gunicorn on Ubuntu 16.04 LTS&lt;/a&gt;
if you need help getting your 
&lt;a href="/development-environments.html"&gt;development environment&lt;/a&gt; 
configured.&lt;/p&gt;
&lt;h2&gt;Application Dependency Installation&lt;/h2&gt;
&lt;p&gt;Our application will use a helper code library to reply to inbound SMS.
Bottle and the helper library are installable from 
&lt;a href="https://pypi.python.org/pypi"&gt;PyPI&lt;/a&gt; into a virtualenv. Open your terminal 
and use the &lt;code&gt;virtualenv&lt;/code&gt; command to create a new virtualenv:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;virtualenv replysms
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Invoke the virtualenv's &lt;code&gt;activate&lt;/code&gt; script, which makes it the "active" 
Python installation. Note that you need to do this in every terminal window
that you want this virtualenv to be used.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;source replysms/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The command prompt will change after activating the virtualenv:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160605-reply-sms-python-bottle/activate-virtualenv.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Use the &lt;code&gt;pip&lt;/code&gt; command to install the &lt;a href="/bottle.html"&gt;Bottle&lt;/a&gt; and 
&lt;a href="https://www.twilio.com/docs/libraries/python"&gt;Twilio Python&lt;/a&gt; packages
into your virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install bottle twilio==5.7.0
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We have installed the required dependencies so now Python code that is run 
with the virtualenv activated will be able to use those packages. It's time 
to build our Bottle web app and reply to incoming text messages.&lt;/p&gt;
&lt;h2&gt;Coding Our Bottle App&lt;/h2&gt;
&lt;p&gt;The Bottle web app will have two routes. One route will allow us to test
that the app is running. The other route will handle and respond to incoming
HTTP POST requests from Twilio. Create a new file named &lt;code&gt;app.py&lt;/code&gt; in your 
in the directory where you want to store this Python project.&lt;/p&gt;
&lt;p&gt;Write the following code in the new &lt;code&gt;app.py&lt;/code&gt; file. There is also
&lt;a href="https://gist.github.com/mattmakai/6ec3b46e40a1020a3ea9c772c601199a"&gt;a GitHub Gist&lt;/a&gt; 
with the code that you can copy and paste.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;bottle&lt;/span&gt; &lt;span class="kn"&gt;import&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="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;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;twilio&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;twiml&lt;/span&gt;


&lt;span class="nd"&gt;@route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&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_app&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# returns a simple string stating the app is working&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;It works!&amp;quot;&lt;/span&gt;


&lt;span class="nd"&gt;@post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/twilio&amp;#39;&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;inbound_sms&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;twiml_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;twiml&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# grab message from the request. could also get the &amp;quot;To&amp;quot; and &lt;/span&gt;
    &lt;span class="c1"&gt;# &amp;quot;From&amp;quot; phone numbers as well from parameters with those names&lt;/span&gt;
    &lt;span class="n"&gt;inbound_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;forms&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Body&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# we can now use the incoming message text in our Python application&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;inbound_message&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Hello&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;twiml_response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello from Bottle right back at you!&amp;quot;&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;twiml_response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hi! Not quite sure what you meant, but okay.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# we return back the mimetype because Twilio needs an XML response&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;application/xml&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;return&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;twiml_response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;127.0.0.1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reloader&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The lines starting with &lt;code&gt;#&lt;/code&gt; are comments that give explanations for what
the code lines below them are doing. Bottle web apps define URL routes with 
the &lt;code&gt;@route&lt;/code&gt; and &lt;code&gt;@post&lt;/code&gt; decorators, depending on the type of HTTP request
the route should handle. &lt;/p&gt;
&lt;p&gt;Make sure your virtualenv is still active so that the application can use 
the Bottle and Twilio code libraries we installed earlier. Give the 
application a try by running it with &lt;code&gt;python app.py&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;Open a web browser and go to localhost:5000 (or 127.0.0.1:5000). We should
see "It works!" on the screen.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160605-reply-sms-python-bottle/bottle-app-local.jpg" width="100%" class="technical-diagram img-rounded" alt="Bottle application running locally on Ubuntu."&gt;&lt;/p&gt;
&lt;p&gt;However, there is an issue with our web app running on our local development
environment. Twilio cannot send a the HTTP POST request to the web app
server unless a localhost tunnel is created.&lt;/p&gt;
&lt;h2&gt;Ngrok Localhost Tunneling&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://ngrok.com"&gt;Ngrok&lt;/a&gt; is a localhost tunneling tool that bridges
your local development environment to an external URL. 
&lt;a href="https://ngrok.com/download"&gt;Download and install&lt;/a&gt; the Ngrok version that's
appropriate for your operating system.&lt;/p&gt;
&lt;p&gt;We can run Ngrok locally and expose our Bottle app that is running on 
port 5000. Run this command within the directory where the Ngrok executable is
located.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;./ngrok http 5000
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img src="/img/160605-reply-sms-python-bottle/start-ngrok.jpg" width="100%" class="technical-diagram img-rounded" alt="Ngrok started and running to serve as a localhost tunnel."&gt;&lt;/p&gt;
&lt;p&gt;Cool, now we can use the Forwarding URL so Twilio can send POST requests
to our application when there is an inbound SMS. Replace the URL in the
text box with your own Forwarding URL, like I did in this screenshot.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160605-reply-sms-python-bottle/access-ngrok.jpg" width="100%" class="technical-diagram img-rounded" alt="Paste the ngrok Forwarding URL into the Twilio webhook configuration text box."&gt;&lt;/p&gt;
&lt;p&gt;Now we just need a Twilio phone number that will send POST request to our
application when there is an inbound SMS.&lt;/p&gt;
&lt;h2&gt;Obtain a Phone Number&lt;/h2&gt;
&lt;p&gt;Our Bottle web app's route can respond to incoming POST requests but we
need to use Twilio to have a phone number that will convert the inbound SMS
data into the POST request. In your web browser go to the
&lt;a href="https://www.twilio.com/try-twilio"&gt;Twilio website and sign up for a free account&lt;/a&gt;. 
You can also sign into your existing Twilio account if you already have one.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160605-reply-sms-python-bottle/try-twilio.png" width="100%" class="technical-diagram img-rounded" alt="Twilio sign up screen."&gt;&lt;/p&gt;
&lt;p&gt;The Twilio trial account allows you to send and receive text messages to 
your own validated phone number. To send and reply to SMS to and from any 
phone number then you need to upgrade your account. Trial accounts are 
great for initial development before your application goes live.&lt;/p&gt;
&lt;p&gt;When you sign up, you receive a free Twilio phone number. We can
configure that phone number to forward the SMS information to our web 
application by setting up the response webhook.&lt;/p&gt;
&lt;p&gt;Go to the 
&lt;a href="https://www.twilio.com/console/phone-numbers"&gt;manage phone numbers screen&lt;/a&gt; 
and click on the phone number you want to configure for replying to 
text messages.&lt;/p&gt;
&lt;p&gt;Scroll down and look for the "Messaging" header. Change the 
"A Message Comes in" text box to input the ngrok Forwarding URL plus 
the "/twilio" route, as shown in the screenshot below.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160605-reply-sms-python-bottle/webhook-ngrok.jpg" width="100%" class="technical-diagram img-rounded" alt="Paste the ngrok Forwarding URL into the Twilio webhook configuration text box."&gt;&lt;/p&gt;
&lt;p&gt;Click the "Save" button so that our changes take effect.&lt;/p&gt;
&lt;p&gt;Our application is ready to go - time to give our phone number a try! 
Send "Hello" or whatever text you want to your phone number. Here is what 
the result looks like on my iPhone.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160605-reply-sms-python-bottle/bottle-success.png" width="100%" class="technical-diagram img-rounded" alt="Example screenshot of what SMS replies look like on the iPhone."&gt;&lt;/p&gt;
&lt;p&gt;The concise Bottle web app is a good start to build more complicated
programs such as 
&lt;a href="https://www.twilio.com/blog/2014/11/choose-your-own-adventure-presentations-with-reveal-js-python-and-websockets.html"&gt;Choose Your Own Adventure Presentations&lt;/a&gt;
or
&lt;a href="https://www.twilio.com/blog/2016/05/build-sms-slack-bot-python.html"&gt;SMS Slack bots&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;What's next?&lt;/h2&gt;
&lt;p&gt;Awesome, our Bottle application now replies to inbound SMS text
messages! &lt;/p&gt;
&lt;p&gt;Questions? Contact me via Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;. I'm also on GitHub with
the username &lt;a href="https://github.com/mattmakai"&gt;mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See something wrong in this post? Fork 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/160605-reply-sms-python-bottle.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Wed, 10 Aug 2016 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2016-06-05:blog/reply-sms-text-messages-python-bottle.html</guid></item><item><title>How to Build Your First Slack Bot with Python</title><link>https://www.fullstackpython.com/blog/build-first-slack-bot-python.html</link><description>&lt;p&gt;&lt;a href="/bots.html"&gt;Bots&lt;/a&gt; are a useful way to interact with chat services such as
&lt;a href="https://slack.com/"&gt;Slack&lt;/a&gt;. If you have never built a bot before, this
post provides an easy starter tutorial for combining the
&lt;a href="https://api.slack.com/"&gt;Slack API&lt;/a&gt; with Python to create your first bot.&lt;/p&gt;
&lt;p&gt;We will walk through setting up your development environment, obtaining a
Slack API bot token and coding our simple bot in Python.&lt;/p&gt;
&lt;h2&gt;Tools We Need&lt;/h2&gt;
&lt;p&gt;Our bot, which we will name "StarterBot", requires Python and the Slack API.
To run our Python code we need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Either &lt;a href="/python-2-or-3.html"&gt;Python 2 or 3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pip.pypa.io/en/stable/"&gt;pip&lt;/a&gt; and
  &lt;a href="https://virtualenv.pypa.io/en/stable/"&gt;virtualenv&lt;/a&gt; to handle Python
  &lt;a href="/application-dependencies.html"&gt;application dependencies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://slack.com/"&gt;Free Slack account&lt;/a&gt; - you need to be signed into at
  least one workspace where you have access to building apps.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It is also useful to have the &lt;a href="https://api.slack.com/"&gt;Slack API docs&lt;/a&gt; handy
while you're building this tutorial.&lt;/p&gt;
&lt;p&gt;All the code for this tutorial is available open source under the MIT license
in the &lt;a href="https://github.com/mattmakai/slack-starterbot"&gt;slack-starterbot&lt;/a&gt; public
repository.&lt;/p&gt;
&lt;h2&gt;Establishing Our Environment&lt;/h2&gt;
&lt;p&gt;We now know what tools we need for our project so let's get our development
environment set up. Go to the terminal (or Command Prompt on Windows) and
change into the directory where you want to store this project. Within
that directory, create a new virtualenv to isolate our application
dependencies from other Python projects.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;virtualenv starterbot
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Activate the virtualenv:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;source starterbot/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Your prompt should now look like the one in this screenshot.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160604-simple-python-slack-bot/virtualenv-activate.png" width="100%" class="technical-diagram img-rounded" alt="Command prompt with starterbot's virtualenv activated."&gt;&lt;/p&gt;
&lt;p&gt;The official &lt;code&gt;slackclient&lt;/code&gt; API helper library built by Slack can send and
receive messages from a Slack channel. Install the slackclient library with
the &lt;code&gt;pip&lt;/code&gt; command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install slackclient==1.3.2
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;When &lt;code&gt;pip&lt;/code&gt; is finished you should see output like this and you'll be
back at the prompt.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160604-simple-python-slack-bot/pip-install-slackclient.png" width="100%" class="technical-diagram img-rounded" alt="Output from using the pip install slackclient command with a virtualenv activated."&gt;&lt;/p&gt;
&lt;p&gt;We also need to &lt;a href="https://api.slack.com/apps/new"&gt;create a Slack App&lt;/a&gt; to recieve
an API token for your bot. Use "Starter Bot" as your App name. If you are signed
into more than one workspace, pick a Development Workspace from the dropdown.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160604-simple-python-slack-bot/create-slack-app.png" width="100%" class="technical-diagram img-rounded" alt="Create a Slack App form filled"&gt;&lt;/p&gt;
&lt;p&gt;After submitting the form, keep the app configuration page open.&lt;/p&gt;
&lt;h2&gt;Slack APIs and App Configuration&lt;/h2&gt;
&lt;p&gt;We want our Starter Bot to appear like any other user in your team - it will
participate in conversations inside channels, groups, and DMs. In a Slack
App, this is called a &lt;a href="https://api.slack.com/bot-users"&gt;bot user&lt;/a&gt;, which
we set up by choosing "Bot Users" under the "Features" section. After
clicking "Add a Bot User", you should choose a display name, choose a
default username, and save your choices by clicking "Add Bot User". You'll
end up with a page that looks like the following:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160604-simple-python-slack-bot/add-bot-user.png" width="100%" class="technical-diagram img-rounded" alt="Added a bot user to the Slack App"&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;slackclient&lt;/code&gt; library makes it simple to use Slack's
&lt;a href="https://api.slack.com/rtm"&gt;RTM API&lt;/a&gt; and &lt;a href="https://api.slack.com/web"&gt;Web API&lt;/a&gt;.
We'll use both to implement Starter Bot, and they each require authentication.
Conveniently, the bot user we created earlier can be used to authenticate for
both APIs.&lt;/p&gt;
&lt;p&gt;Click on the "Install App" under the "Settings" section. The button on this page
will install the App into our Development Workspace. Once the App is installed,
it displays a &lt;em&gt;bot user oauth access token&lt;/em&gt; for authentication as the bot user.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160604-simple-python-slack-bot/copy-bot-access-token.png" width="100%" class="technical-diagram img-rounded" alt="After installing on the development workspace, you can copy the bot user oauth access token"&gt;&lt;/p&gt;
&lt;p&gt;A common practice for Python developers is to export secret tokens as
environment variables. Back in your terminal, export the Slack token with the
name &lt;code&gt;SLACK_BOT_TOKEN&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="n"&gt;SLACK_BOT_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;your bot user access token here&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Nice, now we are authorized to use the Slack RTM and Web APIs as a bot user.&lt;/p&gt;
&lt;h2&gt;Coding Our Starter Bot&lt;/h2&gt;
&lt;p&gt;We've got everything we need to write the Starter Bot code. Create a new file
named &lt;code&gt;starterbot.py&lt;/code&gt; and include the following code in it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;slackclient&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SlackClient&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;With our dependencies imported we can use them to obtain the environment
variable values and then instantiate the Slack client.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;instantiate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Slack&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;slack_client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SlackClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SLACK_BOT_TOKEN&amp;#39;&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="n"&gt;starterbot&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;Slack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;assigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;after&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;starts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;up&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;starterbot_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;None&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="n"&gt;constants&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;RTM_READ_DELAY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&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="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;second&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;between&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;reading&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RTM&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;EXAMPLE_COMMAND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;do&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;MENTION_REGEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;^&amp;lt;@(|[WU].+?)&amp;gt;(.*)&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The code instantiates the &lt;code&gt;SlackClient&lt;/code&gt; client with our &lt;code&gt;SLACK_BOT_TOKEN&lt;/code&gt;
exported as an environment variable. It also declares a variable we can use to
store the Slack user ID of our Starter Bot. A few constants are also declared,
and each of them will be explained as they are used in the code that follows.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;:
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;slack_client&lt;/span&gt;.&lt;span class="nv"&gt;rtm_connect&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;with_team_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;False&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;:
        &lt;span class="nv"&gt;print&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;Starter Bot connected and running!&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
        # &lt;span class="nv"&gt;Read&lt;/span&gt; &lt;span class="nv"&gt;bot&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;s user ID by calling Web API method `auth.test`&lt;/span&gt;
        &lt;span class="nv"&gt;starterbot_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;slack_client&lt;/span&gt;.&lt;span class="nv"&gt;api_call&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;auth.test&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;[&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;user_id&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;]
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="nv"&gt;True&lt;/span&gt;:
            &lt;span class="nv"&gt;command&lt;/span&gt;, &lt;span class="nv"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;parse_bot_commands&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;slack_client&lt;/span&gt;.&lt;span class="nv"&gt;rtm_read&lt;/span&gt;&lt;span class="ss"&gt;())&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;command&lt;/span&gt;:
                &lt;span class="nv"&gt;handle_command&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;command&lt;/span&gt;, &lt;span class="nv"&gt;channel&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
            &lt;span class="nv"&gt;time&lt;/span&gt;.&lt;span class="nv"&gt;sleep&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;RTM_READ_DELAY&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;:
        &lt;span class="nv"&gt;print&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;Connection failed. Exception traceback printed above.&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The Slack client connects to the Slack RTM API. Once it's connected, it calls a
Web API method (&lt;a href="https://api.slack.com/methods/auth.test"&gt;&lt;code&gt;auth.test&lt;/code&gt;&lt;/a&gt;) to find
Starter Bot's user ID.&lt;/p&gt;
&lt;p&gt;Each bot user has a user ID for each workspace the Slack App is installed
within. Storing this user ID will help the program understand if someone has
mentioned the bot in a message.&lt;/p&gt;
&lt;p&gt;Next, the program enters an infinite loop, where each time the loop runs the
client recieves any events that arrived from Slack's RTM API. Notice that
before the loop ends, the program pauses for one second so that it doesn't loop
too fast and waste your CPU time.&lt;/p&gt;
&lt;p&gt;For each event that is read, the &lt;code&gt;parse_bot_commands()&lt;/code&gt; function determines if
the event contains a command for Starter Bot. If it does, then &lt;code&gt;command&lt;/code&gt; will
contain a value and the &lt;code&gt;handle_command()&lt;/code&gt; function determines what
to do with the command.&lt;/p&gt;
&lt;p&gt;We've laid the groundwork for processing Slack events and calling Slack methods
in the program. Next, add three new functions above the previous snippet to
complete handling commands:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;def&lt;/span&gt; &lt;span class="nv"&gt;parse_bot_commands&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;slack_events&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;:
    &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="nv"&gt;Parses&lt;/span&gt; &lt;span class="nv"&gt;a&lt;/span&gt; &lt;span class="nv"&gt;list&lt;/span&gt; &lt;span class="nv"&gt;of&lt;/span&gt; &lt;span class="nv"&gt;events&lt;/span&gt; &lt;span class="nv"&gt;coming&lt;/span&gt; &lt;span class="nv"&gt;from&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;Slack&lt;/span&gt; &lt;span class="nv"&gt;RTM&lt;/span&gt; &lt;span class="nv"&gt;API&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;find&lt;/span&gt; &lt;span class="nv"&gt;bot&lt;/span&gt; &lt;span class="nv"&gt;commands&lt;/span&gt;.
        &lt;span class="k"&gt;If&lt;/span&gt; &lt;span class="nv"&gt;a&lt;/span&gt; &lt;span class="nv"&gt;bot&lt;/span&gt; &lt;span class="nv"&gt;command&lt;/span&gt; &lt;span class="nv"&gt;is&lt;/span&gt; &lt;span class="nv"&gt;found&lt;/span&gt;, &lt;span class="nv"&gt;this&lt;/span&gt; &lt;span class="nv"&gt;function&lt;/span&gt; &lt;span class="nv"&gt;returns&lt;/span&gt; &lt;span class="nv"&gt;a&lt;/span&gt; &lt;span class="nv"&gt;tuple&lt;/span&gt; &lt;span class="nv"&gt;of&lt;/span&gt; &lt;span class="nv"&gt;command&lt;/span&gt; &lt;span class="nv"&gt;and&lt;/span&gt; &lt;span class="nv"&gt;channel&lt;/span&gt;.
        &lt;span class="k"&gt;If&lt;/span&gt; &lt;span class="nv"&gt;its&lt;/span&gt; &lt;span class="nv"&gt;not&lt;/span&gt; &lt;span class="nv"&gt;found&lt;/span&gt;, &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="nv"&gt;this&lt;/span&gt; &lt;span class="nv"&gt;function&lt;/span&gt; &lt;span class="nv"&gt;returns&lt;/span&gt; &lt;span class="nv"&gt;None&lt;/span&gt;, &lt;span class="nv"&gt;None&lt;/span&gt;.
    &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;event&lt;/span&gt; &lt;span class="nv"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;slack_events&lt;/span&gt;:
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;event&lt;/span&gt;[&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;] &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="nv"&gt;and&lt;/span&gt; &lt;span class="nv"&gt;not&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;subtype&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="nv"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;event&lt;/span&gt;:
            &lt;span class="nv"&gt;user_id&lt;/span&gt;, &lt;span class="nv"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;parse_direct_mention&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;event&lt;/span&gt;[&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;]&lt;span class="ss"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nv"&gt;starterbot_id&lt;/span&gt;:
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt;, &lt;span class="nv"&gt;event&lt;/span&gt;[&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;channel&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;]
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;None&lt;/span&gt;, &lt;span class="nv"&gt;None&lt;/span&gt;

&lt;span class="nv"&gt;def&lt;/span&gt; &lt;span class="nv"&gt;parse_direct_mention&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;message_text&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;:
    &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="nv"&gt;Finds&lt;/span&gt; &lt;span class="nv"&gt;a&lt;/span&gt; &lt;span class="nv"&gt;direct&lt;/span&gt; &lt;span class="nv"&gt;mention&lt;/span&gt; &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt; &lt;span class="nv"&gt;mention&lt;/span&gt; &lt;span class="nv"&gt;that&lt;/span&gt; &lt;span class="nv"&gt;is&lt;/span&gt; &lt;span class="nv"&gt;at&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;beginning&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt; &lt;span class="nv"&gt;text&lt;/span&gt;
        &lt;span class="nv"&gt;and&lt;/span&gt; &lt;span class="nv"&gt;returns&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;user&lt;/span&gt; &lt;span class="nv"&gt;ID&lt;/span&gt; &lt;span class="nv"&gt;which&lt;/span&gt; &lt;span class="nv"&gt;was&lt;/span&gt; &lt;span class="nv"&gt;mentioned&lt;/span&gt;. &lt;span class="k"&gt;If&lt;/span&gt; &lt;span class="nv"&gt;there&lt;/span&gt; &lt;span class="nv"&gt;is&lt;/span&gt; &lt;span class="nv"&gt;no&lt;/span&gt; &lt;span class="nv"&gt;direct&lt;/span&gt; &lt;span class="nv"&gt;mention&lt;/span&gt;, &lt;span class="nv"&gt;returns&lt;/span&gt; &lt;span class="nv"&gt;None&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="nv"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;re&lt;/span&gt;.&lt;span class="nv"&gt;search&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;MENTION_REGEX&lt;/span&gt;, &lt;span class="nv"&gt;message_text&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
    # &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;first&lt;/span&gt; &lt;span class="nv"&gt;group&lt;/span&gt; &lt;span class="nv"&gt;contains&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;username&lt;/span&gt;, &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;second&lt;/span&gt; &lt;span class="nv"&gt;group&lt;/span&gt; &lt;span class="nv"&gt;contains&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;remaining&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;matches&lt;/span&gt;.&lt;span class="nv"&gt;group&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;, &lt;span class="nv"&gt;matches&lt;/span&gt;.&lt;span class="nv"&gt;group&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;.&lt;span class="nv"&gt;strip&lt;/span&gt;&lt;span class="ss"&gt;())&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;matches&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;None&lt;/span&gt;, &lt;span class="nv"&gt;None&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;def&lt;/span&gt; &lt;span class="nv"&gt;handle_command&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;command&lt;/span&gt;, &lt;span class="nv"&gt;channel&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;:
    &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="nv"&gt;Executes&lt;/span&gt; &lt;span class="nv"&gt;bot&lt;/span&gt; &lt;span class="nv"&gt;command&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;command&lt;/span&gt; &lt;span class="nv"&gt;is&lt;/span&gt; &lt;span class="nv"&gt;known&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    # &lt;span class="nv"&gt;Default&lt;/span&gt; &lt;span class="nv"&gt;response&lt;/span&gt; &lt;span class="nv"&gt;is&lt;/span&gt; &lt;span class="nv"&gt;help&lt;/span&gt; &lt;span class="nv"&gt;text&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;user&lt;/span&gt;
    &lt;span class="nv"&gt;default_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;Not sure what you mean. Try *{}*.&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;.&lt;span class="nv"&gt;format&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;EXAMPLE_COMMAND&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;

    # &lt;span class="nv"&gt;Finds&lt;/span&gt; &lt;span class="nv"&gt;and&lt;/span&gt; &lt;span class="nv"&gt;executes&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;given&lt;/span&gt; &lt;span class="nv"&gt;command&lt;/span&gt;, &lt;span class="nv"&gt;filling&lt;/span&gt; &lt;span class="nv"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;response&lt;/span&gt;
    &lt;span class="nv"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;None&lt;/span&gt;
    # &lt;span class="nv"&gt;This&lt;/span&gt; &lt;span class="nv"&gt;is&lt;/span&gt; &lt;span class="nv"&gt;where&lt;/span&gt; &lt;span class="nv"&gt;you&lt;/span&gt; &lt;span class="nv"&gt;start&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;implement&lt;/span&gt; &lt;span class="nv"&gt;more&lt;/span&gt; &lt;span class="nv"&gt;commands&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;command&lt;/span&gt;.&lt;span class="nv"&gt;startswith&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;EXAMPLE_COMMAND&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;:
        &lt;span class="nv"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;Sure...write some more code then I can do that!&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

    # &lt;span class="nv"&gt;Sends&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;response&lt;/span&gt; &lt;span class="nv"&gt;back&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;channel&lt;/span&gt;
    &lt;span class="nv"&gt;slack_client&lt;/span&gt;.&lt;span class="nv"&gt;api_call&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="s"&gt;chat.postMessage&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;,
        &lt;span class="nv"&gt;channel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;channel&lt;/span&gt;,
        &lt;span class="nv"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;response&lt;/span&gt; &lt;span class="nv"&gt;or&lt;/span&gt; &lt;span class="nv"&gt;default_response&lt;/span&gt;
    &lt;span class="ss"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;parse_bot_commands()&lt;/code&gt; function takes events from Slack and determines
if they are commands directed at Starter Bot. There are many
&lt;a href="https://api.slack.com/events"&gt;event types&lt;/a&gt; that our bot will encounter, but to
find commands we only want to consider
&lt;a href="https://api.slack.com/events/message"&gt;message events&lt;/a&gt;. Message events also have
subtypes, but the commands we want to find won't have any subtype defined. The
function filters out uninteresting events by checking these properties. Now we
know the event represents a message with some text, but we want to find out
if Starter Bot is being mentioned in the text. The &lt;code&gt;parse_direct_mention()&lt;/code&gt;
function will figure out of the message text starts with a mention, and then
we compare that to the user ID we stored earlier for Starter Bot. If they are
the same, then we know this is a bot command, and return the command text with
the channel ID.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;parse_direct_mentions()&lt;/code&gt; function uses a regular expression to determine
if a user is being mentioned &lt;em&gt;at the beginning&lt;/em&gt; of the message. It returns
the user ID and the remaining message (and &lt;code&gt;None, None&lt;/code&gt; if no mention was
found).&lt;/p&gt;
&lt;p&gt;The last function, &lt;code&gt;handle_command()&lt;/code&gt; is where in the future you'll add all the
interesting commands, humor, and personality for Starter Bot. For now, it has
just one example command: &lt;em&gt;do&lt;/em&gt;. If the command starts with a known command, it
will have an appropriate response. If not, a default response is used. The
response is sent back to Slack by calling the
&lt;a href="https://api.slack.com/methods/chat.postMessage"&gt;&lt;code&gt;chat.postMessage&lt;/code&gt;&lt;/a&gt; Web API
method with the channel.&lt;/p&gt;
&lt;p&gt;Here is how the entire program should look when it's all put together
(you can also
&lt;a href="https://github.com/mattmakai/slack-starterbot/blob/master/starterbot.py"&gt;view the file in GitHub&lt;/a&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;slackclient&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SlackClient&lt;/span&gt;


&lt;span class="c1"&gt;# instantiate Slack client&lt;/span&gt;
&lt;span class="n"&gt;slack_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SlackClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SLACK_BOT_TOKEN&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;# starterbot&amp;#39;s user ID in Slack: value is assigned after the bot starts up&lt;/span&gt;
&lt;span class="n"&gt;starterbot_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

&lt;span class="c1"&gt;# constants&lt;/span&gt;
&lt;span class="n"&gt;RTM_READ_DELAY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="c1"&gt;# 1 second delay between reading from RTM&lt;/span&gt;
&lt;span class="n"&gt;EXAMPLE_COMMAND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;do&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;MENTION_REGEX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;^&amp;lt;@(|[WU].+?)&amp;gt;(.*)&amp;quot;&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_bot_commands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slack_events&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;        Parses a list of events coming from the Slack RTM API to find bot commands.&lt;/span&gt;
&lt;span class="sd"&gt;        If a bot command is found, this function returns a tuple of command and channel.&lt;/span&gt;
&lt;span class="sd"&gt;        If its not found, then this function returns None, None.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;slack_events&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;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;message&amp;quot;&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;subtype&amp;quot;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parse_direct_mention&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text&amp;quot;&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;user_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;starterbot_id&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;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;channel&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_direct_mention&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message_text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;        Finds a direct mention (a mention that is at the beginning) in message text&lt;/span&gt;
&lt;span class="sd"&gt;        and returns the user ID which was mentioned. If there is no direct mention, returns None&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MENTION_REGEX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# the first group contains the username, the second group contains the remaining message&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&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;matches&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&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;handle_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;        Executes bot command if the command is known&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="c1"&gt;# Default response is help text for the user&lt;/span&gt;
    &lt;span class="n"&gt;default_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Not sure what you mean. Try *&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;*.&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EXAMPLE_COMMAND&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Finds and executes the given command, filling in response&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
    &lt;span class="c1"&gt;# This is where you start to implement more commands!&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EXAMPLE_COMMAND&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Sure...write some more code then I can do that!&amp;quot;&lt;/span&gt;

    &lt;span class="c1"&gt;# Sends the response back to the channel&lt;/span&gt;
    &lt;span class="n"&gt;slack_client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;chat.postMessage&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;default_response&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&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;slack_client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rtm_connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;with_team_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Starter Bot connected and running!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# Read bot&amp;#39;s user ID by calling Web API method `auth.test`&lt;/span&gt;
        &lt;span class="n"&gt;starterbot_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;slack_client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;auth.test&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;user_id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parse_bot_commands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slack_client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rtm_read&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;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;handle_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RTM_READ_DELAY&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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Connection failed. Exception traceback printed above.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now that all of our code is in place we can run our Starter Bot on the
command line with the &lt;code&gt;python starterbot.py&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160604-simple-python-slack-bot/starterbot-running.png" width="100%" class="technical-diagram img-rounded" alt="Console output when the StarterBot is running and connected to the API."&gt;&lt;/p&gt;
&lt;p&gt;In Slack, create a new channel and invite Starter Bot or invite it to an
existing channel.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160604-simple-python-slack-bot/create-channel.png" width="100%" class="technical-diagram img-rounded" alt="In the Slack user interface create a new channel and invite StarterBot."&gt;&lt;/p&gt;
&lt;p&gt;Now start giving Starter Bot commands in your channel.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160604-simple-python-slack-bot/working-starterbot.png" width="100%" class="technical-diagram img-rounded" alt="Give StarterBot commands in your Slack channel."&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Additional Note:&lt;/strong&gt;&lt;/em&gt; Currently there's an &lt;a href="https://github.com/slackapi/python-slackclient/issues/334"&gt;issue&lt;/a&gt; with the &lt;code&gt;websocket&lt;/code&gt; package and the CA certificate it uses, so if you encounter an error like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;...
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1045)
...
slackclient.server.SlackConnectionError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1045)
Connection failed. Exception traceback printed above.
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;There are a couple of things that can be done:
1. Downgrading the websocket-client library to &lt;code&gt;0.47.0&lt;/code&gt;
2. Or, download the certificate (&lt;code&gt;wget https://www.tbs-certificats.com/issuerdata/DigiCertGlobalRootCA.crt&lt;/code&gt;), then set the environment variable &lt;code&gt;export WEBSOCKET_CLIENT_CA_BUNDLE=DigiCertGlobalRootCA.crt&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;Alright, now you've got a simple Starter Bot with a bunch of places in the
code you can add whatever features you want to build.&lt;/p&gt;
&lt;p&gt;There is a whole lot more that could be done using the Slack RTM API and Python.
Check out these posts to learn what you could do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Attach a persistent &lt;a href="/databases.html"&gt;relational database&lt;/a&gt; or
  &lt;a href="/no-sql-datastore.html"&gt;NoSQL back-end&lt;/a&gt; such as
  &lt;a href="/postgresql.html"&gt;PostgreSQL&lt;/a&gt;, &lt;a href="/mysql.html"&gt;MySQL&lt;/a&gt; or &lt;a href="/sqlite.html"&gt;SQLite&lt;/a&gt;
  to save and retrieve user data&lt;/li&gt;
&lt;li&gt;Add another channel to interact with the bot
  &lt;a href="https://www.twilio.com/blog/2016/05/build-sms-slack-bot-python.html"&gt;via SMS&lt;/a&gt;
  or
  &lt;a href="https://www.twilio.com/blog/2016/05/add-phone-calling-slack-python.html"&gt;phone calls&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/api-integration.html"&gt;Integrate other web APIs&lt;/a&gt; such as
  &lt;a href="https://developer.github.com/v3/"&gt;GitHub&lt;/a&gt; or &lt;a href="/twilio.html"&gt;Twilio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Explore other &lt;a href="https://api.slack.com"&gt;Slack Platform APIs&lt;/a&gt; and the &lt;a href="https://medium.com/slack-developer-blog/getting-started-with-slacks-apis-f930c73fc889"&gt;reasons you might use one over another&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Build an &lt;a href="https://github.com/slackapi/Slack-Python-Onboarding-Tutorial"&gt;onboarding bot using the Slack Events API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Questions? Contact me via Twitter
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;. I'm also on GitHub with
the username &lt;a href="https://github.com/mattmakai"&gt;mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See something wrong in this post? Fork
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/160604-build-first-slack-bot-python.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Wed, 13 Dec 2017 00:00:00 -0500</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2016-06-04:blog/build-first-slack-bot-python.html</guid></item><item><title>Responding to SMS Text Messages with Python &amp; Flask</title><link>https://www.fullstackpython.com/blog/respond-sms-text-messages-python-flask.html</link><description>&lt;p&gt;Short Message Service (SMS) text messages are 
&lt;a href="/blog/send-sms-text-messages-python.html"&gt;easy to send from Python applications&lt;/a&gt; 
with a
&lt;a href="/application-programming-interfaces.html"&gt;web application programming interface (API)&lt;/a&gt;. 
Flask applications can also receive incoming text messages and respond
back to the sender with just a few lines of Python code.&lt;/p&gt;
&lt;h2&gt;Tools We Need&lt;/h2&gt;
&lt;p&gt;This tutorial is fine for both Python 2 and 3. Make sure you have one of 
those two versions installed on your system.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Either &lt;a href="/python-2-or-3.html"&gt;Python 2 or 3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pip.pypa.io/en/stable/"&gt;pip&lt;/a&gt; and 
  &lt;a href="https://virtualenv.pypa.io/en/latest/"&gt;virtualenv&lt;/a&gt; to handle
  &lt;a href="/application-dependencies.html"&gt;application dependencies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The &lt;a href="/flask.html"&gt;Flask&lt;/a&gt; micro web framework&lt;/li&gt;
&lt;li&gt;A free &lt;a href="www.twilio.com/referral/w9pugq"&gt;Twilio account&lt;/a&gt; to use their 
  &lt;a href="https://www.twilio.com/docs/api/rest/sending-messages"&gt;SMS web API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Open source 
  &lt;a href="https://pypi.org/project/twilio"&gt;Twilio Python helper library&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ngrok.com/"&gt;Ngrok&lt;/a&gt; for localhost tunneling to our Flask 
  application while it's running on our local development environment&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you need assistance getting pip and virtualenv installed, take a look at 
the first few steps in the 
&lt;a href="/blog/python-3-flask-green-unicorn-ubuntu-1604-xenial-xerus.html"&gt;how to set up Python 3, Flask and Green Unicorn on Ubuntu 16.04 LTS&lt;/a&gt;
guide, which shows how to install system packages for those tools.&lt;/p&gt;
&lt;h2&gt;Installing Our Dependencies&lt;/h2&gt;
&lt;p&gt;Our code will use a helper library to make it easier to respond to text 
messages from Python. The helper library dependency along with the Flask
code library can be installed from &lt;a href="https://pypi.python.org/pypi"&gt;PyPI&lt;/a&gt; into 
a virtualenv. In your terminal use the following command to generate a new 
virtualenv. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;virtualenv respondsms
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Activate the virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;source respondsms/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The command prompt will change after we properly activate the virtualenv
to something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160530-respond-sms-python-flask/activate-virtualenv.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Install Flask and the Twilio Python helper library into the virtualenv with
the &lt;code&gt;pip&lt;/code&gt; command. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install flask twilio==5.7.0
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The dependencies are installed so that we can use it with our Python code.
Now we can write our Python application.&lt;/p&gt;
&lt;h2&gt;Building Our Flask Web App&lt;/h2&gt;
&lt;p&gt;Our Flask application will have two routes: one to make sure the web app
is running and another that handles incoming HTTP POST requests. Create
a new file named &lt;code&gt;app.py&lt;/code&gt; in your home directory or where you choose to
store your Python project files.&lt;/p&gt;
&lt;p&gt;Within &lt;code&gt;app.py&lt;/code&gt; write the following code. You can also see 
&lt;a href="https://gist.github.com/mattmakai/8ab434ccb604d3ba5bde817a183e0bde"&gt;this code in a GitHub Gist&lt;/a&gt; 
if that's easier to copy and paste.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&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;request&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;twilio&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;twiml&lt;/span&gt;


&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&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_app&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# returns a simple string stating the app is working&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;It works!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/twilio&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;POST&amp;quot;&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;inbound_sms&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;twiml&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# we get the SMS message from the request. we could also get the &lt;/span&gt;
    &lt;span class="c1"&gt;# &amp;quot;To&amp;quot; and the &amp;quot;From&amp;quot; phone number as well&lt;/span&gt;
    &lt;span class="n"&gt;inbound_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Body&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# we can now use the incoming message text in our Python application&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;inbound_message&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Hello&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello back to you!&amp;quot;&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;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hi! Not quite sure what you meant, but okay.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# we return back the mimetype because Twilio needs an XML response&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Response&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;response&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;mimetype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;application/xml&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The inline comments on the lines starting with &lt;code&gt;#&lt;/code&gt; explain what the lines
below them do. Flask applications define URL routes with the &lt;code&gt;@app.route&lt;/code&gt;
decorator. Our application needs two routes therefore we have two of those
decorators defined.&lt;/p&gt;
&lt;p&gt;Give the application a try by running it with &lt;code&gt;python app.py&lt;/code&gt;. If you have
trouble running the program, make sure your virtualenv is still active so
that the application can use the Flask and Twilio code libraries we installed
earlier.&lt;/p&gt;
&lt;p&gt;Open a web browser and go to localhost:5000 (or 127.0.0.1:5000). We should
see "It works!" on the screen.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160530-respond-sms-python-flask/app-local.jpg" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;There is one problem with our application running on our local development
environment: there's no way for our server to receive HTTP POST requests 
unless we use a localhost tunnel.&lt;/p&gt;
&lt;h2&gt;Localhost Tunneling with Ngrok&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://ngrok.com"&gt;Ngrok&lt;/a&gt; provides a localhost tunnel so that outside
services can connect to a server running in your local development 
environment. Download and install Ngrok.&lt;/p&gt;
&lt;p&gt;We can now run Ngrok locally and connect our Flask app running on port 5000. 
Within the directory where you extracted Ngrok, run this command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;./ngrok http 5000
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img src="/img/160530-respond-sms-python-flask/start-ngrok.jpg" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Awesome, now we can use that Ngrok Forwarding URL to access our application
from any machine that has an internet connection. Replace the URL in the
web browser with your own Forwarding URL, like I did in this screenshot.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160530-respond-sms-python-flask/access-ngrok.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;We just need a phone number that'll hit our application with a POST request
to respond to text messages.&lt;/p&gt;
&lt;h2&gt;Obtaining Our Phone Number&lt;/h2&gt;
&lt;p&gt;We can use our Flask application's route to respond to incoming web API 
requests based on incoming SMS messages to a Twilio phone number. Go to the
&lt;a href="www.twilio.com/referral/w9pugq"&gt;Twilio website and sign up for a free trial account&lt;/a&gt;
to use their API. If you already have a Twilio account then sign into your 
existing account.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160530-respond-sms-python-flask/try-twilio.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;The Twilio trial account allows you to send and receive text messages to 
your own validated phone number. To send and respond to SMS to and from any 
phone number then you need to upgrade your account. Trial accounts are 
great for initial development before your application goes live.&lt;/p&gt;
&lt;p&gt;When you sign up, you receive a free Twilio phone number. We can
configure that phone number to forward the SMS information to our web 
application by setting up the response webhook.&lt;/p&gt;
&lt;p&gt;Go to the 
&lt;a href="https://www.twilio.com/console/phone-numbers"&gt;manage phone numbers screen&lt;/a&gt; 
and click on the phone number you want to configure for responding to 
inbound text messages.&lt;/p&gt;
&lt;p&gt;Scroll down to near the bottom of the page and look for the "Messaging"
header. Modify the "A Message Comes in" text box so that it has your
ngrok Forwarding URL plus the "/twilio" route, as shown in this screenshot.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160530-respond-sms-python-flask/number-configuration.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Now press the red "Save" button at the bottom to make our changes take
effect.&lt;/p&gt;
&lt;p&gt;Our application is ready to go - time to give our phone number a try! 
Send "Hello" or whatever text you want to your phone number. Here is what 
the result looks like on my iPhone.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160530-respond-sms-python-flask/success.jpg" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;This simple Flask application is a good start to build more complicated
responses such as
&lt;a href="https://www.twilio.com/blog/2014/06/using-natural-language-processing-for-better-sms-interfaces-using-twilio-and-pythons-textblob.html"&gt;adding natural language processing&lt;/a&gt;,
&lt;a href="https://www.twilio.com/blog/2016/05/build-sms-slack-bot-python.html"&gt;building SMS Slack bots&lt;/a&gt;
or 
&lt;a href="https://www.twilio.com/blog/2015/08/romram-hacking-building-an-sms-powered-game-genie-with-lua-and-python.html"&gt;coding SMS-powered NES Game Genies&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;What's next?&lt;/h2&gt;
&lt;p&gt;Sweet, now our Flask web app automatically responds to incoming SMS text
messages! It's pretty crazy to think that entire businesses such as 
&lt;a href="http://techcrunch.com/2016/03/07/superphone/"&gt;SuperPhone&lt;/a&gt; and 
&lt;a href="https://www.remind.com/"&gt;Remind&lt;/a&gt; are built off code that started out very
similar to the code we just wrote.&lt;/p&gt;
&lt;p&gt;Questions? Contact me via Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;. I'm also on GitHub with
the username &lt;a href="https://github.com/mattmakai"&gt;mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See something wrong in this post? Fork 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/160530-respond-sms-text-messages-python-flask.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Wed, 05 Aug 2020 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2016-05-30:blog/respond-sms-text-messages-python-flask.html</guid></item><item><title>How to Install and Use MySQL on Ubuntu 16.04</title><link>https://www.fullstackpython.com/blog/install-mysql-ubuntu-1604.html</link><description>&lt;p&gt;&lt;a href="/mysql.html"&gt;MySQL&lt;/a&gt; is a common open source 
&lt;a href="/databases.html"&gt;relational database&lt;/a&gt; for creating, reading, updating 
and deleting data in &lt;a href="/web-frameworks.html"&gt;Python web applications&lt;/a&gt;.
Let's learn how to install MySQL on &lt;a href="/ubuntu.html"&gt;Ubuntu 16.04&lt;/a&gt; and then 
run a few SQL queries within the command line client.&lt;/p&gt;
&lt;p&gt;We will not go over connecting via Python applications using
&lt;a href="/object-relational-mappers-orms.html"&gt;object-relational mappers (ORMs)&lt;/a&gt; 
but these steps can be used as a prerequisite to working with an ORM such 
as SQLAlchemy or Peewee.&lt;/p&gt;
&lt;h2&gt;Tools We Need&lt;/h2&gt;
&lt;p&gt;In this tutorial we'll use the following components:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://releases.ubuntu.com/16.04/"&gt;Ubuntu 16.04.2&lt;/a&gt; (this tutorial 
  should also work on other Ubuntu versions)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://dev.mysql.com/doc/"&gt;MySQL&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Install MySQL&lt;/h2&gt;
&lt;p&gt;We can install MySQL by using the &lt;code&gt;apt&lt;/code&gt; package manager. First make sure
your packages list are up to date. Open the terminal and run this &lt;code&gt;apt&lt;/code&gt;
command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo apt-get update
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We need to install the &lt;code&gt;mysql-server&lt;/code&gt; package, which downloads the required
files, configures the initial database set up and handles running MySQL
as a system service. Run this &lt;code&gt;apt&lt;/code&gt; command to get the process started.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo apt-get install mysql-server
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Enter 'y' when prompted with whether or not you want to install the
new package.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160528-mysql-ubuntu-1604/apt-install-prompt.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;An administrative screen asking for a new root password will appear in the 
middle of the package installation process. Enter your chosen new password 
twice and the installation will continue.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160528-mysql-ubuntu-1604/new-root-password.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;In a moment the installation will finish and you'll be back at the command
prompt.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160528-mysql-ubuntu-1604/apt-finished.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;MySQL is now installed with a root user. However, we do not want to have our
applications connect to the database with that user, so next we will 
create a new non-root user.&lt;/p&gt;
&lt;h2&gt;Securing MySQL&lt;/h2&gt;
&lt;p&gt;MySQL is installed with a basic configuration meant for development and testing
purposes. However, the configuration is not secure for production enviroments,
therefore it comes with a utility to handle basic security. Run the
following command and answer the questions based on your environment 
requirements.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo mysql_secure_installation
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;When you finish running the script you should see the following output and
be back at the command prompt.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160528-mysql-ubuntu-1604/mysql-secure-installation.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Our MySQL instance has basic security in place but we need to create a 
non-root user for applications to interact with the database.&lt;/p&gt;
&lt;h2&gt;Creating MySQL Users&lt;/h2&gt;
&lt;p&gt;To create a non-root user, connect to the MySQL instance with the 
&lt;code&gt;mysql&lt;/code&gt; command line client.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;mysql -u root -p
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now use the &lt;code&gt;CREATE USER&lt;/code&gt; command to generate a new user. Make sure to
change "mynewuser" and "goodPassword" with your own values.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;CREATE USER &amp;#39;mynewuser&amp;#39;@&amp;#39;localhost&amp;#39; IDENTIFIED BY &amp;#39;goodPassword&amp;#39;;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;No output after the command is good - that means the command succeeded.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160528-mysql-ubuntu-1604/create-user.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;We need to apply privileges to the new user so it can handle basic database
operations. Again, make sure to replace the default username in this command
with your new username.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;GRANT ALL PRIVILEGES ON * . * TO &amp;#39;mynewuser&amp;#39;@&amp;#39;localhost&amp;#39;;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img src="/img/160528-mysql-ubuntu-1604/grant-all.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;It's a good idea to reload the privileges to make sure our new user's
permissions are in place.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;FLUSH PRIVILEGES;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now that our permissions are reloaded we can connect with the new user.&lt;/p&gt;
&lt;h2&gt;New User Connection&lt;/h2&gt;
&lt;p&gt;We're set to connect to the database with our new user. Exit the MySQL
client with "Ctrl-d". Reconnect using a slightly different command than
we used earlier.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;mysql -u mynewuser -p
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img src="/img/160528-mysql-ubuntu-1604/mysql-new-user.png" width="100%" class="technical-diagram img-rounded" alt="Connect to MySQL as the new user we just created."&gt;&lt;/p&gt;
&lt;p&gt;Create a new database with the &lt;code&gt;CREATE DATABASE&lt;/code&gt; command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;CREATE DATABASE fullstackpython;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img src="/img/160528-mysql-ubuntu-1604/create-database.png" width="100%" class="technical-diagram img-rounded" alt="Create a new MySQL database with our new user."&gt;&lt;/p&gt;
&lt;p&gt;Connect to the new database with the &lt;code&gt;USE&lt;/code&gt; command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;use fullstackpython;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img src="/img/160528-mysql-ubuntu-1604/use-command.png" width="100%" class="technical-diagram img-rounded" alt="Connect to the newly-created database with the USE command."&gt;&lt;/p&gt;
&lt;p&gt;Create a simple new table with the &lt;code&gt;CREATE TABLE&lt;/code&gt; command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;CREATE TABLE pages (name VARCHAR(50), url VARCHAR(1024));
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Our table is ready to go - we can interact with it using the 
&lt;code&gt;SELECT&lt;/code&gt;, &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt; and &lt;code&gt;DELETE&lt;/code&gt; SQL commands.&lt;/p&gt;
&lt;h2&gt;What's next?&lt;/h2&gt;
&lt;p&gt;We now have our MySQL instance installed and ready for interaction.
Take a look at the &lt;a href="/mysql.html"&gt;MySQL&lt;/a&gt;, 
&lt;a href="/databases.html"&gt;relational databases&lt;/a&gt; and 
&lt;a href="/object-relational-mappers-orms.html"&gt;object-relational mappers (ORMs)&lt;/a&gt;
pages for more tutorials.&lt;/p&gt;
&lt;p&gt;Questions? Tweet &lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or post a message on the 
&lt;a href="https://www.facebook.com/fullstackpython"&gt;Full Stack Python Facebook page&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;See something wrong in this post? Fork 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/160528-install-mysql-ubuntu-1604.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Fri, 22 Dec 2017 00:00:00 -0500</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2016-05-28:blog/install-mysql-ubuntu-1604.html</guid></item><item><title>Setting up PostgreSQL with Python 3 and psycopg on Ubuntu 16.04</title><link>https://www.fullstackpython.com/blog/postgresql-python-3-psycopg2-ubuntu-1604.html</link><description>&lt;p&gt;&lt;a href="/postgresql.html"&gt;PostgreSQL&lt;/a&gt; is a powerful open source 
&lt;a href="/databases.html"&gt;relational database&lt;/a&gt; frequently used to create, read,
update and delete &lt;a href="/web-frameworks.html"&gt;Python web application&lt;/a&gt; data.
&lt;a href="https://www.psycopg.org/"&gt;Psycopg2&lt;/a&gt; is a PostgreSQL database 
driver that serves as a Python client for access to the PostgreSQL server. 
This post explains how to install PostgreSQL on &lt;a href="/ubuntu.html"&gt;Ubuntu 16.04&lt;/a&gt; 
and run a few basic SQL queries within a Python program.&lt;/p&gt;
&lt;p&gt;We won't cover 
&lt;a href="/object-relational-mappers-orms.html"&gt;object-relational mappers (ORMs)&lt;/a&gt; 
in this tutorial but these steps can be used as a prerequisite to working 
with an ORM such as SQLAlchemy or Peewee.&lt;/p&gt;
&lt;h2&gt;Tools We Need&lt;/h2&gt;
&lt;p&gt;Our walkthrough should work with either &lt;a href="/python-2-or-3.html"&gt;Python 2 or 3&lt;/a&gt; 
although all the steps were tested specifically with Python 3.5. Besides 
the Python interpreter, here are the other components we'll use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://releases.ubuntu.com/16.04/"&gt;Ubuntu 16.04.2&lt;/a&gt; (these 
  steps should also work fine with other Ubuntu versions)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pip.pypa.io/en/stable/"&gt;pip&lt;/a&gt; and 
  &lt;a href="https://virtualenv.pypa.io/en/latest/"&gt;virtualenv&lt;/a&gt; to handle the
  &lt;a href="https://pypi.org/project/psycopg2/2.6.1"&gt;psycopg2&lt;/a&gt; 
  &lt;a href="/application-dependencies.html"&gt;application dependency&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.postgresql.org/"&gt;PostgreSQL&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you aren't sure how to install pip and virtualenv, review the 
first few steps of the 
&lt;a href="/blog/python-3-bottle-gunicorn-ubuntu-1604-xenial-xerus.html"&gt;how to set up Python 3, Bottle and Green Unicorn on Ubuntu 16.04 LTS&lt;/a&gt;
guide.&lt;/p&gt;
&lt;h2&gt;Install PostgreSQL&lt;/h2&gt;
&lt;p&gt;We'll install PostgreSQL via the &lt;code&gt;apt&lt;/code&gt; package manager. There are a few
packages we need since we want to both run PostgreSQL and use the psycopg2
driver with our Python programs. PostgreSQL will also be installed as a
system service so we can start, stop and reload its configuration when
necessary with the &lt;code&gt;service&lt;/code&gt; command. Open the terminal and run: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo apt-get install postgresql libpq-dev postgresql-client postgresql-client-common
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Enter your &lt;code&gt;sudo&lt;/code&gt; password when prompted and enter 'yes' when &lt;code&gt;apt&lt;/code&gt; asks
if you want to install the new packages.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160518-postgresql-ubuntu-1604/apt-get-postgresql.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;After a few moments &lt;code&gt;apt&lt;/code&gt; will finish downloading, installing and 
processing.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160518-postgresql-ubuntu-1604/apt-get-postgresql-done.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;We now have PostgreSQL installed and the PostgreSQL service is running
in the background. However, we need to create a user and a database instance
to really start using it. Use the &lt;code&gt;sudo&lt;/code&gt; command to switch to the new
"postgres" account.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo -i -u postgres
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Within the "postgres" account, create a user from the command line with the
&lt;code&gt;createuser&lt;/code&gt; command. PostgreSQL will prompt you with several questions.
Answer "n" to superuser and "y" to the other questions.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;createuser matt -P --interactive
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img src="/img/160518-postgresql-ubuntu-1604/createuser.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Awesome, now we have a PostgreSQL user that matches our Ubuntu login
account. Exit out of the postgres account by pressing the "Ctrl" key along
with "d" into the shell. We're back in our own user account.&lt;/p&gt;
&lt;p&gt;Create a new database we can use for testing. You can name it "testpython"
or whatever you want for your application.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;createdb testpython
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now we can interact with "testpython" via the PostgreSQL command line tool.&lt;/p&gt;
&lt;h2&gt;Interacting with PostgreSQL&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;psql&lt;/code&gt; command line client is useful for connecting directly to our
PostgreSQL server without any Python code. Try out &lt;code&gt;psql&lt;/code&gt; by using this
command at the prompt: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;psql testpython
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The PostgreSQL client will connect to the localhost server. The client is
now ready for input:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160518-postgresql-ubuntu-1604/postgresql-cli.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Try out PostgreSQL's command prompt a try with commands such as &lt;code&gt;\dt&lt;/code&gt; and
&lt;code&gt;\dd&lt;/code&gt;. We can also run SQL queries such as "SELECT * from testpython", 
although that won't give us back any data yet because we have not inserted
any into the database. A full list of PostgreSQL commands can be 
found in the
&lt;a href="http://www.postgresql.org/docs/9.6/static/app-psql.html"&gt;psql documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Installing psycopg2&lt;/h2&gt;
&lt;p&gt;Now that PostgreSQL is installed and we have a non-superuser account, we
can install the &lt;a href="https://www.psycopg.org/"&gt;psycopg2&lt;/a&gt; package. Let's
figure out where our &lt;code&gt;python3&lt;/code&gt; executable is located, create a virtualenv
with &lt;code&gt;python3&lt;/code&gt;, activate the virtualenv and then install the psycopg2 package
with &lt;code&gt;pip&lt;/code&gt;. Find your &lt;code&gt;python3&lt;/code&gt; executable using the &lt;code&gt;which&lt;/code&gt; command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;which python3
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We will see output like what is in this screenshot.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160518-postgresql-ubuntu-1604/which-python-3.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Create a new virtualenv in either your home directory or wherever you
store your Python virtualenvs. Specify the full path to your &lt;code&gt;python3&lt;/code&gt;
installation. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# specify the system python3 installation
virtualenv --python=/usr/bin/python3 venvs/postgrestest
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Activate the virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;source ~/venvs/postgrestest/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Next we can install the psycopg2 Python package from 
&lt;a href="https://pypi.python.org/pypi"&gt;PyPI&lt;/a&gt; using the &lt;code&gt;pip&lt;/code&gt; command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install psycopg2
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img src="/img/160518-postgresql-ubuntu-1604/pip-install-psycopg2.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Sweet, we've got our PostgreSQL driver installed in our virtualenv! We can 
now test out the installation by writing a few lines of Python code.&lt;/p&gt;
&lt;h2&gt;Using PostgreSQL from Python&lt;/h2&gt;
&lt;p&gt;Launch the Python REPL with the &lt;code&gt;python&lt;/code&gt; or &lt;code&gt;python3&lt;/code&gt; command. You can also 
write the following code in a Python file such as "testpostgres.py" then
execute it with &lt;code&gt;python testpostgres.py&lt;/code&gt;. Make sure to replace the "user"
and "password" values with your own.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;psycopg2&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;connect_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;dbname=&amp;#39;testpython&amp;#39; user=&amp;#39;matt&amp;#39; host=&amp;#39;localhost&amp;#39; &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; \
                  &lt;span class="s2"&gt;&amp;quot;password=&amp;#39;myOwnPassword&amp;#39;&amp;quot;&lt;/span&gt;
    &lt;span class="c1"&gt;# use our connection values to establish a connection&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;psycopg2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connect_str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# create a psycopg2 cursor that can execute queries&lt;/span&gt;
    &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# create a new table with a single column called &amp;quot;name&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;CREATE TABLE tutorials (name char(40));&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# run a SELECT statement - no data in there, but we can try it&lt;/span&gt;
    &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;SELECT * from tutorials&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;--- makes sure the change is shown in the database&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetchall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Uh oh, can&amp;#39;t connect. Invalid dbname, user or password?&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;When we run the above code we won't get anything fancy, just an empty
list printed out. However, in those few lines of code we've ensured our 
connection to our new database works and we can create new tables in it as 
well as query them.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160518-postgresql-ubuntu-1604/output.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;That's just enough of a hook to get started writing more complicated SQL 
queries using psycopg2 and PostgreSQL. Make sure to check out the 
&lt;a href="/postgresql.html"&gt;PostgreSQL&lt;/a&gt;,
&lt;a href="/databases.html"&gt;relational databases&lt;/a&gt; and 
&lt;a href="/object-relational-mappers-orms.html"&gt;object-relational mappers (ORMs)&lt;/a&gt;
pages for more tutorials.&lt;/p&gt;
&lt;p&gt;Questions? Tweet &lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or post a message on the 
&lt;a href="https://www.facebook.com/fullstackpython"&gt;Full Stack Python Facebook page&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;See something wrong in this post? Fork 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/160518-install-postgresql-python-3-ubuntu-1604.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Mon, 25 Dec 2017 00:00:00 -0500</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2016-05-18:blog/postgresql-python-3-psycopg2-ubuntu-1604.html</guid></item><item><title>How to Use Redis with Python 3 and redis-py on Ubuntu 16.04</title><link>https://www.fullstackpython.com/blog/install-redis-use-python-3-ubuntu-1604.html</link><description>&lt;p&gt;&lt;a href="/redis.html"&gt;Redis&lt;/a&gt; is an in-memory key-value pair 
&lt;a href="/no-sql-datastore.html"&gt;NoSQL data store&lt;/a&gt; often used 
for &lt;a href="/web-frameworks.html"&gt;web application&lt;/a&gt; sessions,
transient &lt;a href="/data.html"&gt;data&lt;/a&gt; and as a broker for 
&lt;a href="/task-queues.html"&gt;task queues&lt;/a&gt;. redis-py is a common Python code 
library for interacting with Redis. Let's learn how to get Redis up
and running on &lt;a href="/ubuntu.html"&gt;Ubuntu&lt;/a&gt; and then start using it in a simple 
Python application.&lt;/p&gt;
&lt;h2&gt;Tools We Need&lt;/h2&gt;
&lt;p&gt;This tutorial is tested with Python 3.5 but either 
&lt;a href="/python-2-or-3.html"&gt;Python 2 or 3&lt;/a&gt; should work for everything written 
here. Just make sure one version is installed on your system by going to 
the terminal and typing &lt;code&gt;python --version&lt;/code&gt;. Other than Python itself,
here is the software we are going to use throughout the rest of this post:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://releases.ubuntu.com/16.04/"&gt;Ubuntu 16.04&lt;/a&gt; (these 
  instructions should work fine with earlier Ubuntu versions as well)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pip.pypa.io/en/stable/"&gt;pip&lt;/a&gt; and 
  &lt;a href="https://virtualenv.pypa.io/en/latest/"&gt;virtualenv&lt;/a&gt; to handle the
  redis-py &lt;a href="/application-dependencies.html"&gt;application dependency&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://redis.io"&gt;Redis&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://redis-py.readthedocs.io/en/latest/"&gt;redis-py&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you aren't sure how how to install pip and virtualenv, review the 
first few steps of the 
&lt;a href="/blog/python-3-flask-green-unicorn-ubuntu-1604-xenial-xerus.html"&gt;how to set up Python 3, Flask and Green Unicorn on Ubuntu 16.04 LTS&lt;/a&gt;
guide.&lt;/p&gt;
&lt;h2&gt;Install Redis&lt;/h2&gt;
&lt;p&gt;There are a few ways to install Redis, such as 
&lt;a href="http://redis.io/topics/quickstart"&gt;downloading and compiling from source&lt;/a&gt;.
However, on Ubuntu we can install a system package through &lt;code&gt;apt&lt;/code&gt;. The
advantage of this method is that the &lt;code&gt;apt&lt;/code&gt; process will take care of 
installing &lt;code&gt;redis-server&lt;/code&gt; as a system service. Open the terminal and run 
the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo apt-get install redis-server
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Enter your &lt;code&gt;sudo&lt;/code&gt; password and when you are prompted whether you want 
to install the new package enter 'yes'.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160516-redis-ubuntu-1604/apt-get-redis.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;After a few moments the downloading and processing should be complete
and you will be back at the prompt.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160516-redis-ubuntu-1604/apt-get-redis-done.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Redis is now installed and the Redis server is running in the background 
as a system service. Even though we installed the &lt;code&gt;redis-server&lt;/code&gt; package,
the installation also comes with the Redis command line client. The client
is useful for connecting directly to the Redis server without any Python
code. Give &lt;code&gt;redis-cli&lt;/code&gt; a try by typing this into the command prompt:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;redis-cli
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The Redis client connects to the localhost server and gives a new prompt
to show it's ready for commands:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160516-redis-ubuntu-1604/redis-cli.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Give the prompt a try by using Redis commands such as &lt;code&gt;keys *&lt;/code&gt; or &lt;code&gt;set a 1&lt;/code&gt;.
The full list of Redis commands is provided in the 
&lt;a href="http://redis.io/commands"&gt;project documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Virtualenv and Install redis-py&lt;/h2&gt;
&lt;p&gt;We need to figure out our &lt;code&gt;python3&lt;/code&gt; location, then create a virtualenv,
activate the virtualenv and then install redis-py with &lt;code&gt;pip&lt;/code&gt;.
Determine your &lt;code&gt;python3&lt;/code&gt; executable location with the &lt;code&gt;which&lt;/code&gt; command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;which python3
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;You'll see some output like the following screenshot.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160516-redis-ubuntu-1604/which-python-3.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Create a new virtualenv either in your home directory or wherever you
store your project virtualenvs. Specify the full path to your &lt;code&gt;python3&lt;/code&gt;
installation. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# specify the system python3 installation
virtualenv --python=/usr/bin/python3 venvs/redistest
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Activate the virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;source ~/venvs/redistest/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Next we can install the redis-py Python package from 
&lt;a href="https://pypi.python.org/pypi"&gt;PyPI&lt;/a&gt; using the &lt;code&gt;pip&lt;/code&gt; command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install redis
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img src="/img/160516-redis-ubuntu-1604/pip-install-redis.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Alright, now it is installed in our virtualenv. Let's write some simple 
Python code to try out give redis-py!&lt;/p&gt;
&lt;h2&gt;Working with Redis from Python&lt;/h2&gt;
&lt;p&gt;Fire up the Python REPL with the &lt;code&gt;python&lt;/code&gt; or &lt;code&gt;python3&lt;/code&gt; command. You can also 
write the following code in a Python file such as "testredis.py" then
execute it with &lt;code&gt;python testredis.py&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;redis&lt;/span&gt;
&lt;span class="c1"&gt;# create a connection to the localhost Redis server instance, by&lt;/span&gt;
&lt;span class="c1"&gt;# default it runs on port 6379&lt;/span&gt;
&lt;span class="n"&gt;redis_db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StrictRedis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;localhost&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6379&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&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="c1"&gt;# see what keys are in Redis&lt;/span&gt;
&lt;span class="n"&gt;redis_db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# output for keys() should be an empty list &amp;quot;[]&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;redis_db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;full stack&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;python&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# output should be &amp;quot;True&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;redis_db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# now we have one key so the output will be &amp;quot;[b&amp;#39;full stack&amp;#39;]&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;redis_db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;full stack&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# output is &amp;quot;b&amp;#39;python&amp;#39;&amp;quot;, the key and value still exist in Redis&lt;/span&gt;
&lt;span class="n"&gt;redis_db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;incr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;twilio&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# output is &amp;quot;1&amp;quot;, we just incremented even though the key did not&lt;/span&gt;
&lt;span class="c1"&gt;# previously exist&lt;/span&gt;
&lt;span class="n"&gt;redis_db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;twilio&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# output is &amp;quot;b&amp;#39;1&amp;#39;&amp;quot; again, since we just obtained the value from&lt;/span&gt;
&lt;span class="c1"&gt;# the existing key&lt;/span&gt;
&lt;span class="n"&gt;redis_db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;twilio&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# output is &amp;quot;1&amp;quot; because the command was successful&lt;/span&gt;
&lt;span class="n"&gt;redis_db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;twilio&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# nothing is returned because the key and value no longer exist&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;That is a quick introduction to some commonly-used Redis commands
invoked by their Python bindings through the redis-py library. Take a look 
at the 
&lt;a href="https://redis-py.readthedocs.io/en/latest/"&gt;redis-py official documentation&lt;/a&gt;
to learn more about the extensive command list you can use to create,
read, modify and delete keys and values in Redis.&lt;/p&gt;
&lt;p&gt;Questions? Tweet &lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or post a message on the 
&lt;a href="https://www.facebook.com/fullstackpython"&gt;Full Stack Python Facebook page&lt;/a&gt;. 
See something wrong in this post? Fork 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/160516-install-redis-use-python-3-ubuntu-1604.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Fri, 28 Apr 2017 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2016-05-16:blog/install-redis-use-python-3-ubuntu-1604.html</guid></item><item><title>How to Send MMS Picture Messages with Python</title><link>https://www.fullstackpython.com/blog/send-mms-picture-messages-python.html</link><description>&lt;p&gt;Multimedia Message Service (MMS) picture and video messages are a common 
extension to the Short Message Service (SMS) system for sending text 
messages. Using a 
&lt;a href="/application-programming-interfaces.html"&gt;web application programming interface (API)&lt;/a&gt;
with Python makes it easy to send MMS messages from a web application or
script. In this short tutorial we'll learn how to add MMS sending capability
to a new or existing Python application.&lt;/p&gt;
&lt;h2&gt;Tools We Need&lt;/h2&gt;
&lt;p&gt;Either &lt;a href="/python-2-or-3.html"&gt;Python 2 or 3&lt;/a&gt; works for the code in this 
tutorial. Just make sure you have one of those two versions installed on 
your system by going to the terminal and typing &lt;code&gt;python --version&lt;/code&gt;.
The other dependencies for this tutorial include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.python.org/"&gt;Python&lt;/a&gt; version &lt;a href="/python-2-or-3.html"&gt;2 or 3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pip.pypa.io/en/stable/"&gt;pip&lt;/a&gt; and 
  &lt;a href="https://virtualenv.pypa.io/en/latest/"&gt;virtualenv&lt;/a&gt; to handle one
  &lt;a href="/application-dependencies.html"&gt;application dependency&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A free &lt;a href="https://www.twilio.com/try-twilio"&gt;Twilio account&lt;/a&gt; to use their 
  &lt;a href="https://www.twilio.com/docs/api/rest/sending-messages"&gt;MMS web API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pypi.org/project/twilio"&gt;Twilio Python helper library&lt;/a&gt;,
  &lt;a href="https://github.com/twilio/twilio-python/tree/6.0.0"&gt;version 6.0.0&lt;/a&gt; 
  or later&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you are unsure of how to get pip and virtualenv installed, take a look
at the first few steps of the 
&lt;a href="/blog/python-3-flask-green-unicorn-ubuntu-1604-xenial-xerus.html"&gt;how to set up Python 3, Flask and Green Unicorn on Ubuntu 16.04 LTS&lt;/a&gt;
guide.&lt;/p&gt;
&lt;h2&gt;Twilio Web API&lt;/h2&gt;
&lt;p&gt;Our simple Python example application will use the Twilio web API to send
picture messages.
Go to the Twilio website
&lt;a href="https://www.twilio.com/try-twilio"&gt;sign up for a free trial account&lt;/a&gt;. If 
you already have a Twilio account (and you should because it makes it easy 
to add almost any type of communications to applications!) then sign into 
your existing account.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160515-mms-python/try-twilio.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;In trial mode Twilio can send MMS to a validated phone number associated 
with the account. When you're ready to send MMS messages to any phone in 
any country then you will have to upgrade your account.&lt;/p&gt;
&lt;p&gt;After signing up for a Twilio account, you will receive your own phone 
number that'll be used to send messages. That phone number can send outbound
MMS messages without any configuration. It can also receive messages but 
that requires 
&lt;a href="https://www.twilio.com/docs/quickstart/python/sms/hello-monkey"&gt;modifying the Request URL webhook&lt;/a&gt; 
in the phone number details screen.&lt;/p&gt;
&lt;h2&gt;Installing Our Dependency&lt;/h2&gt;
&lt;p&gt;We'll use the &lt;a href="https://pypi.org/project/twilio"&gt;twilio helper library&lt;/a&gt; 
as a dependency for our Python code. The helper library can be installed
via the &lt;code&gt;pip&lt;/code&gt; command, which pulls the code from 
&lt;a href="https://pypi.python.org/pypi"&gt;PyPI&lt;/a&gt; into our local virtualenv. In this
tutorial we'll call our virtualenv &lt;code&gt;pymms&lt;/code&gt; but you can name it whatever
you want for your application.&lt;/p&gt;
&lt;p&gt;We have to create the virtualenv before using it. In your terminal enter:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;virtualenv pymms
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If you need to install virtualenv take a look at the
&lt;a href="/blog/python-3-django-gunicorn-ubuntu-1604-xenial-xerus.html"&gt;how to set up Python 3, Django and Green Unicorn on Ubuntu 16.04 LTS&lt;/a&gt;
guide.&lt;/p&gt;
&lt;p&gt;Activate the virtualenv with the &lt;code&gt;source&lt;/code&gt; command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;source pymms/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The command prompt will change to look like this after it is activated:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160515-mms-python/activate-virtualenv.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Now install the 
&lt;a href="https://www.twilio.com/docs/libraries/python"&gt;Twilio Python helper library&lt;/a&gt;. 
Make sure you install the
version 6.0.0 or later current version because the syntax for this
code changed a bit from earlier helper library versions before 6.0.0.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install twilio&amp;gt;=6.0.0
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Once the helper library installs we can use it in our Python code.&lt;/p&gt;
&lt;h2&gt;Sending MMS From Python&lt;/h2&gt;
&lt;p&gt;Launch the the Python interpreter by executing the &lt;code&gt;python&lt;/code&gt; command in
your terminal. You can also create a new file named &lt;code&gt;send_mms.py&lt;/code&gt; if you
want to re-use the code after we give it a try. &lt;/p&gt;
&lt;p&gt;We need to grab our account credentials from the Twilio Console to connect 
our Python code to our Twilio account. Go to the 
&lt;a href="https://www.twilio.com/console"&gt;Twilio Console&lt;/a&gt; and copy the Account SID
and Authentication Token into your Python code.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160515-mms-python/console-tokens.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Enter the following code into the new Python file, or copy it from
&lt;a href="https://github.com/fullstackpython/blog-code-examples"&gt;this GitHub repository that contains all blog code examples&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# import the Twilio client from the dependency we just installed&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;twilio.rest&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;

&lt;span class="c1"&gt;# the following line needs your Twilio Account SID and Auth Token&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ACxxxxxxxxxxxxxx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;zzzzzzzzzzzzz&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# this is the URL to an image file we&amp;#39;re going to send in the MMS&lt;/span&gt;
&lt;span class="n"&gt;media&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://raw.githubusercontent.com/mattmakai/fullstackpython.com/master/static/img/logos/f.png&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# change the &amp;quot;from_&amp;quot; number to your Twilio number and the &amp;quot;to&amp;quot; number&lt;/span&gt;
&lt;span class="c1"&gt;# to the phone number you signed up for Twilio with, or upgrade your&lt;/span&gt;
&lt;span class="c1"&gt;# account to send MMS to any phone number that MMS is available&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&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;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;+19732644152&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                   &lt;span class="n"&gt;from_&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;+12023351278&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                   &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;MMS via Python? Nice!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                   &lt;span class="n"&gt;media_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;All the lines above that start with &lt;code&gt;#&lt;/code&gt; are comments to give you some
context for what each line is doing. After entering that code into the
interpreter or running the Python script with &lt;code&gt;python send_mms.py&lt;/code&gt;
Twilio will send your MMS.&lt;/p&gt;
&lt;p&gt;In a few seconds you should see a message appear on your phone - note that
MMS can take a little longer because your phone has to download the image. 
I use an iPhone so here is what the message looked like when I received it:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160515-mms-python/mms-result.jpg" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;That is everything need to send MMS to a phone. Pretty awesome result for 
a few lines of Python code, right? This code can be added to any Python 
program to send outbound MMS.&lt;/p&gt;
&lt;p&gt;One final note: keep your Twilio Auth Token secret otherwise anyone who
gets it will be able to send and receive messages through your account.&lt;/p&gt;
&lt;p&gt;Questions? Contact me via Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;. I'm also on GitHub with
the username &lt;a href="https://github.com/mattmakai"&gt;mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See something wrong in this post? Fork 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/160515-sending-mms-picture-messages-python.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Wed, 28 Mar 2018 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2016-05-15:blog/send-mms-picture-messages-python.html</guid></item><item><title>Configuring Python 3, Bottle and Gunicorn for Development on Ubuntu 16.04 LTS</title><link>https://www.fullstackpython.com/blog/python-3-bottle-gunicorn-ubuntu-1604-xenial-xerus.html</link><description>&lt;p&gt;The &lt;a href="/ubuntu.html"&gt;Ubuntu 16.04 Long Term Support (LTS)&lt;/a&gt; Linux
&lt;a href="/operating-systems.html"&gt;operating system&lt;/a&gt; was released in April 2016.
This latest Ubuntu release is named "Xenial Xerus" and
it is the first Ubuntu release to include &lt;a href="/python-2-or-3.html"&gt;Python 3&lt;/a&gt;,
instead of Python 2.x, as the default Python installation.&lt;/p&gt;
&lt;p&gt;We can quickly start a new &lt;a href="/bottle.html"&gt;Bottle&lt;/a&gt; web application project 
and run it with &lt;a href="/green-unicorn-gunicorn.html"&gt;Green Unicorn (Gunicorn)&lt;/a&gt; on
Ubuntu 16.04.&lt;/p&gt;
&lt;h2&gt;Tools We Need&lt;/h2&gt;
&lt;p&gt;Our setup requires the Ubuntu 16.04 release along with a few other code 
libraries. Don't install these tools just yet since we'll get to them as 
we go through the walkthrough. Our requirements and their current versions 
as of April 2017 are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://releases.ubuntu.com/16.04/"&gt;Ubuntu 16.04.2 LTS (Xenial Xerus)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/why-use-python.html"&gt;Python&lt;/a&gt; version 
  &lt;a href="https://docs.python.org/3/whatsnew/3.5.html"&gt;3.5.1&lt;/a&gt; 
  (default in Ubuntu 16.04.2)&lt;/li&gt;
&lt;li&gt;&lt;a href="/bottle.html"&gt;Bottle&lt;/a&gt; web framework version 
  &lt;a href="http://bottlepy.org/docs/stable/"&gt;0.13&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/green-unicorn-gunicorn.html"&gt;Green Unicorn (Gunicorn)&lt;/a&gt; version 
  &lt;a href="http://docs.gunicorn.org/en/stable/news.html"&gt;19.7.1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you are developing on Mac OS X or Windows, make sure to use 
virtualization software such
as &lt;a href="https://www.parallels.com/products/desktop/"&gt;Parallels&lt;/a&gt; or
&lt;a href="https://www.virtualbox.org/wiki/Downloads"&gt;VirtualBox&lt;/a&gt; with the 
&lt;a href="http://releases.ubuntu.com/16.04/"&gt;Ubuntu .iso file&lt;/a&gt;. Either the amd64 or
i386 version of 16.04 is fine. I use the amd64 version for my own local 
development.&lt;/p&gt;
&lt;p&gt;A desktop screen like this one appears when you boot up Ubuntu.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160513-ubuntu-bottle-gunicorn/ubuntu-desktop.jpg" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Open a terminal window to install the system packages.&lt;/p&gt;
&lt;h2&gt;System Packages&lt;/h2&gt;
&lt;p&gt;We can see the python3 system version Ubuntu comes with and where its
executable is stored using these commands.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python3 --version
which python3
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img src="/img/160513-ubuntu-bottle-gunicorn/which-python.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Our Ubuntu installation requires a few system packages. We will get prompted 
for the superuser password because restricted system access is needed 
to install packages through 
&lt;a href="https://en.wikipedia.org/wiki/Advanced_Packaging_Tool"&gt;apt&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo apt-get install python3-pip python3-dev
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img src="/img/160513-ubuntu-bottle-gunicorn/install-packages.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Enter &lt;code&gt;y&lt;/code&gt; to let the system package installation process do its job.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160513-ubuntu-bottle-gunicorn/packages-installed.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;The packages we need are now installed. We can continue on to install our 
Python-specific dependencies.&lt;/p&gt;
&lt;h2&gt;Virtualenv&lt;/h2&gt;
&lt;p&gt;In the previous section, &lt;a href="https://virtualenv.pypa.io/en/latest/"&gt;virtualenv&lt;/a&gt; 
and &lt;a href="https://pypi.org/project/pip"&gt;pip&lt;/a&gt; were installed to handle our 
&lt;a href="/application-dependencies.html"&gt;application dependencies&lt;/a&gt;.
We can now use them to download and install Bottle and Gunicorn.&lt;/p&gt;
&lt;p&gt;Create a directory for the virtualenvs. Then create a new virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# make sure pip and setuptools are the latest version&lt;/span&gt;
&lt;span class="n"&gt;pip3&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;upgrade&lt;/span&gt; &lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;setuptools&lt;/span&gt;
&lt;span class="c1"&gt;# the tilde &amp;quot;~&amp;quot; specifies the user&amp;#39;s home directory, like /home/matt&lt;/span&gt;
&lt;span class="n"&gt;cd&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt;
&lt;span class="n"&gt;mkdir&lt;/span&gt; &lt;span class="n"&gt;venvs&lt;/span&gt;
&lt;span class="c1"&gt;# specify the system python3 installation&lt;/span&gt;
&lt;span class="n"&gt;virtualenv&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;=/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;python3&lt;/span&gt; &lt;span class="n"&gt;venvs&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bottleproj&lt;/span&gt;
&lt;span class="n"&gt;python3&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="n"&gt;venv&lt;/span&gt; &lt;span class="n"&gt;venvs&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bottleproj&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Activate the virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;source ~/venvs/bottleproj/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Our prompt will change after we properly activate the virtualenv.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160513-ubuntu-bottle-gunicorn/venv-activated.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Our virtualenv is now activated with Python 3. We can install whatever
dependencies we want, in our case Bottle and Gunicorn. &lt;/p&gt;
&lt;h2&gt;Bottle and Gunicorn&lt;/h2&gt;
&lt;p&gt;We can now install Bottle and Green Unicorn via the &lt;code&gt;pip&lt;/code&gt; command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install bottle gunicorn
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;No errors like we see in the following screenshot is a good sign.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160513-ubuntu-bottle-gunicorn/good-sign.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Use the &lt;code&gt;mkdir&lt;/code&gt; command to create a new directory to keep our Bottle 
project then use the &lt;code&gt;cd&lt;/code&gt; (change directory) command to move into the
new folder.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;mkdir ~/bottleproj
cd ~/bottleproj
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Create a new file named &lt;code&gt;app.py&lt;/code&gt; within our &lt;code&gt;bottleproj&lt;/code&gt; directory so
we can test to make sure Bottle is working properly. I prefer to use
&lt;a href="/vim.html"&gt;Vim&lt;/a&gt; but &lt;a href="/emacs.html"&gt;Emacs&lt;/a&gt; and other 
&lt;a href="/development-environments.html"&gt;development environments&lt;/a&gt; work great as
well.&lt;/p&gt;
&lt;p&gt;Within the new &lt;code&gt;app.py&lt;/code&gt; file write the following code.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;bottle&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;bottle&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;

&lt;span class="c1"&gt;# a basic URL route to test whether Bottle is responding properly&lt;/span&gt;
&lt;span class="nd"&gt;@route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&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;index&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;It works!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# these two lines are only used for python app.py&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;0.0.0.0&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reloader&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# this is the hook for Gunicorn to run Bottle&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bottle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_app&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We could run our app with the Bottle development server using the 
&lt;code&gt;python app.py&lt;/code&gt; command. Let's instead run our Bottle app with
Gunicorn.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;gunicorn -w 2 app:app
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img src="/img/160513-ubuntu-bottle-gunicorn/gunicorn-run.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Sweet, we can bring up our shell Bottle app in the web browser at
the &lt;code&gt;localhost:8000&lt;/code&gt; or &lt;code&gt;127.0.0.1:8000&lt;/code&gt; address.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160513-ubuntu-bottle-gunicorn/it-works.jpg" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Time to develop a full-fledged web application with &lt;a href="/bottle.html"&gt;Bottle&lt;/a&gt;!&lt;/p&gt;
&lt;h2&gt;Ready for Development&lt;/h2&gt;
&lt;p&gt;Now you have a simple setup to develop Bottle web apps using Gunicorn as
the &lt;a href="/wsgi-servers.html"&gt;WSGI server&lt;/a&gt; on Ubuntu 16.04. If you need a
full step-by-step tutorial to deploy your Python web application to a
production environment, check out the 
&lt;a href="http://www.deploypython.com/"&gt;Full Stack Python Guide to Deployments book&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To decide what to do next with your Python project, check out the
&lt;a href="/table-of-contents.html"&gt;Full Stack Python table of contents&lt;/a&gt; page.&lt;/p&gt;
&lt;p&gt;See something wrong in this post? Fork 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/160513-bottle-gunicorn-ubuntu-1604.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Fri, 28 Apr 2017 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2016-05-13:blog/python-3-bottle-gunicorn-ubuntu-1604-xenial-xerus.html</guid></item><item><title>How to Send SMS Text Messages with Python</title><link>https://www.fullstackpython.com/blog/send-sms-text-messages-python.html</link><description>&lt;p&gt;Short Message Service (SMS) text messages are ubiquitous for communication
all over the world. It is easy to send SMS text messages from a 
&lt;a href="/why-use-python.html"&gt;Python&lt;/a&gt; application using a 
&lt;a href="/application-programming-interfaces.html"&gt;web application programming interface (API)&lt;/a&gt;. 
Let's take a look at the tools we need to quickly add SMS capability to our
Python apps.&lt;/p&gt;
&lt;h2&gt;Tools We Need&lt;/h2&gt;
&lt;p&gt;This guide works with both Python 2 and 3, so make sure you have one of 
those two versions installed.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Either &lt;a href="/python-2-or-3.html"&gt;Python 2 or 3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pip.pypa.io/en/stable/"&gt;pip&lt;/a&gt; and 
  &lt;a href="https://virtualenv.pypa.io/en/latest/"&gt;virtualenv&lt;/a&gt; to handle
  &lt;a href="/application-dependencies.html"&gt;application dependencies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A free &lt;a href="www.twilio.com/referral/w9pugq"&gt;Twilio account&lt;/a&gt; to use their 
  &lt;a href="https://www.twilio.com/docs/api/rest/sending-messages"&gt;SMS web API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Open source 
  &lt;a href="https://pypi.org/project/twilio"&gt;Twilio Python helper library&lt;/a&gt;,
  &lt;a href="https://github.com/twilio/twilio-python/tree/6.0.0"&gt;version 6.0.0&lt;/a&gt; 
  or later&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you need assistance getting pip and virtualenv installed, check out the
first few steps of the 
&lt;a href="/blog/python-3-flask-green-unicorn-ubuntu-1604-xenial-xerus.html"&gt;how to set up Python 3, Flask and Green Unicorn on Ubuntu 16.04 LTS&lt;/a&gt;
guide that'll show how to install system packages for those tools.&lt;/p&gt;
&lt;h2&gt;Using a Web API&lt;/h2&gt;
&lt;p&gt;We're going to use a web API to make sending SMS easier and more reliable.
Head to the 
&lt;a href="www.twilio.com/referral/w9pugq"&gt;Twilio website and sign up for a free trial account&lt;/a&gt;
awesome for more than just sending text messages!) then sign into your 
existing account.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160511-send-sms-python/try-twilio.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;The Twilio trial account allows you to send text messages to your own 
validated phone number.  When you want to send SMS to any phone number in 
your country or other countries then you can upgrade your account to send 
messages for fractions of a cent.&lt;/p&gt;
&lt;p&gt;After signing up, you will get a free phone number in your country. We can
use that phone number without any configuration to send outbound text 
messsages. You can also receive text messages but that requires changing
the Request URL webhook in the phone number configuration screen - we'll
cover that in a future blog post.&lt;/p&gt;
&lt;h2&gt;Installing Our Dependency&lt;/h2&gt;
&lt;p&gt;Our code will use a helper library to make it easier to send text messages
from Python. We are going to install the helper library from 
&lt;a href="https://pypi.python.org/pypi"&gt;PyPI&lt;/a&gt; into a virtualenv. First we need to
create the virtualenv. In your terminal use the following command to create
a new virtualenv. If you need to install virtualenv take a look at the
&lt;a href="/blog/python-3-flask-green-unicorn-ubuntu-1604-xenial-xerus.html"&gt;how to set up Python 3, Flask and Green Unicorn on Ubuntu 16.04 LTS&lt;/a&gt;
guide.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;virtualenv sendsms
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Activate the virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;source sendsms/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The command prompt will change after we properly activate the virtualenv
to something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160511-send-sms-python/activate-virtualenv.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Now install the Twilio Python helper library. We are using the 6.0.0
or above library version, which is important because the syntax in
this post is backwards-incompatible with 5.x and previous Twilio helper
library versions.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install twilio&amp;gt;=6.0.0
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The helper library is now installed and we can use it with the Python code 
we create and execute.&lt;/p&gt;
&lt;h2&gt;Sending SMS From Python&lt;/h2&gt;
&lt;p&gt;Fire up the Python interpreter in the terminal using the &lt;code&gt;python&lt;/code&gt; command,
or create a new file named &lt;code&gt;send_sms.py&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;We need to grab our account credentials from the Twilio Console to connect 
our Python code to our Twilio account. Go to the 
&lt;a href="https://www.twilio.com/console"&gt;Twilio Console&lt;/a&gt; and copy the Account SID
and Authentication Token into your Python code.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160511-send-sms-python/console-tokens.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Enter the following code into the interpreter or into the new Python file.
You can also copy and paste the code from the 
&lt;a href="https://github.com/fullstackpython/blog-code-examples/blob/master/send-sms-text-messages-python/send_sms.py"&gt;blog-code-examples Git repository&lt;/a&gt;
in the 
&lt;a href="https://github.com/fullstackpython"&gt;Full Stack Python GitHub organization&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# we import the Twilio client from the dependency we just installed&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;twilio.rest&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;

&lt;span class="c1"&gt;# the following line needs your Twilio Account SID and Auth Token&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ACxxxxxxxxxxxxxx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;zzzzzzzzzzzzz&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# change the &amp;quot;from_&amp;quot; number to your Twilio number and the &amp;quot;to&amp;quot; number&lt;/span&gt;
&lt;span class="c1"&gt;# to the phone number you signed up for Twilio with, or upgrade your&lt;/span&gt;
&lt;span class="c1"&gt;# account to send SMS to any phone number&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&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;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;+19732644152&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                       &lt;span class="n"&gt;from_&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;+12023351278&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                       &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello from Python!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;All the lines above that start with &lt;code&gt;#&lt;/code&gt; are comments. Once you enter that 
code into the interpreter or run the Python script using 
&lt;code&gt;python send_sms.py&lt;/code&gt; the SMS will be sent.&lt;/p&gt;
&lt;p&gt;In a few seconds you should see a message appear on your phone. I'm on
iOS so here's how the text message I received looked.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160511-send-sms-python/hello-from-python.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;That's it! You can add this code to any Python code to send text messages.
Just keep your Auth Token secret as it'll allow anyone that has it to use
your account to send and receive messages.&lt;/p&gt;
&lt;p&gt;Questions? Contact me via Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;. I'm also on GitHub with
the username &lt;a href="https://github.com/mattmakai"&gt;mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See something wrong in this post? Fork 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/160511-send-sms-text-message-python.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Wed, 05 Aug 2020 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2016-05-11:blog/send-sms-text-messages-python.html</guid></item><item><title>How to set up Python 3, Flask and Green Unicorn on Ubuntu 16.04 LTS</title><link>https://www.fullstackpython.com/blog/python-3-flask-green-unicorn-ubuntu-1604-xenial-xerus.html</link><description>&lt;p&gt;&lt;a href="/ubuntu.html"&gt;Ubuntu&lt;/a&gt;'s latest Long Term Support (LTS) 
&lt;a href="/operating-systems.html"&gt;operating system&lt;/a&gt; was released last year, in 
April 2016. The 16.04 update for Ubuntu is known as "Xenial Xerus" and
it is the first Ubuntu release to include &lt;a href="/python-2-or-3.html"&gt;Python 3&lt;/a&gt; 
as the default Python installation.&lt;/p&gt;
&lt;p&gt;We can use the Ubuntu release along with Python version 3.5 to 
start a new &lt;a href="/flask.html"&gt;Flask&lt;/a&gt; web application project and run it with 
&lt;a href="/green-unicorn-gunicorn.html"&gt;Green Unicorn (Gunicorn)&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Tools We'll Need&lt;/h2&gt;
&lt;p&gt;Our project will use the Ubuntu 16.04 release along with a few other 
libraries. You don't have to install these tools just yet, we will get 
to them as we progress through the walkthrough. Our requirements
and their current versions as of April 2017 are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://releases.ubuntu.com/16.04/"&gt;Ubuntu 16.04.2 LTS (Xenial Xerus)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/why-use-python.html"&gt;Python&lt;/a&gt; version 
  &lt;a href="https://docs.python.org/3/whatsnew/3.5.html"&gt;3.5.1&lt;/a&gt; 
  (default in Ubuntu 16.04.2)&lt;/li&gt;
&lt;li&gt;&lt;a href="/flask.html"&gt;Flask&lt;/a&gt; web framework version 
  &lt;a href="http://flask.pocoo.org/docs/0.12/"&gt;0.12&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/green-unicorn-gunicorn.html"&gt;Green Unicorn (Gunicorn)&lt;/a&gt; version 
  &lt;a href="http://docs.gunicorn.org/en/stable/news.html"&gt;19.7.1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you're running on Mac OS X or Windows, use virtualization software such
as &lt;a href="https://www.parallels.com/products/desktop/"&gt;Parallels&lt;/a&gt; or
&lt;a href="https://www.virtualbox.org/wiki/Downloads"&gt;VirtualBox&lt;/a&gt; with the 
&lt;a href="http://releases.ubuntu.com/16.04/"&gt;Ubuntu .iso file&lt;/a&gt;. Either the amd64 or
i386 version of 16.04 is fine. I'm using amd64 for development and testing
in this tutorial.&lt;/p&gt;
&lt;p&gt;Once you boot up Ubuntu, you should see a screen like this one.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160510-ubuntu-flask-gunicorn/ubuntu-desktop.jpg" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Open up a terminal window to proceed with the setup.&lt;/p&gt;
&lt;h2&gt;System Packages&lt;/h2&gt;
&lt;p&gt;We can see the python3 system version Ubuntu comes with and where its
executable is stored using these commands.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python3 --version
which python3
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img src="/img/160510-ubuntu-flask-gunicorn/which-python.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Our Ubuntu installation requires a few system packages. We will get prompted 
for the superuser password because restricted system access is needed 
to install packages through 
&lt;a href="https://en.wikipedia.org/wiki/Advanced_Packaging_Tool"&gt;apt&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo apt-get install python3-dev python3-pip
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img src="/img/160510-ubuntu-flask-gunicorn/install-packages.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Enter &lt;code&gt;y&lt;/code&gt; to let the system package installation process do its job.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160510-ubuntu-flask-gunicorn/packages-installed.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;The packages we need are now installed. We can continue on to install our 
Python-specific dependencies.&lt;/p&gt;
&lt;h2&gt;Virtualenv&lt;/h2&gt;
&lt;p&gt;In the previous section, &lt;a href="https://virtualenv.pypa.io/en/latest/"&gt;virtualenv&lt;/a&gt; 
and &lt;a href="https://pypi.org/project/pip"&gt;pip&lt;/a&gt; were installed to handle our 
&lt;a href="/application-dependencies.html"&gt;application dependencies&lt;/a&gt;.
We can now use them to download and install Flask and Gunicorn.&lt;/p&gt;
&lt;p&gt;Create a directory for the virtualenvs. Then create a new virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# make sure pip and setuptools are the latest version&lt;/span&gt;
&lt;span class="n"&gt;pip3&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;upgrade&lt;/span&gt; &lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;setuptools&lt;/span&gt;
&lt;span class="c1"&gt;# the tilde &amp;quot;~&amp;quot; specifies the user&amp;#39;s home directory, like /home/matt&lt;/span&gt;
&lt;span class="n"&gt;cd&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt;
&lt;span class="n"&gt;mkdir&lt;/span&gt; &lt;span class="n"&gt;venvs&lt;/span&gt;
&lt;span class="c1"&gt;# specify the system python3 installation&lt;/span&gt;
&lt;span class="n"&gt;python3&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="n"&gt;venv&lt;/span&gt; &lt;span class="n"&gt;venvs&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;flaskproj&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Activate the virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;source ~/venvs/flaskproj/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Our prompt will change after we properly activate the virtualenv.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160510-ubuntu-flask-gunicorn/venv-activated.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Our virtualenv is now activated with Python 3. We can install whatever
dependencies we want, in our case Flask and Gunicorn. &lt;/p&gt;
&lt;h2&gt;Flask and Gunicorn&lt;/h2&gt;
&lt;p&gt;We can finally install Flask and Green Unicorn via the pip command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install flask gunicorn
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;It is a good sign if we receive no errors like we see in the following 
screenshot.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160510-ubuntu-flask-gunicorn/good-sign.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Create a new directory under our home directory that will store our
Flask project. Change directory into the new folder.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;mkdir ~/flaskproj
cd ~/flaskproj
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Create a new file named &lt;code&gt;__init__.py&lt;/code&gt; within our &lt;code&gt;flaskproj&lt;/code&gt; directory so
we can test to make sure Flask is working properly. I prefer to use
&lt;a href="/vim.html"&gt;Vim&lt;/a&gt; but &lt;a href="/emacs.html"&gt;Emacs&lt;/a&gt; and other 
&lt;a href="/development-environments.html"&gt;development environments&lt;/a&gt; work great as
well.&lt;/p&gt;
&lt;p&gt;Within &lt;code&gt;__init__.py&lt;/code&gt; write the following code.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;


&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&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;index&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;It works!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We could run our app with the Flask development server using the 
&lt;code&gt;python __init__.py&lt;/code&gt; command. Instead run the Flask app with
Gunicorn. Go to the directory above the &lt;code&gt;flaskproj&lt;/code&gt; folder, in our
case we can enter &lt;code&gt;cd ~&lt;/code&gt; then use the &lt;code&gt;gunicorn&lt;/code&gt; command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;gunicorn flaskproj:app
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img src="/img/160510-ubuntu-flask-gunicorn/gunicorn-run.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Sweet, we can bring up our shell Flask app in the web browser at
the &lt;code&gt;localhost:8000&lt;/code&gt; or &lt;code&gt;127.0.0.1:8000&lt;/code&gt; address.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160510-ubuntu-flask-gunicorn/it-works.jpg" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Now we're ready for some real &lt;a href="/flask.html"&gt;Flask&lt;/a&gt; development!&lt;/p&gt;
&lt;h2&gt;Ready for Development&lt;/h2&gt;
&lt;p&gt;That's a simple setup for developing with Flask and Gunicorn on 
Ubuntu 16.04. If you need an in-depth step-by-step tutorial to 
deploy your &lt;a href="/wsgi-servers.html"&gt;WSGI-powered web application&lt;/a&gt; to a 
production environment, check out the 
&lt;a href="http://www.deploypython.com/"&gt;Full Stack Python Guide to Deployments book&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To determine what to code next for your Python project, read the topics 
found on the &lt;a href="/table-of-contents.html"&gt;table of contents&lt;/a&gt; page.&lt;/p&gt;
&lt;p&gt;Questions? Contact me via Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;. I'm also on GitHub with
the username &lt;a href="https://github.com/mattmakai"&gt;mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Something wrong with this post? Fork 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/160510-flask-gunicorn-ubuntu-1604.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Wed, 14 Jun 2017 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2016-05-10:blog/python-3-flask-green-unicorn-ubuntu-1604-xenial-xerus.html</guid></item><item><title>Setting up Python 3, Django and Gunicorn on Ubuntu 16.04 LTS</title><link>https://www.fullstackpython.com/blog/python-3-django-gunicorn-ubuntu-1604-xenial-xerus.html</link><description>&lt;p&gt;&lt;a href="/ubuntu.html"&gt;Ubuntu&lt;/a&gt; released the newest Long Term Support (LTS) 
version of its &lt;a href="/operating-systems.html"&gt;operating system&lt;/a&gt; in April 2016. 
The update brings Ubuntu to version 16.04 and its latest code name is 
"Xenial Xerus". 16.04 is the first Ubuntu release to include 
&lt;a href="/python-2-or-3.html"&gt;Python 3&lt;/a&gt; as the default Python installation.&lt;/p&gt;
&lt;p&gt;Let's use this newest Ubuntu release along with Python version 3.5 to 
start a new &lt;a href="/django.html"&gt;Django&lt;/a&gt; web application project and run it with 
&lt;a href="/green-unicorn-gunicorn.html"&gt;Green Unicorn (Gunicorn)&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Tools We Need&lt;/h2&gt;
&lt;p&gt;We will need a few tools to complete our project. Don't worry about 
installing these just yet as we'll get to them as we progress through the
tutorial. The tools and their current versions as of April 2017 are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://releases.ubuntu.com/16.04/"&gt;Ubuntu 16.04.2 LTS (Xenial Xerus)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/why-use-python.html"&gt;Python&lt;/a&gt; version 
  &lt;a href="https://docs.python.org/3/whatsnew/3.5.html"&gt;3.5.1&lt;/a&gt; 
  (default in Ubuntu 16.04.2). Python 3.6 has been released but 
  Ubuntu 16.04.2 comes with 3.5.1 by default so we'll use the version 3.5.1 
  in this post.&lt;/li&gt;
&lt;li&gt;&lt;a href="/django.html"&gt;Django&lt;/a&gt; web framework version 
  &lt;a href="https://docs.djangoproject.com/en/1.11/releases/1.11/"&gt;1.11&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/green-unicorn-gunicorn.html"&gt;Green Unicorn (Gunicorn)&lt;/a&gt; version 
  &lt;a href="http://docs.gunicorn.org/en/stable/news.html"&gt;19.7.1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you are running Mac OS X or Windows, use virtualization software such
as &lt;a href="https://www.parallels.com/products/desktop/"&gt;Parallels&lt;/a&gt; 
(this is what I use, but it's Mac OS X-only) or 
&lt;a href="https://www.virtualbox.org/wiki/Downloads"&gt;VirtualBox&lt;/a&gt; with the 
&lt;a href="http://releases.ubuntu.com/16.04/"&gt;Ubuntu .iso file&lt;/a&gt;. Either the amd64 or
i386 version of 16.04 is fine, but I use amd64 for development and testing
in this blog post.&lt;/p&gt;
&lt;p&gt;When we boot up for the first time, we should see a desktop screen like 
this one.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160509-ubuntu-django-gunicorn/ubuntu-desktop.jpg" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Open up terminal to proceed with the setup.&lt;/p&gt;
&lt;h2&gt;System Packages&lt;/h2&gt;
&lt;p&gt;We can see the python3 version Ubuntu comes with, as well as where its
executable is stored.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;python3 --version
which python3
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img src="/img/160509-ubuntu-django-gunicorn/which-python.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Our Ubuntu installation first needs system packages for Python development.
You'll be prompted for your superuser password because restricted system
access is required to install packages through apt.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo apt-get install python3-pip python3-dev
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img src="/img/160509-ubuntu-django-gunicorn/install-packages.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Enter &lt;code&gt;y&lt;/code&gt; and let the system package installation process run.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160509-ubuntu-django-gunicorn/packages-installed.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;The basic system packages we need are now installed so we can proceed to
our Python-specific dependencies.&lt;/p&gt;
&lt;h2&gt;Virtualenv&lt;/h2&gt;
&lt;p&gt;Virtualenv and pip for isolating and handling 
&lt;a href="/application-dependencies.html"&gt;application dependencies&lt;/a&gt; were just 
installed via system packages so we can now use them to obtain Django and 
Gunicorn.&lt;/p&gt;
&lt;p&gt;Create a directory to store virtualenvs then put a new virtualenv in it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# make sure pip and setuptools are the latest version&lt;/span&gt;
&lt;span class="n"&gt;pip3&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;upgrade&lt;/span&gt; &lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;setuptools&lt;/span&gt;
&lt;span class="c1"&gt;# the tilde &amp;quot;~&amp;quot; specifies the user&amp;#39;s home directory, like /home/matt&lt;/span&gt;
&lt;span class="n"&gt;cd&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt;
&lt;span class="n"&gt;mkdir&lt;/span&gt; &lt;span class="n"&gt;venvs&lt;/span&gt;
&lt;span class="c1"&gt;# specify the system python3 installation&lt;/span&gt;
&lt;span class="n"&gt;python3&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="n"&gt;venv&lt;/span&gt; &lt;span class="n"&gt;venvs&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;djproject&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Activate the virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;source ~/venvs/djproject/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We should see our prompt change so that we know the virtualenv is properly 
activated.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160509-ubuntu-django-gunicorn/venv-activated.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Our virtualenv with Python 3 is activated so we can install whatever
dependencies we want, such as Django and Gunicorn. &lt;/p&gt;
&lt;h2&gt;Django and Gunicorn&lt;/h2&gt;
&lt;p&gt;Time to install Django and Green Unicorn into our virtualenv.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install django gunicorn
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;No errors is a good sign everything worked for us.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160509-ubuntu-django-gunicorn/good-sign.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Create a new Django project named &lt;code&gt;djproject&lt;/code&gt;, or whatever you want to name
your project. Then change into the directory for the new project.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;django-admin startproject djproject
cd djproject
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We could run Django with the development server using the 
&lt;code&gt;python manage.py runserver&lt;/code&gt; command. However, start Django up with
Gunicorn instead.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;gunicorn djproject.wsgi
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img src="/img/160509-ubuntu-django-gunicorn/gunicorn-run.png" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Awesome, now we can bring up our shell project in the web browser at
the &lt;code&gt;localhost:8000&lt;/code&gt; or &lt;code&gt;127.0.0.1:8000&lt;/code&gt; address.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/160509-ubuntu-django-gunicorn/it-worked.jpg" width="100%" class="technical-diagram img-rounded"&gt;&lt;/p&gt;
&lt;p&gt;Ready for development!&lt;/p&gt;
&lt;h2&gt;Ready for Development&lt;/h2&gt;
&lt;p&gt;Those are the basics for starting development with Django and Gunicorn on 
Ubuntu 16.04. If you need an even more in-depth step-by-step tutorial to 
deploy your Python web application to a production environment, check out the 
&lt;a href="http://www.deploypython.com/"&gt;Full Stack Python Guide to Deployments book&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To figure out what to do next for your Python project, read the topics 
found on the &lt;a href="/table-of-contents.html"&gt;table of contents&lt;/a&gt; page.&lt;/p&gt;
&lt;p&gt;Questions? Contact me via Twitter 
&lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;. I'm also on GitHub with
the username &lt;a href="https://github.com/mattmakai"&gt;mattmakai&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See something wrong in this post? Fork 
&lt;a href="https://github.com/mattmakai/fullstackpython.com/blob/master/content/posts/160509-django-gunicorn-ubuntu-1604.markdown"&gt;this page's source on GitHub&lt;/a&gt;
and submit a pull request.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Fri, 28 Apr 2017 00:00:00 -0400</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2016-05-09:blog/python-3-django-gunicorn-ubuntu-1604-xenial-xerus.html</guid></item><item><title>The Full Stack Python Blog</title><link>https://www.fullstackpython.com/blog/full-stack-python-blog.html</link><description>&lt;p&gt;&lt;a href="https://www.fullstackpython.com/"&gt;Full Stack Python&lt;/a&gt; began 
&lt;a href="/change-log.html"&gt;way back in December 2012&lt;/a&gt;
when I started writing the initial &lt;a href="/deployment.html"&gt;deployment&lt;/a&gt;, 
&lt;a href="/servers.html"&gt;server&lt;/a&gt;, &lt;a href="/operating-systems.html"&gt;operating system&lt;/a&gt;, 
&lt;a href="/web-servers.html"&gt;web server&lt;/a&gt; and &lt;a href="/wsgi-servers.html"&gt;WSGI server&lt;/a&gt; pages. 
The site has has broadly expanded out into a 
&lt;a href="/table-of-contents.html"&gt;many other subjects&lt;/a&gt; outside the deployment 
topics I originally started this site to explain.&lt;/p&gt;
&lt;p&gt;However, I frequently wanted to write a Python walkthrough that was not a
good fit for the page format I use for each topic. Many of those walkthroughs
became &lt;a href="https://www.twilio.com/blog/author/mmakai"&gt;Twilio blog posts&lt;/a&gt;
but not all of them were quite the right fit on there. I'll still write
more &lt;a href="/twilio.html"&gt;Twilio&lt;/a&gt; tutorials, but this 
&lt;a href="/blog.html"&gt;Full Stack Python blog&lt;/a&gt; is the spot for technical posts that 
fall outside the Twilio domain.&lt;/p&gt;
&lt;p&gt;Let me know what you think and what tutorials you'd like to see in the 
future. &lt;/p&gt;
&lt;p&gt;Hit me up on Twitter &lt;a href="https://twitter.com/fullstackpython"&gt;@fullstackpython&lt;/a&gt;
or &lt;a href="https://twitter.com/mattmakai"&gt;@mattmakai&lt;/a&gt;.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Matt Makai</dc:creator><pubDate>Thu, 28 Dec 2017 00:00:00 -0500</pubDate><guid isPermaLink="false">tag:www.fullstackpython.com,2016-05-08:blog/full-stack-python-blog.html</guid></item></channel></rss>