<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[The Teclado Blog]]></title><description><![CDATA[Kickstart your coding journey]]></description><link>https://blog.teclado.com/</link><image><url>https://blog.teclado.com/favicon.png</url><title>The Teclado Blog</title><link>https://blog.teclado.com/</link></image><generator>Ghost 5.68</generator><lastBuildDate>Mon, 06 Apr 2026 05:11:07 GMT</lastBuildDate><atom:link href="https://blog.teclado.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Run Flask Apps with Docker Compose]]></title><description><![CDATA[Learn how to Dockerize a Flask app and then run it, PostgreSQL, Redis, Celery, and Flower with Docker Compose.]]></description><link>https://blog.teclado.com/run-flask-apps-with-docker-compose/</link><guid isPermaLink="false">6613d6f4394358e03c300a0c</guid><category><![CDATA[Flask]]></category><dc:creator><![CDATA[Jose Salvatierra]]></dc:creator><pubDate>Mon, 08 Apr 2024 11:53:08 GMT</pubDate><media:content url="https://blog.teclado.com/content/images/2024/04/0_0-6.webp" medium="image"/><content:encoded><![CDATA[<img src="https://blog.teclado.com/content/images/2024/04/0_0-6.webp" alt="Run Flask Apps with Docker Compose"><p>Using Docker to run your applications makes everything more reproducible: running the Docker container in your computer and in your deployment service of choice should be identical, so you shouldn&apos;t run into unforeseen issues in production.</p>
<p>But oftentimes when running in production, you don&apos;t use a single service. Databases, background workers, and caches all need to run as well.</p>
<p>If you&apos;re using something like Railway or Render, you&apos;ll be used to setting up the different services with the UI:</p>
<blockquote>
<p>IMAGE showing a complex project set-up with Render, using various different services</p>
</blockquote>
<p>Although it&apos;s handy that you can use their UI to set up your services, it does pose a problem: how do you run all these locally, so you can make sure they all play together nicely before deploying?</p>
<p>This is where Docker Compose comes into play.</p>
<h2 id="creating-a-docker-container-for-a-flask-app">Creating a Docker container for a Flask app</h2>
<p>Before we dive into Docker Compose, let&apos;s start by creating a basic Docker container for a Flask application. First, create a new directory for your project and navigate to it:</p>
<pre><code class="language-bash">mkdir flask-docker-compose
cd flask-docker-compose
</code></pre>
<p>Create a new file named <code>Dockerfile</code> with the following content:</p>
<pre><code class="language-dockerfile">FROM python:3.12
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD [&quot;python&quot;, &quot;app.py&quot;]
</code></pre>
<p>This Dockerfile specifies the base Python image, sets the working directory, installs dependencies, copies the application files, and defines the command to run the Flask app.</p>
<p>Create a <code>requirements.txt</code> file with the following content:</p>
<pre><code>flask
</code></pre>
<p>Finally, create an <code>app.py</code> file with a basic Flask application:</p>
<pre><code class="language-python">from flask import Flask

app = Flask(__name__)

@app.route(&quot;/&quot;)
def hello():
    return &quot;Hello, World!&quot;

if __name__ == &quot;__main__&quot;:
    app.run(host=&quot;0.0.0.0&quot;, port=5000)
</code></pre>
<h3 id="running-the-flask-app-using-docker">Running the Flask app using Docker</h3>
<p>To build and run the Docker container for your Flask app, execute the following commands:</p>
<pre><code class="language-bash">docker build -t flask-app .
docker run -p 5000:5000 flask-app
</code></pre>
<p>Open your web browser and navigate to <code>http://localhost:5000</code>. You should see the &quot;Hello, World!&quot; message.</p>
<h2 id="use-docker-compose-to-run-the-flask-app-and-postgresql">Use Docker Compose to run the Flask app and PostgreSQL</h2>
<p>Now that we have a basic Flask app running in a Docker container, let&apos;s add PostgreSQL to the mix using Docker Compose.</p>
<h3 id="the-docker-compose-code-for-flask">The Docker Compose code for Flask</h3>
<p>Create a new file named <code>docker-compose.yml</code> with the following content:</p>
<pre><code class="language-yaml">version: &apos;3&apos;
services:
  web:
    build: .
    ports:
      - &quot;5000:5000&quot;
    depends_on:
      - db
    environment:
      - DATABASE_URL=postgresql://postgres:password@db/myapp
    volumes:
      - .:/app
  db:
    image: postgres
    environment:
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=myapp
    volumes:
      - postgres_data:/var/lib/postgresql/data
volumes:
  postgres_data:
</code></pre>
<p>Let&apos;s break down the different parts of this Docker Compose file:</p>
<h4 id="port-mapping">Port mapping</h4>
<p>The <code>ports</code> section maps the container&apos;s port 5000 to the host&apos;s port 5000, allowing you to access the Flask app from your local machine.</p>
<h4 id="healthchecks">Healthchecks</h4>
<p>Docker Compose allows you to define healthchecks for your services. In this example, we haven&apos;t added any specific healthchecks, but you can add them to ensure that your services are running properly.</p>
<h4 id="environment-variables-and-files">Environment variables and files</h4>
<p>The <code>environment</code> section allows you to set environment variables for your services. In this case, we set the <code>DATABASE_URL</code> for the Flask app to connect to the PostgreSQL database.</p>
<p>If you wanted to use a <code>.env</code> file for your service instead, you&apos;d define the following block in <code>docker-compose.yml</code>:</p>
<pre><code class="language-yaml">web:
  build: .
  # ...
  env_file:
    - .env
</code></pre>
<p>Your <code>.env</code> file would contain the environment variables you&apos;d like defined in your service:</p>
<pre><code>DATABASE_URL=postgresql://postgres:password@db/myapp
REDIS_URL=redis://redis:6379
SECRET_KEY=your-secret-key
</code></pre>
<div class="definition">
    <div class="definition-label">note</div>
    <h3 class="definition-title">Virtual network</h3>
    <p class="definition-content">
        Docker compose creates a virtual network for your service. When a container is created, it joins the virtual network with the given service name. That&apos;s why the PostgreSQL address is <code>@db</code> and the Redis address hostname is simply <code>redis</code>.
    </p>
</div>
<p>Whether you use environment variables or environment files, to access their values in your Flask app you&apos;d use the <code>os</code> module:</p>
<pre><code class="language-py">import os
from flask import Flask

app = Flask(__name__)
app.config[&quot;SQLALCHEMY_DATABASE_URI&quot;] = os.getenv(&quot;DATABASE_URL&quot;)
app.config[&quot;SECRET_KEY&quot;] = os.getenv(&quot;SECRET_KEY&quot;)
</code></pre>
<h3 id="docker-compose-for-flask-postgres">Docker Compose for Flask + Postgres</h3>
<p>To run the Flask app and PostgreSQL using Docker Compose, execute the following command:</p>
<pre><code class="language-bash">docker-compose up
</code></pre>
<p>Docker Compose will build the Flask app image and start both the Flask app and PostgreSQL containers.</p>
<h3 id="local-volumes">Local volumes</h3>
<p>In the <code>volumes</code> section, we mount the current directory (<code>.</code>) to the <code>/app</code> directory inside the Flask app container. This allows you to make changes to your code and see the changes instantly without rebuilding the container.</p>
<p>We also define a named volume <code>postgres_data</code> for PostgreSQL to persist the database data across container restarts.</p>
<h3 id="specifying-dependencies-between-services">Specifying dependencies between services</h3>
<p>The <code>depends_on</code> section allows you to specify dependencies between services. In this case, the Flask app depends on the PostgreSQL database, so Docker Compose will start the <code>db</code> service before the <code>web</code> service.</p>
<h2 id="adding-more-services-to-your-docker-compose-set-up">Adding more services to your Docker Compose set-up</h2>
<p>Now that we have a basic Flask app and PostgreSQL running with Docker Compose, let&quot;s explore how to add more services like Celery and Flower.</p>
<h3 id="celery">Celery</h3>
<p>To add Celery, a distributed task queue, to your Docker Compose setup, update your <code>docker-compose.yml</code> file:</p>
<pre><code class="language-yaml">version: &apos;3&apos;
services:
  # ...
  celery:
    build: .
    command: celery -A tasks worker --loglevel=info
    volumes:
      - .:/app
    depends_on:
      - db
      - redis
  redis:
    image: redis
</code></pre>
<p>This adds a Celery worker service that depends on the PostgreSQL database and a Redis instance for message brokering.</p>
<p>To run Celery you&apos;ll need a <code>tasks.py</code> file (feel free to name it differently, but that&apos;s the convention!) with the <code>Celery</code> object definition and the task definitions. For example:</p>
<pre><code class="language-py">from celery import Celery
import os

app = Celery(&quot;tasks&quot;, broker=os.getenv(&quot;REDIS_URL&quot;))

@app.task
def example_task(param):
    # Perform task logic here
    print(f&quot;Executing task with param: {param}&quot;)
</code></pre>
<h3 id="flower">Flower</h3>
<p>Flower is a web-based tool for monitoring Celery. To add Flower to your Docker Compose setup:</p>
<pre><code class="language-yaml">version: &apos;3&apos;
services:
  # ...
  flower:
    build: .
    command: celery flower -A tasks --port=5555
    ports:
      - &quot;5555:5555&quot;
    depends_on:
      - celery
</code></pre>
<p>This adds a Flower service that depends on the Celery worker and exposes port 5555 for accessing the Flower web interface.</p>
<p>With these additions, your Docker Compose setup now includes a Flask app, PostgreSQL database, Celery worker, Redis message broker, and Flower monitoring tool. You can start all the services with a single <code>docker-compose up</code> command and develop your application locally with ease.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Thank you for reading! If you&apos;ve enjoyed this article, consider <a href="#/portal/signup">subscribing</a> to get notified when we publish our next one.</p>
]]></content:encoded></item><item><title><![CDATA[VSCode for Django Development in 2024]]></title><description><![CDATA[Setting up Visual Studio Code for Django is straightforward! There are just a few extensions we recommend, and then you'll be good to go.]]></description><link>https://blog.teclado.com/vscode-for-django-development-in-2024/</link><guid isPermaLink="false">6606d1f9394358e03c2ff4ed</guid><category><![CDATA[Learn Python Programming]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Jose Salvatierra]]></dc:creator><pubDate>Mon, 01 Apr 2024 10:59:29 GMT</pubDate><media:content url="https://blog.teclado.com/content/images/2024/04/0_0-5.webp" medium="image"/><content:encoded><![CDATA[<img src="https://blog.teclado.com/content/images/2024/04/0_0-5.webp" alt="VSCode for Django Development in 2024"><p>Setting up your editor properly will make your development experience better and save you time. In this article, let me show you my Visual Studio Code settings for working with Django.</p>
<p>If you&apos;re new to Python or to VSCode, check out our blog post <a href="https://blog.teclado.com/how-to-set-up-visual-studio-code-for-python-development/">How to set up Visual Studio Code for Python development</a> for the initial editor set-up when working with Python.</p>
<h2 id="setting-up-vscode-for-django">Setting up VSCode for Django</h2>
<p>There are just a few extensions you need to work with Django effectively in VSCode:</p>
<ul>
<li><a href="https://marketplace.visualstudio.com/items?itemName=batisteo.vscode-django&amp;ref=blog.teclado.com">Django</a></li>
<li><a href="https://marketplace.visualstudio.com/items?itemName=monosans.djlint&amp;ref=blog.teclado.com">Djlint</a></li>
<li><a href="https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss&amp;ref=blog.teclado.com">Tailwind CSS IntelliSense</a> (optional, but recommended!)</li>
</ul>
<p>More info on what each one does below!</p>
<h3 id="the-django-extension">The Django extension</h3>
<p><a href="https://marketplace.visualstudio.com/items?itemName=batisteo.vscode-django&amp;ref=blog.teclado.com">This extension</a> helps us write Django templates using HTML and the Django templating language. It gives us:</p>
<ul>
<li>Syntax highlighting in the templates, so we keep HTML syntax and also the Django templating language.</li>
<li><code>CMD+Click</code> (or <code>CTRL+Click</code> on Windows) to &quot;Go to definition&quot; in templates. For example, in your <code>{% extends &apos;base.html&apos; %}</code>, you can <code>CMD+Click</code> <code>&apos;base.html&apos;</code> to go to that file. Neat!</li>
<li>Support for snippets, but I don&apos;t tend to use these.</li>
</ul>
<h3 id="linting-html-with-djlint">Linting HTML with Djlint</h3>
<p>Djlint is a library for linting HTML. Just as we use Ruff for linting our Python code, Djlint can tell us of any issues in our HTML code.</p>
<p>To use Djlint, <a href="https://marketplace.visualstudio.com/items?itemName=monosans.djlint&amp;ref=blog.teclado.com">install the extension</a> and also install the <code>djlint</code> Python package in your virtual environment:</p>
<pre><code>source .venv/bin/activate
pip install -U djlint
</code></pre>
<p>Not using a virtual environment for your project? I strongly recommend you do, and I&apos;ve written a <a href="https://blog.teclado.com/python-virtual-environments-complete-guide/">complete guide to virtual environments</a> for your enjoyment. It&apos;s our most popular free tutorial by a mile!</p>
<h4 id="optionally-formatting-html-with-djlint">Optionally, formatting HTML with Djlint</h4>
<p>You can use Djlint to format your HTML code as well as linting. This just means adding newlines to break down long lines, and indenting HTML attributes evenly.</p>
<p>I really like formatting my HTML, because doing it by hand takes a while.</p>
<p>But I tend to use Django with AlpineJS, and Djlint formatting doesn&apos;t work with AlpineJS well because the HTML attributes AlpineJS uses can get quite long.</p>
<p>If you don&apos;t use super long HTML attributes, I recommend you use Djlint for formatting! You can enable it in your settings.json file like so:</p>
<pre><code>&quot;[django-html]&quot;: {
	&quot;editor.defaultFormatter&quot;: &quot;monosans.djlint&quot;,
    &quot;editor.detectIndentation&quot;: true,
    &quot;editor.formatOnSave&quot;: true,
    &quot;editor.tabSize&quot;: 2,
    &quot;djlint.profile&quot;: &quot;django&quot;
}
</code></pre>
<p>This will automatically format on save. Feel free to disable it if you&apos;d rather format manually.</p>
<p>To run formatting manually you&apos;d open the command palette with <code>CMD+Shift+P</code> (or <code>CTRL+Shift+P</code> on Windows) and then &quot;Format Document&quot;.</p>
<p>If you enable format on save and you want to save <em>without</em> formatting, you can use the shortcut <code>CMD+k s</code> (or <code>CTRL+k s</code> on Windows). That shortcut is a two-part shortcut, so you&apos;d press <code>CMD+k</code> first, and then <code>s</code>.</p>
<p>As I said earlier, I don&apos;t use Djlint for formatting because it doesn&apos;t work well with AlpineJS, so I keep format on save off.</p>
<h3 id="tailwind-css-intellisense">Tailwind CSS IntelliSense</h3>
<p>If you use TailwindCSS (I do, and recommend it), install the <a href="https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss&amp;ref=blog.teclado.com">Tailwind CSS IntelliSense</a> extension. It will make it so in HTML files, when you start typing a class name, you get autocomplete and documentation as to what the class does.</p>
<p><strong>Note</strong>: it only works if you have a <code>tailwind.config.js</code> in the root of your project (the folder you opened with VSCode). I&apos;ve sometimes created an empty file there just so the extension works, if I have my config file somewhere else.</p>
<h2 id="conclusion">Conclusion</h2>
<p>That&apos;s about it! We&apos;ve installed a number of VSCode to make our life easier:</p>
<ul>
<li>Django</li>
<li>Djlint</li>
<li>Tailwind CSS IntelliSense</li>
</ul>
<p>And some from our <a href="https://blog.teclado.com/how-to-set-up-visual-studio-code-for-python-development/">VSCode for Python blog post</a>:</p>
<ul>
<li>Python (and Pylance)</li>
<li>Ruff</li>
<li>indent-rainbow</li>
<li>Rainbow Brackets</li>
<li>vscode-icons</li>
<li>LiveShare</li>
</ul>
<p>If you&apos;ve enjoyed this blog post, consider subscribing below! We&apos;re working on a completely new Django mega-course that will be released in a couple months.</p>
<p>Subscribing will let me notify you when the course is available, and also you&apos;ll get new blog posts in your inbox (once a week or so).</p>
<p>See you in the next one!</p>
]]></content:encoded></item><item><title><![CDATA[Run A/B Tests with PostHog and Flask]]></title><description><![CDATA[Learn how to use PostHog and feature flags to run A/B tests. This lets us compare two variants and see which performs better!]]></description><link>https://blog.teclado.com/a-b-tests-posthog-flask/</link><guid isPermaLink="false">6602d1e8394358e03c2fe0f7</guid><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Jose Salvatierra]]></dc:creator><pubDate>Tue, 26 Mar 2024 14:22:04 GMT</pubDate><media:content url="https://blog.teclado.com/content/images/2024/03/0_0-4.webp" medium="image"/><content:encoded><![CDATA[<img src="https://blog.teclado.com/content/images/2024/03/0_0-4.webp" alt="Run A/B Tests with PostHog and Flask"><p>When your application starts growing, you start wondering... Should my checkout button say &quot;Checkout&quot; or &quot;Buy now&quot;? Which one will make my customers buy more? Also, should it be green or blue?</p>
<p>If you have these questions, then A/B tests are the answer!</p>
<p>An A/B test lets you compare two versions of your code side by side, and check which version of the code achieve more of your goal.</p>
<p>Your goal may be increased conversions (purchases), or it may be more clickthroughs, more returning visitors, or anything else!</p>
<p>In PostHog, A/B tests are implemented using feature flags. If you haven&apos;t read our article on using <a href="blog.teclado.com/how-to-use-feature-flags-posthog-flask/">feature flags with Python and PostHog</a>, I recommend reading it first!</p>
<p>As a <a href="https://blog.teclado.com/feature-flags-posthog-flask/">quick recap</a>, once you&apos;ve created your feature flags (or A/B tests), you can retrieve them in your Flask app with this snippet:</p>
<pre><code class="language-python">@app.before_request
def before_request():
    if &quot;user_id&quot; not in session:
        if current_user.is_authenticated:
            session[&quot;user_id&quot;] = current_user.id
        else:
            session[&quot;user_id&quot;] = str(uuid.uuid4())

	if current_user.is_authenticated and current_user.id != session.get(&quot;user_id&quot;):
        posthog.alias(session[&quot;user_id&quot;], current_user.id)
        session[&quot;user_id&quot;] = current_user.id

    try:
        session[&quot;feature_flags&quot;] = posthog.get_all_flags(
            session[&quot;user_id&quot;], only_evaluate_locally=True
        )
    except:  # noqa
        session[&quot;feature_flags&quot;] = {}
</code></pre>
<h2 id="how-to-create-an-ab-test-with-posthog">How to create an A/B test with PostHog</h2>
<p>Let&apos;s say that you want to check whether the text &quot;Purchase&quot;, &quot;Buy now&quot; or &quot;Subscribe&quot; performs better in the main homepage button.</p>
<p>First, click on the A/B tests section of the navigation, and then click on &quot;New experiment&quot;:</p>
<p><img src="https://res.cloudinary.com/teclado/image/upload/f_auto,q_auto/v1710516359/blog/2024-03-16-posthog-ab-tests/posthog-new-experiment_gkd560.png" alt="Run A/B Tests with PostHog and Flask" loading="lazy"></p>
<p>Then you&apos;d create your A/B test using PostHog:</p>
<p><img src="https://res.cloudinary.com/teclado/image/upload/f_auto,q_auto/v1710516356/blog/2024-03-16-posthog-ab-tests/posthog-ab-test-name-variants_fbhflx.png" alt="Run A/B Tests with PostHog and Flask" loading="lazy"></p>
<p>Here you&apos;d also give each test variant a value. Leaving one as &quot;control&quot; is the norm, so you always know that that&apos;s the default. The other can get a name that&apos;s meaningful for what you are testing. In this case, &quot;buy_now&quot; and &quot;subscribe&quot; may be suitable.</p>
<p>In the A/B test configuration you can select conversions as the metric you want to see a change in.</p>
<p>If you haven&apos;t set up event tracking with PostHog, read <a href="https://blog.teclado.com/analytics-posthog-python-flask/">our article</a> on how to do that!</p>
<p><img src="https://res.cloudinary.com/teclado/image/upload/f_auto,q_auto/v1710516353/blog/2024-03-16-posthog-ab-tests/posthog-ab-test-goal_mrumgd.png" alt="Run A/B Tests with PostHog and Flask" loading="lazy"></p>
<p>Finally, you can optionally specify the minimum acceptable conversion. This helps PostHog tell you for how long the experiment should run, and when it has achieved the goal.</p>
<p><img src="https://res.cloudinary.com/teclado/image/upload/f_auto,q_auto/v1710516352/blog/2024-03-16-posthog-ab-tests/posthog-ab-test-improvement_fpupni.png" alt="Run A/B Tests with PostHog and Flask" loading="lazy"></p>
<p>It can happen that there is no statistically significant difference between your variants! PostHog will tell you that in this screen as the experiment progresses, and in that case it won&apos;t matter which variant you opt for.</p>
<h2 id="how-to-implement-the-ab-test-in-code">How to implement the A/B test in code</h2>
<p>Now that we&apos;ve got the A/B test created, we can use the PostHog client in our code to implement the two paths. For changing the button text, we&apos;d do it in the template:</p>
<pre><code class="language-html">&lt;button class=&quot;px-4 py-2 rounded-md shadow bg-green-500&quot;&gt;
    {%- if session[&quot;feature_flags&quot;].get(&quot;purchase_button&quot;) == &quot;control&quot; -%}
        Purchase
    {%- else if session[&quot;feature_flags&quot;].get(&quot;purchase_button&quot;) == &quot;buy_now&quot; -%}
        Buy now
    {%- else if session[&quot;feature_flags&quot;].get(&quot;purchase_button&quot;) == &quot;subscribe&quot; -%}
        Subscribe
    {%- endif -%}
&lt;/button&gt;
</code></pre>
<p>When the experiment ends&#x2014;after you&apos;ve selected the winning variant in PostHog&#x2014;it&apos;s important to go back and delete these if statements to simplify the code. At that point you&apos;d just keep the code from the winning variant.</p>
<h2 id="simplify-accessing-feature-flags-in-jinja-with-a-filter">Simplify accessing feature flags in Jinja with a filter</h2>
<p>Although that works fine, if we have a lot of feature flags, we can shorten the code quite a bit by using a custom Jinja filter.</p>
<p>In your app definition file, you can add this:</p>
<pre><code class="language-python">@app.template_filter(&quot;is_active&quot;)
def flag_is_active(feature_flag: str, test_name: str = None) -&gt; bool:
    flag_value = test_name or True
    return session[&quot;feature_flags&quot;].get(feature_flag) == flag_value
</code></pre>
<p>This is a simple filter called <code>is_active</code> that will let us check wither:</p>
<ul>
<li>A feature flag is active or not; or</li>
<li>An A/B test has a specific value.</li>
</ul>
<p>To use it in your template, you&apos;d do this:</p>
<pre><code class="language-html">&lt;button class=&quot;px-4 py-2 rounded-md shadow bg-green-500&quot;&gt;
    {%- if &quot;purchase_button&quot; | is_active(&quot;control&quot;) -%}
        Checkout
    {%- else if &quot;purchase_button&quot; | is_active(&quot;buy_now&quot;) -%}
        Buy now
    {%- else if &quot;purchase_button&quot; | is_active(&quot;subscribe&quot;) -%}
        Subscribe
    {%- endif -%}
&lt;/button&gt;
</code></pre>
<p>That&apos;s for an A/B test. If you wanted to use it for a feature flag without a payload, you could just do:</p>
<pre><code class="language-html">{% if &quot;new_feature&quot; | is_active %}
</code></pre>
<h2 id="conclusion">Conclusion</h2>
<p>That&apos;s everything for now! I hope you&apos;ve learned something new about running A/B tests with PostHog and how to use them.</p>
<p>If you want to learn more about web development with Flask, check out our <a href="https://go.tecla.do/web-dev-course-sale?ref=blog.teclado.com">Web Developer Bootcamp</a>. You can enrol for a one-time fee, or <a href="https://teclado.com/subscribe/?ref=blog.teclado.com">subscribe</a> to gain access to all our courses!</p>
]]></content:encoded></item><item><title><![CDATA[Feature flags in Flask using PostHog]]></title><description><![CDATA[Learn how to use PostHog feature flags in your Flask applications to change your app's behaviour in real time.]]></description><link>https://blog.teclado.com/feature-flags-posthog-flask/</link><guid isPermaLink="false">65f467bc394358e03c2fcbf9</guid><category><![CDATA[Web Development]]></category><category><![CDATA[Flask]]></category><dc:creator><![CDATA[Jose Salvatierra]]></dc:creator><pubDate>Mon, 18 Mar 2024 11:29:22 GMT</pubDate><media:content url="https://blog.teclado.com/content/images/2024/03/0_0-3.webp" medium="image"/><content:encoded><![CDATA[<img src="https://blog.teclado.com/content/images/2024/03/0_0-3.webp" alt="Feature flags in Flask using PostHog"><p>The term &quot;feature flagging&quot; means to use a boolean value to determine whether a certain feature should be enabled or disabled. A feature flag can be as simple as a Python variable!</p>
<p>Here&apos;s a very basic feature flag (note that <code>secure_hash</code> would have to be implemented separately):</p>
<pre><code class="language-python">username = input(&quot;Enter your username: &quot;)
password = input(&quot;Enter your password: &quot;)

use_hashing = False

if use_hashing:
	password = secure_hash(password)

print(username, password)
</code></pre>
<p>In the block of code above, <code>use_hashing</code> is a feature flag! When it is <code>True</code>, then the password hashing functionality would be used. If it isn&apos;t, then password hashing wouldn&apos;t be used.</p>
<p>Normally though, we don&apos;t use plain boolean variables as feature flags!</p>
<p>A key aspect of feature flagging is that we should be able to change the value of the flag <em>without modifying the code</em>.</p>
<h3 id="what-are-feature-flags">What are feature flags?</h3>
<p>As we&apos;ve just seen, a feature flag is a boolean value that tells the code whether a certain feature should be turned on or off.</p>
<p>Normally feature flags are not hard-coded. Instead, they&apos;re read from a feature flag service. This could be a database or a third party service that stores feature flags and lets us change them while our code is running.</p>
<h2 id="why-should-you-use-feature-flags">Why should you use feature flags?</h2>
<p>As long as your feature flags are read from a third party service or a database, and you can change them while the code is running, using feature flags lets you modify what the code does, without requiring a code change and a re-deploy.</p>
<p>Because you don&apos;t need to change the code and re-deploy&#x2014;you just toggle the feature flag on or off&#x2014;it&apos;s <em>much faster</em> to enable or disable a feature.</p>
<p>This is particularly useful for:</p>
<ul>
<li>New features that you just added to your code, and you want to be able to turn them off if there&apos;s a problem.</li>
<li>Features you&apos;re still working on, but you want to deploy the code while keeping the feature flag off. This means the app can still run, and the code can be integrated with the work other developers are doing.</li>
<li>Features that you are upgrading as part of a new version, and you want to both turn the old version off and turn the new version on at the same time.</li>
</ul>
<h2 id="how-to-create-feature-flags-with-posthog">How to create feature flags with PostHog</h2>
<p>First navigate to the Feature Flags section of the page using the left hand navigation. Then, click on &quot;New feature flag&quot;:</p>
<p><img src="https://res.cloudinary.com/teclado/image/upload/f_auto,q_auto/v1710506274/blog/2024-03-15-posthog-feature-flags/create-feature-flag_v83m1u.png" alt="Feature flags in Flask using PostHog" loading="lazy"></p>
<p>Now you should give your feature flag a name so that you can check it in your code later. A description is never amiss, since sometimes feature flags can stick around for a long time and you might forget what it&apos;s about!</p>
<p><img src="https://res.cloudinary.com/teclado/image/upload/f_auto,q_auto/v1710506281/blog/2024-03-15-posthog-feature-flags/new-feature-flag_rspcyf.png" alt="Feature flags in Flask using PostHog" loading="lazy"></p>
<p>You can optionally tell PostHog to return a payload instead of <code>true</code> when the feature flag is enabled for a user. Below we&apos;re returning the string <code>&quot;v2&quot;</code>.</p>
<p>You should also specify what percentage of users should see this feature flag. Using 100% means that when the feature flag is enabled, all users will see the new feature.</p>
<p><img src="https://res.cloudinary.com/teclado/image/upload/f_auto,q_auto/v1710506277/blog/2024-03-15-posthog-feature-flags/feature-flag-settings_ozwdro.png" alt="Feature flags in Flask using PostHog" loading="lazy"></p>
<h2 id="how-to-check-the-active-feature-flags-from-posthog-using-python">How to check the active feature flags from PostHog using Python</h2>
<p>Before you can check the active feature flags for a user, you should set up tracking for anonymous and authenticated users. We looked at how to do this in our <a href="https://blog.teclado.com/analytics-posthog-python-flask/">first PostHog blog post</a>.</p>
<p>Once your users all have unique IDs, you can use the PostHog client to calculate the feature flags that should be enabled for this user.</p>
<p>You can do it in the same <code>before_request</code> function:</p>
<pre><code class="language-python">@app.before_request
def before_request():
    # Create a unique uuid identifier for this user
    # Store it in cookes
    # Use it to track user in PostHog
    if &quot;user_id&quot; not in session:
        if current_user.is_authenticated:
            session[&quot;user_id&quot;] = current_user.id
        else:
            session[&quot;user_id&quot;] = str(uuid.uuid4())

	if current_user.is_authenticated and current_user.id != session.get(&quot;user_id&quot;):
        posthog.alias(session[&quot;user_id&quot;], current_user.id)
        session[&quot;user_id&quot;] = current_user.id

    try:
        session[&quot;feature_flags&quot;] = posthog.get_all_flags(
            session[&quot;user_id&quot;], only_evaluate_locally=True
        )
    except:  # noqa
        session[&quot;feature_flags&quot;] = {}
</code></pre>
<p>Using <code>only_evaluate_locally</code> will mean that the PostHog client will make fewer requests to the PostHog API. Otherwise, many requests are made every time a request is made to your Flask app.</p>
<p>This works because the PostHog client periodically fetches the feature flag information from your account, and then can assign feature flag variants to users without having to make an API call per user request.</p>
<h2 id="how-to-use-feature-flags-in-flask">How to use feature flags in Flask</h2>
<p>Above you learned how to check the feature flags for a given user. With that, you can now enable or disable specific features!</p>
<p>For example, let&apos;s say that you want to change the way an endpoint behaves. If a feature flag is active, you&apos;ll render one template. But if it isn&apos;t active, you&apos;ll render a different template.</p>
<pre><code class="language-python">@app.route(&quot;/homepage&quot;)
def homepage():
	if session[&quot;feature_flags&quot;].get(&quot;new_homepage&quot;) == &quot;v2&quot;:
		return render_template(&quot;homepage_v2.html&quot;)
	return render_template(&quot;homepage.html&quot;)
</code></pre>
<p>As you can see, using the feature flag is as simple as getting the feature flag name you created in PostHog from the <code>session[&quot;feature_flags&quot;]</code> dictionary!</p>
<p>But what if you wanted to use a feature flag to show or hide a part of the page?</p>
<p>Since the feature flags are stored in the <code>session</code>, you can use them within a template.</p>
<h3 id="how-to-change-parts-of-the-page-using-feature-flags">How to change parts of the page using feature flags</h3>
<p>Using a Jinja if statement, we can check the feature flag values and render part of the  page or another.</p>
<p>Note that if you use caching together with feature flags, any changes to your feature flags won&apos;t be shown until your cache for the given page expires. That&apos;s because when a page is cached, the Python code doesn&apos;t run and the template isn&apos;t re-rendered.</p>
<p>Let&apos;s change part of the page template using feature flags (the feature flag below was created without a custom payload):</p>
<pre><code class="language-html">{% if session[&quot;feature_flags&quot;].get(&quot;new_homepage_button&quot;) %}
&lt;button class=&quot;modern-button&quot;&gt;Click me!&lt;/button&gt;
{% else %}
&lt;button class=&quot;old-button&quot;&gt;I&apos;m looking dated...&lt;/button&gt;
{% endif %}
</code></pre>
<p>It&apos;s as simple as that! Just wrap the new or modified page content using an if statement that checks the feature flag. Since the feature flags were loaded before the request was processed, rendering a template has access to the latest feature flags.</p>
<h3 id="when-to-use-feature-flags-and-when-to-use-ab-tests">When to use feature flags and when to use A/B tests?</h3>
<p>In PostHog, A/B testing and feature flags both are implemented using feature flags. However, when you create an A/B test in PostHog you get some extra analytics and tracking to help you identify which version (A or B) performs better.</p>
<p>If you just want to directly or incrementally release a new feature, using feature flags is the way to go as it&apos;s much simpler. If you want to compare two options for conversion rate optimization, you should run an A/B test.</p>
<p>In our next blog post we&apos;ll talk about A/B testing with PostHog.</p>
<p>Thank you for reading, and I hope you&apos;ve learned something new!</p>
<p>If you want to learn more about web development with Flask, check out our <a href="https://go.tecla.do/web-dev-course-sale?ref=blog.teclado.com">Web Developer Bootcamp</a>. You can enrol for a one-time fee, or <a href="https://teclado.com/subscribe/?ref=blog.teclado.com">subscribe</a> to gain access to all our courses!</p>
]]></content:encoded></item><item><title><![CDATA[Tracking Web and Product Analytics with PostHog]]></title><description><![CDATA[Product and web analytics help you build what your users want. In this article, start learning about PostHog and how to use it with Flask or Django.]]></description><link>https://blog.teclado.com/analytics-posthog-python-flask/</link><guid isPermaLink="false">65e700fa394358e03c2fb4b1</guid><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Jose Salvatierra]]></dc:creator><pubDate>Tue, 05 Mar 2024 12:03:34 GMT</pubDate><media:content url="https://blog.teclado.com/content/images/2024/03/0_0.webp" medium="image"/><content:encoded><![CDATA[<img src="https://blog.teclado.com/content/images/2024/03/0_0.webp" alt="Tracking Web and Product Analytics with PostHog"><p>Understanding your users is crucial to the success of your web application.</p><p>Whether you&apos;re a startup or an established company, having access to some data around usage and engagement helps you make better decisions.</p><p>However, any discussion around web analytics needs a caveat: not <em>everything</em> can be described by a data point on a chart. Sometimes you need to talk to users. Other times, your gut and intuition are your best friends!</p><p>With that said, PostHog has an excellent offering that helps you understand usage as well as test variations (using A/B testing) and perform feature rollouts (with feature flags). Let&apos;s take a look.</p><p>By the way, I&apos;ve made a video based on this blog post! You can check it out below.</p><figure class="kg-card kg-embed-card"><iframe width="200" height="113" src="https://www.youtube.com/embed/NdRusxssLMI?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="Tracking Web and Product Analytics with PostHog and Python"></iframe></figure><h2 id="importance-of-analytics-in-growth">Importance of Analytics in Growth</h2><p>With user analytics, you can understand which features are most popular and which are essentially unused.</p><p>I&apos;ve developed features before that nobody ended up using. But were users trying out the feature, and deciding they didn&apos;t need it? Or was the feature too hidden, and users didn&apos;t even know it was there?</p><p>Good analytics (and talking with your users) helps you detect which of these happened.</p><p>By analyzing product usage, sign-ups, and onboarding processes, you can identify patterns that guide your next big feature or enhancement.</p><p>Web analytics provide further granularity by revealing page views, session durations, and user flows across your marketing and content pages. Understanding these metrics is vital for optimizing user experience and conversion rate.</p><h2 id="feature-flags-and-ab-testing">Feature Flags and A/B Testing</h2><p>PostHog&apos;s feature flags offer the flexibility of progressively rolling out new features of your application.</p><p>For example, you could make a new feature available to just 5% of your users with a feature flag. If you see any glaring errors, you can turn the feature off without affecting your entire user base.</p><p>A/B testing is an extension of this concept. By presenting different versions of a page to different user segments, you can determine which performs better, which can help maximize conversions.</p><h2 id="session-replay-and-user-insight">Session Replay and User Insight</h2><p>Session replay is another powerful feature of PostHog. It provides a visual playback of user interactions, allowing you to diagnose errors and refine the user journey with precision.</p><h2 id="getting-started-with-posthog">Getting Started with PostHog</h2><p>Firstly, you&apos;ll need to create a PostHog account. Register&#xA0;<a href="https://app.posthog.com/signup?ref=blog.teclado.com" rel="nofollow">here</a>&#xA0;and familiarize yourself with the dashboard. To track analytics, you can integrate PostHog into your application in several ways.</p><h3 id="for-web-clients">For Web Clients</h3><p>Embedding PostHog into your web application begins with setting up the web client. You will include a script in your base HTML template that initializes PostHog and starts tracking page events.</p><p>Here&#x2019;s a quick setup guide:</p><p>Insert the PostHog JavaScript snippet into the&#xA0;<code>&lt;head&gt;</code>&#xA0;section of your&#xA0;<code>base.html</code>&#xA0;template:</p><pre><code class="language-html">&lt;!&#x2013; base.html --&gt;
&lt;head&gt;
    &lt;script&gt;
        &lt;!-- PostHog initialization code --&gt;
        &lt;!-- PostHog user identification code --&gt;
    &lt;/script&gt;
&lt;/head&gt;</code></pre><p>Replace&#xA0;<code>&lt;!-- PostHog initialization code --&gt;</code>&#xA0;with the script provided by PostHog.</p><p>Remember to configure the script with your &apos;Project API Key&apos; to begin tracking page loads and views.</p><p>Then, you&apos;ll want to identify users. Each user in your application should be identifiable with a unique ID. Assigning each user a unique ID is done in the Python code. More on that in a moment!</p><h3 id="for-custom-events-with-python-api">For Custom Events with Python API</h3><p>To identify users and to send custom product analytics events, you&apos;ll use PostHog&apos;s Python library:</p><p>Install the PostHog library using pip:</p><pre><code>pip install posthog</code></pre><p>In your Python application, import and configure PostHog with your API key:</p><pre><code class="language-python">import posthog

posthog.api_key = &apos;your-posthog-api-key&apos;
posthog.host = &apos;https://app.posthog.com&apos;  # or eu.posthog.com</code></pre><p>Capture events by calling&#xA0;<code>posthog.capture</code>, for example:</p><pre><code class="language-python">posthog.capture(&apos;user-id&apos;, &apos;User Signed Up&apos;)</code></pre><p>Remember to replace&#xA0;<code>&apos;user-id&apos;</code>&#xA0;with the actual identifier of your user.</p><p>But how do you get a user identifier? You use the database user ID, but there&apos;s a bit more to that... Read on!</p><h3 id="identifying-registered-and-anonymous-users-with-posthog-and-flask">Identifying registered and anonymous users with PostHog and Flask</h3><p>Most (all?) users start their visit to a website as anonymous users. They don&apos;t have an account, and they&apos;re not logged in.</p><p>Then at some point they may register or log in.</p><p>In subsequent visits they may start the journey as logged-in users, or not. If their session has expired, they&apos;ll need to log in again.</p><p>This is to say, that our tracking needs to be able to track anonymous and registered users, and it also needs to handle when an anonymous user becomes a registered user, and make sure any captured data is stored under the registered user data.</p><p>For anonymous users, we&apos;ll define a unique ID and store that in the browser cookies. Every time they make a page request, that same user ID will be re-used. For registered users, we&apos;ll use their database ID.</p><pre><code class="language-python">from flask import session
from flask_security_too import current_user
import uuid
import posthog

posthog.api_key = &apos;your-posthog-api-key&apos;
posthog.host = &apos;https://app.posthog.com&apos;

@app.before_request
def before_request():
    if &quot;user_id&quot; not in session:
        if current_user.is_authenticated:
            session[&quot;user_id&quot;] = current_user.id
        else:
            session[&quot;user_id&quot;] = str(uuid.uuid4())

    # When an anonymous user signs in, PostHog aliases the new authenticated user ID with the UUID.
    if current_user.is_authenticated and current_user.id != session.get(&quot;user_id&quot;):
        posthog.alias(session[&quot;user_id&quot;], current_user.id)
        session[&quot;user_id&quot;] = current_user.id</code></pre><p>In the code above, we use the&#xA0;<code>before_request</code>&#xA0;Flask hook to check if a session-level user ID exists. If not, we assign one.</p><p>For authenticated users, we use their unique&#xA0;<code>current_user.id</code>&#xA0;from Flask-Security-Too. If you are using a different library, just replace that by your user&apos;s ID that is stored in the database.</p><p>For anonymous users, we generate and assign a UUID (<code>str(uuid.uuid4())</code>).</p><p>When an anonymous user registers or logs in, we use&#xA0;<code>posthog.alias</code>&#xA0;to merge the UUID session with the authenticated ID.</p><p>In your template, you&apos;ll then want to run this:</p><pre><code class="language-html">&lt;!-- base.html --&gt;
&lt;head&gt;
    &lt;!-- Other head elements --&gt;

    &lt;!-- Start PostHog --&gt;
    &lt;script&gt;
        &lt;!-- PostHog initialization code --&gt;

        &lt;!-- PostHog user identification code --&gt;
        posthog.identify(&quot;{{ session[&apos;user_id&apos;] }}&quot;)
    &lt;/script&gt;
    &lt;!-- End PostHog --&gt;
&lt;/head&gt;</code></pre><p>With this, you have consistent tracking, so you know what your users do before and after they authenticate!</p><h3 id="identifying-registered-and-anonymous-users-with-posthog-and-django">Identifying registered and anonymous users with PostHog and Django</h3><p>Doing this with Django is similar to with Flask, but instead of using the&#xA0;<code>before_request</code>&#xA0;hook, you&apos;d write a middleware:</p><pre><code class="language-python">import uuid
import posthog

posthog.api_key = &apos;your-posthog-api-key&apos;
posthog.host = &apos;https://app.posthog.com&apos;  # or eu.posthog.com

def posthog_middleware(get_response):
	def middleware(request):
		if &quot;user_id&quot; not in request.session:
	        if request.user.is_authenticated:
	            request.session[&quot;user_id&quot;] = request.user.id
	        else:
	            request.session[&quot;user_id&quot;] = str(uuid.uuid4())

	    # When an anonymous user signs in, PostHog aliases the new authenticated user ID with the UUID.
	    if request.user.is_authenticated and request.user.id != session.get(&quot;user_id&quot;):
	        posthog.alias(session[&quot;user_id&quot;], request.user.id)
	        session[&quot;user_id&quot;] = request.user.id</code></pre><p>Then you can add this middleware to&#xA0;<code>MIDDLEWARE</code>&#xA0;in your&#xA0;<code>settings.py</code>&#xA0;file, making sure it is after other middlewares that must run first, such as for the session and authentication:</p><pre><code class="language-python">MIDDLEWARE = [
    # ... (other middleware entries)
    &apos;django.contrib.sessions.middleware.SessionMiddleware&apos;,
    &apos;django.middleware.common.CommonMiddleware&apos;,
    &apos;django.contrib.auth.middleware.AuthenticationMiddleware&apos;,

    # Add posthog_middleware after AuthenticationMiddleware
    &apos;path.to.your.application.posthog_middleware&apos;,

    # ... (other middleware entries)
]</code></pre><p>For more information on middleware, I strongly recommend reading the&#xA0;<a href="https://docs.djangoproject.com/en/4.2/topics/http/middleware/?ref=blog.teclado.com" rel="nofollow">official documentation</a>.</p><p>Then in your base template, you&apos;d again call&#xA0;<code>posthog.identify()</code>, passing your user ID.</p><h2 id="conclusion">Conclusion</h2><p>I really like PostHog, and it&apos;s super easy to get started with it! I use it for product analytics, web analytics, observing session replays, implementing feature flags, and A/B testing.</p><p>If you want to learn more about web development with Python and Flask, including web app design, HTML, and CSS, check out our&#xA0;<a href="https://go.tecla.do/web-dev-course-sale?ref=blog.teclado.com" rel="nofollow">Web Developer Bootcamp</a>. It&apos;s a complete course that teaches you how to build web applications!</p><p>In our next PostHog article we&apos;ll look at working with feature flags using PostHog. I&apos;ll link it below when it&apos;s published next week!</p>]]></content:encoded></item><item><title><![CDATA[How to: HTML contact form with Flask and Resend]]></title><description><![CDATA[Learn how to create an HTML contact form, add it to your Flask website, and have it send you an email when someone submits it!]]></description><link>https://blog.teclado.com/how-to-make-html-contact-form-flask-resend/</link><guid isPermaLink="false">6553898a394358e03c2f9374</guid><category><![CDATA[Flask]]></category><dc:creator><![CDATA[Leonardo Spairani]]></dc:creator><pubDate>Tue, 14 Nov 2023 16:37:35 GMT</pubDate><media:content url="https://blog.teclado.com/content/images/2023/11/glenn-carstens-peters-npxXWgQ33ZQ-unsplash-1.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.teclado.com/content/images/2023/11/glenn-carstens-peters-npxXWgQ33ZQ-unsplash-1.jpg" alt="How to: HTML contact form with Flask and Resend"><p>In this article, I will show you how to add a contact form to your Flask website using HTML.</p>
<p>But writing the HTML for the contact form is only half the job!</p>
<p>I will also show you how to <strong>send emails using Python</strong>, so that when a user submits the contact form you receive an email with the form contents.</p>
<p>Here is an example of what the HTML contact form will look like:</p>
<p><img src="https://res.cloudinary.com/teclado/image/upload/q_auto,f_auto/v1699974058/blog/2023-10-01-how-to-add-an-email-contact-form/contact_form_dspi35.png" alt="How to: HTML contact form with Flask and Resend" loading="lazy"></p>
<p>To make the contact form we will use HTML and optionally Bootstrap, and for the backend code we will use Flask and the <a href="https://resend.com/docs/api-reference/introduction?ref=blog.teclado.com">Resend API</a>.</p>
<p>Our app will have a unique endpoint called <code>/contact</code> where the user will be able to write their name, email address, and a message. When the user submits the contact form, an email will be sent to the website owner with the information submitted by the user.</p>
<h2 id="initial-setup-to-start-coding-the-flask-app">Initial setup to start coding the Flask app</h2>
<p>To begin, set up your Python development environment. Check out <a href="https://blog.teclado.com/how-to-set-up-visual-studio-code-for-python-development/">this blog post</a> if you&apos;re not sure how.</p>
<p>You&apos;ll also need a <a href="https://resend.com/signup?ref=blog.teclado.com">Resend account</a>. For what we&apos;re doing in this blog post, it&apos;s free.</p>
<p>As a first step, create a <a href="https://resend.com/api-keys?ref=blog.teclado.com">Resend API key</a>. Since we are only interested in sending emails, you can select the <code>Send emails</code> permission only. Make sure that you copy the API key, as you will not be able to see it again.</p>
<p>Once you have the API key, create a <code>.env</code> file in your project with the following contents:</p>
<pre><code class="language-txt">RESEND_API_KEY=&lt;Your Resend API Key&gt;
ADMIN_EMAIL=&lt;Your Resend email address&gt;
</code></pre>
<p>The <code>ADMIN_EMAIL</code> is the email address that will receive the contact form submissions. An important thing to consider is that unless you have a custom domain (costs money), you will only be able to send emails to the email address with which you signed up for Resend. They do this to prevent spam.</p>
<p>As always, if you are planning on uploading the code to a public repository, make sure to add the <code>.env</code> file to your <a href="https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files?ref=blog.teclado.com#configuring-ignored-files-for-a-single-repository"><code>.gitignore</code></a>  file.</p>
<p>Afterwards, create a <code>requirements.txt</code> file with the following content in your working directory:</p>
<pre><code class="language-txt">flask==3.0.0
python-dotenv==1.0.0
resend==0.5.2
</code></pre>
<p>Confirm that your virtual environment is activated, then, run the command:</p>
<pre><code class="language-bash">pip install -r requirements.txt
</code></pre>
<p>Finally, add a <code>.flaskenv</code> file with the following content:</p>
<pre><code class="language-txt">FLASK_DEBUG=1
</code></pre>
<p>This will ensure the app will reload when you make changes to the code.</p>
<h2 id="creating-a-basic-html-contact-form">Creating a basic HTML contact form</h2>
<p>In this step we will add a contact form with no styling (we will add styles later on).</p>
<p>To do this, create a <code>templates</code> folder and add a <code>user-contact-form-template.html</code> file with the following content:</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
  &lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot; /&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&gt;
    &lt;title&gt;Contact Form&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
        &lt;h2&gt;Contact Us&lt;/h2&gt;
        &lt;form action=&quot;/contact&quot; method=&quot;POST&quot;&gt;
          &lt;div&gt;
            &lt;label for=&quot;name&quot;&gt;Name&lt;/label&gt;
            &lt;input type=&quot;text&quot; name=&quot;name&quot; required/&gt;
          &lt;/div&gt;
          &lt;div&gt;
            &lt;label for=&quot;email&quot;&gt;Email&lt;/label&gt;
            &lt;input type=&quot;email&quot; name=&quot;email&quot; required/&gt;
          &lt;/div&gt;
          &lt;div&gt;
            &lt;label for=&quot;message&quot;&gt;Message&lt;/label&gt;
            &lt;textarea name=&quot;message&quot; rows=&quot;4&quot; required&gt;&lt;/textarea&gt;
          &lt;/div&gt;
          &lt;div&gt;
            &lt;button type=&quot;submit&quot;&gt;Submit&lt;/button&gt;
          &lt;/div&gt;
        &lt;/form&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<h2 id="creating-the-flask-app">Creating the Flask app</h2>
<h3 id="adding-the-imports">Adding the imports</h3>
<p>Now that we have all set up, we can start working on our app. Let&apos;s create an <code>app.py</code> file and add the imports:</p>
<pre><code class="language-python">import os

import resend
from flask import Flask, redirect, render_template, request
</code></pre>
<p>The <code>resend</code> package is the official Python client for the Resend API. We will use it to send emails.</p>
<p>From <code>flask</code> we import a few things:</p>
<ul>
<li>The <code>Flask</code> class to create a Flask app.</li>
<li><code>redirect</code> to send users to a different page when they submit the contact form.</li>
<li><code>render_template</code> to send our HTML to the user&apos;s browser.</li>
<li><code>request</code> to access the data submitted by the user.</li>
</ul>
<p>We&apos;ll use the <code>os</code> module to access the environment variables we defined in the <code>.env</code> file.</p>
<h3 id="loading-the-environment-variables-with-flask">Loading the environment variables with Flask</h3>
<p>When we have the <a href="https://github.com/theskumar/python-dotenv?ref=blog.teclado.com"><code>python-dotenv</code></a> package installed, Flask will automatically load the <code>.env</code> file when we start the app.</p>
<p>To access their values, we use <code>os.environ</code>:</p>
<pre><code class="language-python">resend.api_key = os.environ[&quot;RESEND_API_KEY&quot;]
ADMIN_EMAIL = os.environ[&quot;ADMIN_EMAIL&quot;]
</code></pre>
<p>After these lines, create a variable to store the HTML that will be sent to the site owner.</p>
<h3 id="creating-the-email-html-body">Creating the email HTML body</h3>
<pre><code class="language-python">CONTACT_EMAIL = &quot;&quot;&quot;
&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
    &lt;title&gt;New Message Notification&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;h2&gt;New Message Received!&lt;/h2&gt;
    &lt;p&gt;&lt;strong&gt;Name:&lt;/strong&gt; {name}&lt;/p&gt;
    &lt;p&gt;&lt;strong&gt;Email:&lt;/strong&gt; {email}&lt;/p&gt;
    &lt;p&gt;&lt;strong&gt;Message:&lt;/strong&gt;&lt;/p&gt;
    &lt;blockquote&gt;
        {message}
    &lt;/blockquote&gt;
&lt;/body&gt;
&lt;/html&gt;
&quot;&quot;&quot;
</code></pre>
<p>Later, we will use the <code>.format()</code> method to inject the variables <code>name</code>, <code>email</code>, and <code>message</code> into the HTML template.</p>
<div class="style-note">
    <h3 class="style-note-title">Security Warning</h3>
    <p class="style-note-content">
    <a href="https://owasp.org/www-community/attacks/xss/?ref=blog.teclado.com">For security reasons</a>, in a production environment, you should use a template engine like <a href="https://jinja.palletsprojects.com/en/3.0.x/?ref=blog.teclado.com">Jinja</a> to render HTML templates instead of using the <code>.format()</code> method.
    </p>
</div>
<h3 id="creating-the-flask-app-object">Creating the Flask app object</h3>
<p>Next, create the Flask app:</p>
<pre><code class="language-python">app = Flask(__name__)
</code></pre>
<h3 id="coding-the-flask-contact-form-endpoint">Coding the Flask contact form endpoint</h3>
<p>Our endpoint looks as follows:</p>
<pre><code class="language-python">@app.route(&quot;/contact&quot;, methods=[&quot;GET&quot;, &quot;POST&quot;])
def contact():
    if request.method == &quot;POST&quot;:
        # Load form data
        form_data = request.form.to_dict()

        # Configuring the email fields
        params = {
            &quot;from&quot;: &quot;Your Flask App &lt;onboarding@resend.dev&gt;&quot;,
            &quot;to&quot;: [ADMIN_EMAIL],
            &quot;subject&quot;: f&quot;New message from {form_data[&apos;name&apos;]}!&quot;,
            &quot;html&quot;: CONTACT_EMAIL.format(**form_data),
        }

        # Sending the email and catching response
        response = resend.Emails.send(params)

        # Handle the response
        if response.get(&quot;id&quot;):
            return redirect(&quot;/contact&quot;)
        else:
            return {&quot;message&quot;: &quot;Something went wrong. Please try again.&quot;}
    else:
        # Render the contact form
        return render_template(&quot;user-contact-form-template.html&quot;)
</code></pre>
<p>There are quite a lot of things going on here, so let&apos;s break it down!</p>
<p>As a first step, we check if the request method is <code>POST</code>. If it is, it means that the user submitted the contact form, so we can proceed to send the email. If it is not, it means that the user is trying to access the <code>/contact</code> endpoint, so we render the contact form:</p>
<pre><code class="language-python">@app.route(&quot;/contact&quot;, methods=[&quot;GET&quot;, &quot;POST&quot;])
def contact():
    if request.method == &quot;POST&quot;:
        # Handle the form submission ...
    else:
        # Render the contact form
        return render_template(&quot;user-contact-form-template.html&quot;)
</code></pre>
<p>If the request method is <code>POST</code>, we load the contact form data into a dictionary using <code>request.form.to_dict()</code>. The resulting dictionary will look like this:</p>
<pre><code class="language-python">{
    &apos;name&apos;: &apos;Some Name&apos;,
    &apos;email&apos;: &apos;example@email.com&apos;,
    &apos;message&apos;: &apos;Some message&apos;
}
</code></pre>
<p>Next, we create a new dictionary with the filled email fields that we need to send the email:</p>
<pre><code class="language-python">@app.route(&quot;/contact&quot;, methods=[&quot;GET&quot;, &quot;POST&quot;])
def contact():
    if request.method == &quot;POST&quot;:
        # Load form data
        form_data = request.form.to_dict()

        # Configuring the email fields
        params = {
            &quot;from&quot;: &quot;Your Flask App &lt;onboarding@resend.dev&gt;&quot;,
            &quot;to&quot;: [ADMIN_EMAIL],
            &quot;subject&quot;: f&quot;New message from {form_data[&apos;name&apos;]}!&quot;,
            &quot;html&quot;: CONTACT_EMAIL.format(**form_data),
        }
    else:
        # Render the contact form
        return render_template(&quot;user-contact-form-template.html&quot;)
</code></pre>
<ul>
<li><code>from</code>: since we haven&apos;t configured a DNS, we can only send emails with the default resend account <code>onboarding@resend.dev</code>.</li>
<li><code>to</code>: it contains the administrator email loaded from the environment variable.</li>
<li><code>subject</code>: we create a custom title by using f-strings to inject the name loaded from the for data.</li>
<li><code>html</code>: contains the HTML body of the email. We use the <code>.format()</code> method with keyword arguments provided by the dictionary keys to inject the custom content.</li>
</ul>
<p>After filling in the email data, we send it using Resend API and catch the response:</p>
<pre><code class="language-python">@app.route(&quot;/contact&quot;, methods=[&quot;GET&quot;, &quot;POST&quot;])
def contact():
    if request.method == &quot;POST&quot;:
        # Load form data
        form_data = request.form.to_dict()

        # Configuring the email fields
        params = {
            &quot;from&quot;: &quot;Your Flask App &lt;onboarding@resend.dev&gt;&quot;,
            &quot;to&quot;: [ADMIN_EMAIL],
            &quot;subject&quot;: f&quot;New message from {form_data[&apos;name&apos;]}!&quot;,
            &quot;html&quot;: CONTACT_EMAIL.format(**form_data),
        }

        # Sending the email and catching response
        response = resend.Emails.send(params)

        # Handle the response
        if response.get(&quot;id&quot;):
            return redirect(&quot;/contact&quot;)
        else:
            return {&quot;message&quot;: &quot;Something went wrong. Please try again.&quot;}
    else:
        # Render the contact form
        return render_template(&quot;user-contact-form-template.html&quot;)
</code></pre>
<h2 id="exploring-the-resend-dashboard">Exploring the Resend dashboard</h2>
<p>Resend has a really helpful user interface where we can see information related to the emails we sent, here is an example of the overview menu:</p>
<p><img src="https://res.cloudinary.com/teclado/image/upload/q_auto,f_auto/v1699974059/blog/2023-10-01-how-to-add-an-email-contact-form/resend_ui_overview_menu_fslviu.png" alt="How to: HTML contact form with Flask and Resend" loading="lazy"></p>
<p>And another example of how the requests and responses look like from Render&apos;s side:</p>
<p><img src="https://res.cloudinary.com/teclado/image/upload/q_auto,f_auto/v1699974059/blog/2023-10-01-how-to-add-an-email-contact-form/resend_ui_email_log_example_iuklqi.png" alt="How to: HTML contact form with Flask and Resend" loading="lazy"></p>
<h3 id="filling-in-the-html-contact-form">Filling in the HTML contact form</h3>
<p>Now that we have put all the pieces together, we can go to the terminal and run flask with the command:</p>
<pre><code class="language-bash">flask run
</code></pre>
<p>Then, navigate to the URL <code>http://localhost:5000/contact</code>, fill out the HTML contact form, and check out your mail inbox!</p>
<p>Remember that since we are using Resend&apos;s testing service, we can only send emails to the address we registered with.</p>
<h2 id="improve-our-html-contact-form-with-bootstrap">Improve our HTML contact form with Bootstrap</h2>
<p>To add Bootstrap 5 styles to our HTML contact form as shown in the first screenshot, replace the contents of the <code>user-contact-form-template.html</code> with the following code:</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
  &lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot; /&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&gt;
    &lt;title&gt;Contact Form&lt;/title&gt;
    &lt;!-- Bootstrap CSS --&gt;
    &lt;link
      href=&quot;https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css&quot;
      rel=&quot;stylesheet&quot;
      integrity=&quot;sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC&quot;
      crossorigin=&quot;anonymous&quot;
    /&gt;

    &lt;style&gt;
      /* You can add additional custom styles here if needed */
      .contact-form {
        max-width: 600px;
        margin: 50px auto;
        padding: 20px;
        border: 1px solid #ccc;
        box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
      }
    &lt;/style&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div class=&quot;container&quot;&gt;
      &lt;div class=&quot;contact-form&quot;&gt;
        &lt;h2&gt;Contact Us&lt;/h2&gt;
        &lt;form action=&quot;/contact&quot; method=&quot;POST&quot;&gt;
          &lt;div class=&quot;mb-3&quot;&gt;
            &lt;label for=&quot;name&quot; class=&quot;form-label&quot;&gt;Name&lt;/label&gt;
            &lt;input
              type=&quot;text&quot;
              class=&quot;form-control&quot;
              id=&quot;name&quot;
              name=&quot;name&quot;
              required
            /&gt;
          &lt;/div&gt;
          &lt;div class=&quot;mb-3&quot;&gt;
            &lt;label for=&quot;email&quot; class=&quot;form-label&quot;&gt;Email&lt;/label&gt;
            &lt;input
              type=&quot;email&quot;
              class=&quot;form-control&quot;
              id=&quot;email&quot;
              name=&quot;email&quot;
              required
            /&gt;
          &lt;/div&gt;
          &lt;div class=&quot;mb-3&quot;&gt;
            &lt;label for=&quot;message&quot; class=&quot;form-label&quot;&gt;Message&lt;/label&gt;
            &lt;textarea
              class=&quot;form-control&quot;
              id=&quot;message&quot;
              name=&quot;message&quot;
              rows=&quot;4&quot;
              required
            &gt;&lt;/textarea&gt;
          &lt;/div&gt;
          &lt;div class=&quot;mb-3&quot;&gt;
            &lt;button type=&quot;submit&quot; class=&quot;btn btn-primary&quot;&gt;Submit&lt;/button&gt;
          &lt;/div&gt;
        &lt;/form&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<h2 id="conclusion">Conclusion</h2>
<p>In this article we showed you how to send emails with the Resend API and Flask. We created an HTML contact form, processed the form data, populated the email fields and body with the form data, sent the email, handled the response, and finally we added custom Bootstrap 5 styling.</p>
<p>You can learn a lot more about web development with Flask with our course, <a href="https://go.tecla.do/web-dev-course-sale?ref=blog.teclado.com">Web Developer Bootcamp with Flask and Python</a>. In it, we cover how to use Flask, HTML, and CSS, to build 4 different projects of increasing complexity. Check it out!</p>
]]></content:encoded></item><item><title><![CDATA[Build your first REST API with FastAPI]]></title><description><![CDATA[Get started with FastAPI by building a time-tracking API, including database connectivity, Pydantic models, GET and POST requests, and more!]]></description><link>https://blog.teclado.com/build-your-first-rest-api-with-fastapi/</link><guid isPermaLink="false">64b81a838556516e5397763f</guid><category><![CDATA[REST APIs]]></category><dc:creator><![CDATA[Jose Salvatierra]]></dc:creator><pubDate>Mon, 31 Jul 2023 11:39:20 GMT</pubDate><media:content url="https://blog.teclado.com/content/images/2023/07/08_fastApi-beginner_72_Tavola-disegno-1.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.teclado.com/content/images/2023/07/08_fastApi-beginner_72_Tavola-disegno-1.jpg" alt="Build your first REST API with FastAPI"><p>In this blog post, I&apos;ll show you how to build your first API with FastAPI, using <a href="https://docs.pydantic.dev/latest/?ref=blog.teclado.com">pydantic</a> and <a href="https://github.com/encode/databases?ref=blog.teclado.com">encode/databases</a>. Our project will be a time-tracking API.</p><p>No time to waste! Let&apos;s build an API fast, with FastAPI &#x1F680;</p><h2 id="project-overview-and-set-up">Project overview and set-up</h2><p>Our API will be simple, it will have 3 endpoints:</p><ul><li><code>POST /track</code> will receive <code>user_id</code> and <code>description</code>. It creates a new task and starts a timer for it.</li><li><code>POST /stop</code> will receive an <code>id</code> and stop the timer for that task.</li><li><code>GET /times</code> will receive a <code>user_id</code> and <code>date</code> (e.g. <code>2023-07-16</code>) and will respond with a list of tasks and the time spent in each, in that day.</li></ul><p>Our database will have a single table, <code>tasks</code>, with the following columns:</p><ul><li><code>id</code>, an auto-generated integer ID for the task.</li><li><code>description</code>, a textual description of the task.</li><li><code>user_id</code>, the integer ID of the user.</li><li><code>start_time</code>, the time the task was started.</li><li><code>end_time</code>, the time the task was stopped.</li></ul><p>First things first, create a virtual environment for your project:</p><pre><code class="language-bash">python3 -m venv .venv
</code></pre><p>Then activate it (the command differs on Windows):</p><pre><code class="language-bash">source .venv/bin/activate
</code></pre><p>Then let&apos;s create a file called <code>requirements.txt</code>, and put in all the libraries we need:</p><pre><code class="language-text">fastapi
uvicorn
sqlalchemy
databases[sqlite]
pydantic
python-dotenv
</code></pre><p>To install all of these, run:</p><pre><code class="language-bash">pip install -r requirements.txt
</code></pre><h2 id="how-to-define-database-tables-using-sqlalchemy">How to define database tables using SQLAlchemy</h2><p>SQLAlchemy is an ORM, which means that instead of sending SQL queries to the database and receiving rows of data back, we deal with Python classes and objects.</p><p>The first step is to define the tables of data that our application needs.</p><p>Write this code in <code>database.py</code>:</p><pre><code class="language-python">import databases
import sqlalchemy

# Define the URL for our SQLite database. This is a file path which makes
# the database in a file called data.db in the current directory.
DATABASE_URL = &quot;sqlite:///data.db&quot;

# MetaData is a container object that keeps together different features of a database being described.
metadata = sqlalchemy.MetaData()

task_table = sqlalchemy.Table(
    &quot;tasks&quot;,
    metadata,
    sqlalchemy.Column(&quot;id&quot;, sqlalchemy.Integer, primary_key=True),
    sqlalchemy.Column(&quot;user_id&quot;, sqlalchemy.Integer),
    sqlalchemy.Column(&quot;start_time&quot;, sqlalchemy.DateTime),
    sqlalchemy.Column(&quot;end_time&quot;, sqlalchemy.DateTime)
)

# Create an engine that stores data in the local directory&apos;s data.db file. 
# &quot;check_same_thread&quot; is set to False as SQLite connections are not thread-safe by default. 
engine = sqlalchemy.create_engine(
    DATABASE_URL, connect_args={&quot;check_same_thread&quot;: False}
)

# Create all tables stored in this metadata in the actual file.
metadata.create_all(engine)

# Initialize a database connection.
database = databases.Database(DATABASE_URL)
</code></pre><h2 id="how-to-set-up-our-fastapi-app">How to set up our FastAPI app</h2><p>The start of every FastAPI always looks similar! We create the <code>FastAPI</code> object, and then we can start setting up the routes or endpoints.</p><p>When clients make requests to one of our endpoints, we trigger a Python function that can perform some actions and respond with some data. That response data is then sent to the client.</p><p>Let&apos;s set up our FastAPI app in <code>main.py</code>:</p><pre><code class="language-python">from fastapi import FastAPI

app = FastAPI()
</code></pre><p>That&apos;s the FastAPI set-up! Now we need to do two things:</p><ol><li>Connect to our database.</li><li>Write our endpoint functions.</li></ol><h2 id="how-to-connect-to-our-database-using-fastapi-lifespan-events">How to connect to our database using FastAPI lifespan events</h2><p>We can configure a <em>lifespan event</em> in FastAPI apps, which is a function that can perform some set-up before the FastAPI app starts running, and some tear-down after the FastAPI app finishes running.</p><p>We will use it to connect to our database.</p><p>To write the lifespan event, we write an <code>async</code> context manager, which is an <code>async</code> function decorated with <code>@asynccontextmanager</code>. This function must include a <code>yield</code> statement.</p><p>Any code <em>before</em> the <code>yield</code> will run before the FastAPI app starts. During the <code>yield</code>, the FastAPI app runs. This can take seconds, or much longer--even days! The code after the <code>yield</code> is our tear-down code, which runs after the FastAPI app finishes running.</p><p>Write this code in the <code>main.py</code> file:</p><pre><code class="language-python">from contextlib import asynccontextmanager

from database import database
from fastapi import FastAPI


@asynccontextmanager
async def lifespan(app: FastAPI):
    await database.connect()
    yield
    await database.disconnect()


app = FastAPI(lifespan=lifespan)
</code></pre><!--kg-card-begin: markdown--><div class="style-note">
    <h3 class="style-note-title">Enjoying this blog post?</h3>
<p>Check out our brand new comprehensive course!</p>
<p>In <a href="https://teclado.com/fastapi-101/?ref=blog.teclado.com">FastAPI 101</a>, I&apos;ll guide you through building a complex FastAPI app including user authentication, database relationships, file uploads, logging, testing, and much more. Join today or take our subscription to unlock all of our courses!</p>
<!--kg-card-end: markdown--><div class="kg-card kg-button-card kg-align-left"><a href="https://teclado.com/fastapi-101?utm_source=blog" class="kg-btn kg-btn-accent">Join FastAPI 101</a></div><!--kg-card-begin: markdown--><p></p>
</div><!--kg-card-end: markdown--><h3></h3><h2 id="the-pydantic-models-we-need-for-data-validation">The Pydantic models we need for data validation</h2><p>FastAPI uses <a href="https://docs.pydantic.dev/latest/?ref=blog.teclado.com">Pydantic</a> models to automatically handle request validation, serialization, and documentation.</p><p>When clients send us data, we will pass this data through a Pydantic model to ensure it contains what we need. So if we need clients to send us a <code>user_id</code> and the <code>id</code> of the task, then we can write a Pydantic model for that.</p><p>We will need four Pydantic models:</p><ul><li><code>NewTaskItem</code>, for when clients want to create a task. It ensures clients send us <code>user_id</code>, an integer, and <code>description</code>, a textual description of the task.</li><li><code>TaskItem</code>, for when clients want to stop a task. It ensures clients send us <code>id</code>, an integer.</li><li><code>TaskOut</code>, for when we want to respond with a task representation. We will use this model to ensure that our response has the right content. It ensures the response contains <code>id</code>, an integer, <code>description</code>, a string, and <code>time_spent</code>, a float.</li></ul><p>Write this code in a <code>models.py</code> file:</p><pre><code class="language-python">from pydantic import BaseModel


class NewTaskItem(BaseModel):
    user_id: int
    description: str


class TaskItem(BaseModel):
    id: int


class TaskOut(BaseModel):
    id: int
    description: str
    time_spent: float
</code></pre><p>But up to now it may not be very clear <em>how</em> these models are used. Let&apos;s get into that with our first endpoint!</p><h3 id="writing-an-endpoint-to-start-tracking-a-task">Writing an endpoint to start tracking a task</h3><p>Here&apos;s our first endpoint. We&apos;ll add some code to <code>main.py</code>.</p><p>There are some new imports, add those at the top of the file. The rest of the code goes at the bottom of the file.</p><pre><code class="language-python">from datetime import datetime

from models import NewTaskItem
from database import task_table

...

@app.post(&quot;/track&quot;, response_model=TaskItem)
async def track(task: NewTaskItem):
    data = {**task.model_dump(), &quot;start_time&quot;: datetime.now()}
    query = task_table.insert().values(data)
    last_record_id = await database.execute(query)

    return {&quot;id&quot;: last_record_id}
</code></pre><p>When clients want to create a task, they&apos;ll send a request to our <code>/track</code> endpoint. They need to include the data that the <code>TaskItem</code> expects <strong>in the JSON payload of the request</strong>.</p><p>Pydantic will take that data and give us a <code>TaskItem</code> object. We then turn it into a dictionary using <code>.model_dump()</code> so we can insert it into our database. To that dictionary, we add the <code>start_time</code>.</p><p>To create a query to insert data, we use <code>task_table.insert().values(data)</code>.</p><p>The <code>id</code> column is an integer primary key, which means it is auto-generated by the database when we create a new row.</p><p>When we execute an insert query, we get back the ID of the row we just inserted. We send that to the client so they can use it to stop the task later on.</p><h3 id="writing-an-endpoint-to-stop-tracking-a-task">Writing an endpoint to stop tracking a task</h3><p>Just like the <code>/track</code> endpoint, our <code>/stop</code> endpoint needs to accept some data from the client, so we include a parameter with a Pydantic model.</p><p>In this case, we need to accept <code>TaskItem</code>, which includes <code>id</code>. We can then use this information to stop the task by updating its <code>end_time</code> in the database.</p><p>Let&apos;s update our <code>main.py</code> to include this endpoint:</p><pre><code class="language-python">from models import TaskItem

...

@app.post(&quot;/stop&quot;, response_model=TaskItem)
async def stop(task: TaskItem):
    end_time = datetime.now()
    query = (
        task_table.update().where(task_table.c.id == task.id).values(end_time=end_time)
    )
    await database.execute(query)
    return task
</code></pre><p>When a client sends a request to stop a task, they will include the task&apos;s ID. Pydantic will ensure that the field is present and is an integer, then provide us with a <code>TaskItem</code> object.</p><p>We then create a <code>datetime</code> object for the current time, which we will use as the <code>end_time</code> for the task.</p><p>To update a row in the table, we use the <code>update</code> method, specifying the row we want to update using the <code>where</code> method, and then the data we want to update using the <code>values</code> method.</p><h3 id="writing-an-endpoint-to-get-the-times-worked-on-a-day">Writing an endpoint to get the times worked on a day</h3><p>When the client wants to get the tasks worked on a specific day, they need to send a <code>GET</code> request to the <code>/times</code> endpoint, with <code>user_id</code> and <code>date</code> as query parameters.</p><p>We calculate the start and end times of the day the client is interested in and then use these times to filter the tasks from the database.</p><p>Here&apos;s an example of what this URL might look like: <code>http://our-api-domain.com/times?user_id=42&amp;date=2023-07-18</code>.</p><p>In this example, the client is requesting the tasks for the user with ID 42 on July 18th, 2023.</p><p>For FastAPI to take the query parameters from the URL, we need to add parameters to our function that aren&apos;t a Pydantic model (only <code>int</code> and <code>str</code> are supported for query string arguments).</p><p>Here&apos;s the code:</p><pre><code class="language-python">from datetime import timedelta, time

from models import GetTimesItem, TaskOut

...

@app.get(&quot;/times&quot;, response_model=list[TaskOut])
async def get_times(user_id: int, date: str):
    # Using `int` and `str` parameters instead of Pydantic models
    # tells FastAPI we want to get these values from the query string.
    selected_date = datetime.strptime(date, &quot;%Y-%m-%d&quot;).date()
    start_of_day = datetime.combine(selected_date, time.min)
    end_of_day = datetime.combine(selected_date, time.max)

    query = task_table.select().where(
        (task_table.c.user_id == user_id)
        &amp; (task_table.c.start_time &lt;= end_of_day)
        &amp; ((task_table.c.end_time &gt;= start_of_day) | (task_table.c.end_time.is_(None)))
    )
    tasks = await database.fetch_all(query)

    result = []
    for task in tasks:
        end_time = task[&quot;end_time&quot;]
        if task[&quot;end_time&quot;] is None:
            end_time = datetime.now()

        actual_start = max(task[&quot;start_time&quot;], start_of_day)
        actual_end = min(end_time, end_of_day)
        duration = actual_end - actual_start

        result.append({**task, &quot;time_spent&quot;: duration.total_seconds()})

    return result
</code></pre><p>Here, <code>start_of_day</code> and <code>end_of_day</code> mark the beginning and end of the day the user wants the time tracking for. By using <code>datetime.combine</code>, we can combine a date and a time. The first argument, the date, is given by the user. The second argument, <code>time.min</code> or <code>time.max</code> is the minimum or maximum value a time can be (<code>00:00:00</code> or <code>23:59:59</code> respectively).</p><p>The <code>select</code> query fetches tasks from the database where the <code>user_id</code> matches, the <code>start_time</code> is before the end of the day and either <code>end_time</code> is after the start of the day or is not set (indicating an ongoing task).</p><p>In the loop, for each task, if <code>end_time</code> is not set, it&apos;s assigned the current time as the task is still ongoing. We then find the actual start and end times by clamping the task&apos;s start and end times to the start and end of the day. We calculate the duration by subtracting <code>actual_start</code> from <code>actual_end</code> and append the task to the <code>result</code> list.</p><p>Finally, we return the <code>result</code> list, which is a list of dictionaries containing all the task data and <code>time_spent</code> for each task.</p><p>This is by far the most complicated function in our application!</p><h2 id="how-to-get-a-deployed-postgresql-database-from-elephantsql">How to get a deployed PostgreSQL database from ElephantSQL</h2><p>ElephantSQL is a PostgreSQL database hosting service. We can use it to get a deployed database for free. Here&apos;s how to get a database up and running:</p><ol><li>Go to the ElephantSQL website and create an account.</li><li>Once logged in, click on &quot;Create new instance&quot;.</li><li>Choose the free plan, which gives you 20MB storage (good enough for a small project or for learning).</li><li>Give your instance a name and select a region close to where your users will be.</li><li>Click on &quot;Select plan&quot; at the bottom to create your instance.</li><li>Once created, click on the instance name in your dashboard to view its details.</li><li>In the details view, you will find your URL (in the format <code>postgresql://user:password@host:port/dbname</code>). This is your database URL, which you can plug into your application to connect to this database.</li></ol><h2 id="how-to-connect-to-your-deployed-database-securely">How to connect to your deployed database securely</h2><p>It&apos;s not a good idea to hardcode the database connection string directly into your code, as this can pose significant security risks. For example, if your code is shared publicly (e.g., on a GitHub repository), anyone who has access to your code would also have access to your database!</p><p>A safer practice is to use environment variables to store sensitive information. An environment variable is a value that&apos;s available to your application&apos;s environment, and it can be read by your application. This way, you can share your code without exposing any sensitive information, as the actual credentials will be stored safely in the environment, not in the code.</p><p>To manage environment variables, we&apos;ll use the <code>python-dotenv</code> module, which allows you to specify environment variables in a <code>.env</code> file. This can be particularly handy during development, as it allows you to replicate the environment variables that will be used in production.</p><p>Create a <code>.env</code> file in the root of your project, and add the database connection string as an environment variable in the <code>.env</code> file:</p><pre><code class="language-text">DATABASE_URL=&quot;postgresql://username:password@hostname/databasename&quot;
</code></pre><p>Then, in your <code>database.py</code> file, use <code>os.getenv</code> to get the value of the <code>DATABASE_URL</code> environment variable. Make sure to import the <code>os</code> module at the top of the file:</p><pre><code class="language-python">import os
import sqlalchemy
import databases

DATABASE_URL = os.getenv(&quot;DATABASE_URL&quot;)

metadata = sqlalchemy.MetaData()

# Define your tables here...

# You don&apos;t need check_same_thread=False when using PostgreSQL.
engine = sqlalchemy.create_engine(DATABASE_URL)

metadata.create_all(engine)
database = databases.Database(DATABASE_URL)
</code></pre><p>Now, when your application runs, it will read the <code>DATABASE_URL</code> from the environment variables, either from the <code>.env</code> file in a development environment or directly from the system environment in a production environment.</p><h2 id="ensuring-the-env-file-doesnt-end-up-in-github-either">Ensuring the <code>.env</code> file doesn&apos;t end up in GitHub either</h2><p>Now that we&apos;ve moved the database connection string out of our code, we need to make sure that our <code>.env</code> file doesn&apos;t end up being publicly available, as that would defeat the point!</p><p>We need to make sure that the <code>.env</code> file doesn&apos;t end up in GitHub, and the best way to do that is to <em>never commit it or upload it</em>.</p><p>A good way to prevent accidentally committing the <code>.env</code> file to GitHub is by creating another file called <code>.gitignore</code>, and inside it putting this text:</p><pre><code class="language-text">.env
</code></pre><p>This tells Git to never include a file called <code>.env</code> in any commits.</p><h2 id="how-to-deploy-your-fastapi-app-to-rendercom">How to deploy your FastAPI app to Render.com</h2><ol><li><strong>Push Your Code to a Git Repository</strong>: Render uses Git repositories to fetch and build applications. Push your code to a repository on a service like GitHub.</li><li><strong>Create a New Web Service on Render</strong>: Go to the Render dashboard and click on the &quot;+ New&quot; button, then select &quot;Web Service.&quot;</li><li><strong>Select Your Repository</strong>: You&apos;ll be asked to provide a repository where your FastAPI project is hosted. Choose your repository and branch.</li><li><strong>Configure Your Service</strong>: You will need to specify some settings for your application:</li></ol><ul><li><strong>Build Command</strong>: This is the command Render will run to install your dependencies. If you&apos;re using Pipenv, this will be <code>pipenv install</code>. If you&apos;re using poetry, this will be <code>poetry install</code>. If you&apos;re using <code>requirements.txt</code>, it will be <code>pip install -r requirements.txt</code>.</li><li><strong>Start Command</strong>: This is the command to start your FastAPI server. Typically, it&apos;s <code>uvicorn main:app --host 0.0.0.0 --port 10000</code>. Replace <code>main:app</code> with the location of your FastAPI app if it&apos;s not in <code>main.py</code> at the root of your repository.</li></ul><ol><li><strong>Advanced Settings</strong>: Our application requires environment variables, so let&apos;s add one with the name of <code>DATABASE_URL</code> and the value of our ElephantSQL connection string.</li><li><strong>Deploy</strong>: Click &quot;Save &amp; Deploy&quot; to create your service. Render will pull your code, install your dependencies, and start your service. Your service logs will be displayed, helping you monitor the deployment process.</li></ol><p>Your FastAPI application should now be up and running on Render! The URL of your application will be in the format <code>&lt;service-name&gt;.onrender.com</code>.</p><h2 id="conclusion">Conclusion</h2><p>We&apos;ve walked you through building a simple yet practical time-tracking API with FastAPI and SQLAlchemy. This application can track tasks, stop tasks, and retrieve the time spent on tasks for a user on a given day.</p><p>If you&apos;re looking to take your FastAPI knowledge to the next level, check out our comprehensive <a href="https://teclado.com/fastapi-101/?ref=blog.teclado.com">FastAPI 101 course</a>.</p><p>In this course, we cover how to build a complete, complex FastAPI project using pydantic, sqlalchemy, and more. We&apos;ll even guide you through implementing email confirmation, file uploads, and other advanced features. Join us in <a href="https://teclado.com/fastapi-101/?ref=blog.teclado.com">FastAPI 101</a> to make the most of FastAPI&apos;s speed and simplicity while building robust, production-ready applications.</p>]]></content:encoded></item><item><title><![CDATA[Introduction to pytest: A Beginner's Guide]]></title><description><![CDATA[Add tests to your Python code with pytest. Let me show you how to write simple, effective tests!]]></description><link>https://blog.teclado.com/pytest-for-beginners/</link><guid isPermaLink="false">641c711f8556516e53975e54</guid><category><![CDATA[Learn Python Programming]]></category><dc:creator><![CDATA[Jose Salvatierra]]></dc:creator><pubDate>Mon, 03 Apr 2023 09:58:49 GMT</pubDate><media:content url="https://blog.teclado.com/content/images/2023/03/06_pytest.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.teclado.com/content/images/2023/03/06_pytest.jpg" alt="Introduction to pytest: A Beginner&apos;s Guide"><p>Welcome to our beginner&apos;s guide on pytest! pytest is a powerful and easy-to-use testing framework in Python. In this blog post, we&apos;ll introduce you to the essential features of pytest, using code examples and explanations.</p>
<div class="table-of-contents">
<p>Table of contents</p>
<ul>
<li><a href="#how-to-install-and-run-pytest">How to install and run Pytest</a></li>
<li><a href="#test-outcomes-in-pytest">Test outcomes in pytest</a>
<ul>
<li><a href="#expected-failure-in-pytest-with-xfail">Expected failure in pytest with xfail</a></li>
<li><a href="#how-to-skip-tests-with-pytest">How to skip tests with pytest</a></li>
<li><a href="#the-error-test-outcome-in-pytest">The error test outcome in pytest</a></li>
</ul>
</li>
<li><a href="#how-to-write-pytest-tests-with-a-better-example">How to write Pytest tests (with a better example)</a>
<ul>
<li><a href="#the-given-when-then-structure-for-tests">The Given-When-Then structure for tests</a></li>
<li><a href="#how-to-write-tests-for-functions-that-process-data">How to write tests for functions that process data</a></li>
<li><a href="#testing-for-expected-exceptions">Testing for expected exceptions</a></li>
</ul>
</li>
<li><a href="#testing-a-data-store-using-pytest-and-fixtures">Testing a data store using pytest and fixtures</a>
<ul>
<li><a href="#using-fixtures-with-pytest">Using fixtures with pytest</a></li>
<li><a href="#setup-and-teardown-in-pytest-fixtures">Setup and teardown in Pytest fixtures</a></li>
<li><a href="#how-to-use-multiple-fixtures-in-a-pytest-test">How to use multiple fixtures in a Pytest test</a></li>
<li><a href="#tracing-fixture-execution-and-finding-where-fixtures-are-defined">Tracing fixture execution and finding where fixtures are defined</a></li>
</ul>
</li>
<li><a href="#conclusion">Conclusion</a></li>
</ul>
</div>
<p>If you&apos;d rather watch the YouTube video for this article, check it out below!</p>
<iframe src="https://www.youtube.com/embed/Kt6QqGoAlvI?si=DLLIKSMHb7mlaT3N" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
<p>When we write code&#x2014;let&apos;s say one function&#x2014;it&apos;s easy to focus on just the one situation where we will use it. When that happens, we tend to forget about what will happen when we use the function in other circumstances, and also on how the function operates with the data available in our program.</p>
<p>What I mean to say (please bear with the super-simplified example) is we may write a function like this:</p>
<pre><code class="language-python">def divide(x, y):
    return x / y
</code></pre>
<p>And forget that the function may be called with <code>y=0</code>, which will result in an error.</p>
<p>Writing tests as we go along (or even before we write the code) can help put us in a mindset of &quot;what do I want this function to do when given certain inputs?&quot; and this moves from an implementation mindset into a more mathematical mindset.</p>
<p>In this article, I&apos;ll show you how to use pytest to write tests for your Python code!</p>
<h2 id="how-to-install-and-run-pytest">How to install and run Pytest</h2>
<p>To install pytest, just run:</p>
<pre><code class="language-python">pip install pytest
</code></pre>
<p>Remember that if you&apos;re using pytest in a project, you should first <a href="https://blog.teclado.com/python-virtual-environments-complete-guide/">create a virtual environment</a>. I also recommend <a href="https://blog.teclado.com/how-to-use-pyenv-manage-python-versions/">working with pyenv</a> so you can easily use multiple different Python versions in your computer.</p>
<p>Whenever you start a new project, I recommend using the latest Python version available (that is compatible with the libraries you plan to use) and always creating a virtual environment for the project.</p>
<p>Now that you&apos;ve got your virtual environment created and pytest installed, let&apos;s write our first test! In a new file called <code>test_simple.py</code> (naming is important, should start with <code>test_</code>):</p>
<pre><code class="language-python">def test_simple_add():
    assert 1 + 1 == 2
</code></pre>
<p>Note the naming here is <em>also</em> important. Pytest tests are functions, and the functions names should start with <code>test_</code>.</p>
<p>Pytest uses Python assertions, so it&apos;s super simple to get started. In Python, <code>1 + 1 == 2</code> evaluates to the boolean value <code>True</code>, so <code>assert True</code> will pass and therefore your test will pass. Doing <code>assert False</code> will raise an <code>AssertionError</code> which would cause the test to fail. Simple, right?</p>
<p>To run all your tests (just 1, for now), activate your virtual environment and then run:</p>
<pre><code class="language-bash">pytest
</code></pre>
<p>You should see something like this output (among potentially a few other things):</p>
<pre><code class="language-TeXt">platform darwin -- Python 3.11.0, pytest-7.2.2, pluggy-1.0.0
rootdir: /Users/josesalvatierra/Documents/Teclado/Blog/posts/2023/02-07-testing-with-pytest/project
collected 1 item                                                                 

test_simple.py .           [100%]

=== 1 passed in 0.00s ===
</code></pre>
<p>The main thing there is:</p>
<pre><code class="language-TeXt">test_simple.py .           [100%]
</code></pre>
<p>This tells you that in the file <code>test_simple.py</code> there was 1 passing test (denoted by the <code>.</code>). The <code>[100%]</code> at the end tells us that 100% of the tests have ran.</p>
<p>If you have many files, the file names and the <code>.</code> (or other characters, if the test doesn&apos;t pass) will appear as the tests run. Similarly the percentages will add up over a few (or many) lines until you reach 100%.</p>
<p>If you want to run a specific file because you have multiple files, you could always run:</p>
<pre><code class="language-TeXt">pytest test_simple.py
</code></pre>
<p>And if you want to run a specific test, you can do so like this:</p>
<pre><code class="language-TeXt">pytest -k test_simple_add
</code></pre>
<p>The <code>-k</code> flag must be my most-used one. Any tests that contain the string <code>test_simple_add</code> in their function name will run. At the moment it&apos;s just one, but you can see how you could do for example <code>pytest -k register</code> to run all tests related to user registration, for example.</p>
<p>Let&apos;s write another test:</p>
<pre><code class="language-python">def test_simple_add():
    assert 1 + 1 == 2


def test_failing():  # will fail
    assert &apos;a&apos; == &apos;b&apos;
</code></pre>
<p>This new test will fail. When you run <code>pytest</code>, you&apos;ll see this output (I&apos;ll skip showing you the printed boilerplate from now on):</p>
<pre><code class="language-TeXt">test_simple.py .F                                             [100%]

===== FAILURES =====
_____ test_failing _____

    def test_failing():
&gt;       assert &apos;a&apos; == &apos;b&apos;
E       AssertionError: assert &apos;a&apos; == &apos;b&apos;
E         - b
E         + a

test_simple.py:6: AssertionError
===== short test summary info =====
FAILED test_simple.py::test_failing - AssertionError: assert &apos;a&apos; == &apos;b&apos;
</code></pre>
<p>Now this is quite handy! You can see that in <code>test_simple.py</code> there is one passing test and one failing test with <code>.F</code>.</p>
<p>At the end of the test run, pytest shows you which tests have failed, and when there is an assertion that failed, it shows you the values and what the expectation was. Here we had <code>&apos;b&apos;</code>, but the assertion expected to see <code>&apos;a&apos;</code> on the right side.</p>
<h2 id="test-outcomes-in-pytest">Test outcomes in pytest</h2>
<p>This actually brings us nicely to the different test outcomes. So far we&apos;ve seen:</p>
<ul>
<li><code>PASSED</code>, denoted by <code>.</code></li>
<li><code>FAILED</code>, denoted by <code>F</code></li>
</ul>
<p>But there are others too:</p>
<ul>
<li><code>SKIPPED (s)</code></li>
<li><code>XPASS (X)</code>, a.k.a. &quot;unexpectedly passed&quot;</li>
<li><code>ERROR (E)</code></li>
</ul>
<h3 id="expected-failure-in-pytest-with-xfail">Expected failure in pytest with xfail</h3>
<p>Let&apos;s say that for whatever reason, we want to keep our second test but we want to tell pytest that it should fail. That is, that &quot;success&quot; for that test means &quot;it fails&quot;.</p>
<p>We can mark it with a <a href="https://blog.teclado.com/decorators-in-python/">decorator</a>:</p>
<pre><code class="language-python">import pytest


def test_simple_add():
    assert 1 + 1 == 2


@pytest.mark.xfail
def test_failing():
    assert &apos;a&apos; == &apos;b&apos;
</code></pre>
<p>Running these tests will now show us this:</p>
<pre><code class="language-TeXt">test_simple.py .x
</code></pre>
<p>And all tests passed! The second test passed, because we said we expected it to fail. Confusing, right? It&apos;s not used very often, but it&apos;s there if you need it.</p>
<p>Note that the outcome was <code>x</code>, and not <code>X</code>. That&apos;s because we said the test should fail, and it did. It didn&apos;t &quot;unexpectedly pass&quot;, it &quot;expectedly failed&quot;.</p>
<p>If we change it to this:</p>
<pre><code class="language-python">import pytest


def test_simple_add():
    assert 1 + 1 == 2


@pytest.mark.xfail
def test_failing():
    assert &apos;a&apos; == &apos;a&apos;  # this will pass
</code></pre>
<p>Then the outcome will be:</p>
<pre><code class="language-TeXt">test_simple.py .X
</code></pre>
<p>Pytest now says <code>1 passed, 1 xpassed</code>.</p>
<h3 id="how-to-skip-tests-with-pytest">How to skip tests with pytest</h3>
<p>Using similar syntax, we can skip tests:</p>
<pre><code class="language-python">import pytest


def test_simple_add():
    assert 1 + 1 == 2


@pytest.mark.skip
def test_failing():
    assert &apos;a&apos; == &apos;b&apos;
</code></pre>
<p>And now the output:</p>
<pre><code class="language-python">test_simple.py .s
</code></pre>
<p>Again, all tests pass. You shouldn&apos;t skip tests very often, but sometimes there isn&apos;t anything you can do about a test failure (temporarily), so you can skip it.</p>
<p>You can also skip tests only under certain conditions. For example, if a certain test doesn&apos;t work on Mac computers because the library you&apos;re using isn&apos;t supported there, you can use <code>skipif</code>:</p>
<pre><code class="language-python">import os

import pytest


def test_simple_add():
    assert 1 + 1 == 2


@pytest.mark.skipif(os.name == &apos;posix&apos;, reason=&apos;does not run on mac&apos;)
def test_failing():
    assert &apos;a&apos; == &apos;b&apos;
</code></pre>
<p>Again, test is skipped on Mac computers, but not on Windows (where it would fail, since <code>&apos;a&apos; == &apos;b&apos;</code> always fails).</p>
<h3 id="the-error-test-outcome-in-pytest">The error test outcome in pytest</h3>
<p>The final test outcome, error, is pretty self-explanatory. If there&apos;s an error such as <code>ValueError</code>, <code>NameError</code>, or any other error during the runtime of the test that isn&apos;t <code>AssertionError</code>, the test will fail with <code>ERROR</code> (shown as <code>E</code> in the console).</p>
<h2 id="how-to-write-pytest-tests-with-a-better-example">How to write Pytest tests (with a better example)</h2>
<p>Let&apos;s move on to a different example for the rest of this article. Let&apos;s say you are writing a class to represent replies in a forum thread. Something like this (in a file called <code>reply.py</code>):</p>
<pre><code class="language-python">import datetime


class Reply:
    def __init__(self, body: str, user: str, created: datetime.datetime) -&gt; None:
        self.body = body
        self.user = user
        self.created = created
    
    def __repr__(self) -&gt; str:
        return f&quot;&lt;Reply from &apos;{self.user}&apos; on &apos;{self.created.strftime(&apos;%Y-%m-%d %H:%M&apos;)}&apos;&gt;&quot;

    def __eq__(self, __value: object) -&gt; bool:
        if not isinstance(__value, Reply):
            return False
        return (
            self.body == __value.body and
            self.user == __value.user and
            self.created == __value.created
        )
</code></pre>
<p>Now let&apos;s write some tests for this!</p>
<p>First we need to think about what different tests we may want to write:</p>
<ul>
<li>Test that creating a <code>Reply</code> object works (testing <code>__init__</code>).</li>
<li>Test that calling <code>repr(reply)</code> works correctly (testing <code>__repr__</code>).</li>
<li>Test <code>__eq__</code>:
<ul>
<li>Two <code>Reply</code> objects with the same data are equal when doing <code>reply1 == reply2</code>.</li>
<li>Two <code>Reply</code> objects with different data aren&apos;t equal.</li>
<li>A <code>Reply</code> object and something else (like a string) aren&apos;t equal.</li>
</ul>
</li>
</ul>
<h3 id="the-given-when-then-structure-for-tests">The Given-When-Then structure for tests</h3>
<p>Create a file <code>test_reply.py</code> for the next few tests.</p>
<p>First, we&apos;d add our imports:</p>
<pre><code class="language-python">import datetime

from reply import Reply
</code></pre>
<p>Then let&apos;s test that we can create a <code>Reply</code> object:</p>
<pre><code class="language-python">def test_init():
    # Given
    body = &quot;Hello, World!&quot;
    user = &quot;user1&quot;
    created = datetime.datetime.now()

    # When
    reply = Reply(body, user, created)

    # Then
    assert reply.body == body
    assert reply.user == user
    assert reply.created == created
</code></pre>
<p>Here you can see the Given-When-Then structure of tests coming into play. It&apos;s a simple but effective way to structure your tests:</p>
<ol>
<li>Start by defining the data your test will use (&quot;given that this data is available...&quot;)</li>
<li>Then write the actual test (&quot;when this happens...&quot;)</li>
<li>Finally, check that the result is what you expect (&quot;then the result is...&quot;)</li>
</ol>
<h3 id="how-to-write-tests-for-functions-that-process-data">How to write tests for functions that process data</h3>
<p>In our <code>__repr__</code> method, objects have access to <code>self.created</code>, and they will transform it to a string to be displayed.</p>
<p>We may feel like doing something like this is warranted:</p>
<pre><code class="language-python">def test_repr():
    body = &quot;Hello, World!&quot;
    user = &quot;user1&quot;
    created = datetime.datetime.now()

    reply = Reply(body, user, created)
    expected_repr = f&quot;&lt;Reply from &apos;{user}&apos; on &apos;{created.strftime(&apos;%Y-%m-%d %H:%M&apos;)&apos;&gt;&quot;

    assert repr(reply) == expected_repr
</code></pre>
<p>But this would be a bit of a mistake, because the code in the <code>Reply</code> objects calls <code>strftime</code>, and so does our testing code. They&apos;re both doing the same thing, so you aren&apos;t really testing anything.</p>
<p>Instead, it&apos;s better to actually write the expected value in the test. It also makes it more readable!</p>
<pre><code class="language-python">def test_repr():
    body = &quot;Hello, World!&quot;
    user = &quot;user1&quot;
    created = datetime.datetime(2023, 1, 1, 0, 0, 0)

    reply = Reply(body, user, created)
    expected_repr = f&quot;&lt;Reply from &apos;{user}&apos; on &apos;2023-01-01 00:00&apos;&gt;&quot;

    assert repr(reply) == expected_repr
</code></pre>
<p>By the way, I should mention that if your tests fail and you&apos;re not sure why, running Pytest with the <code>-v</code> flag can be very helpful. It gives you a bit more information about the expected and actual assertion values.</p>
<pre><code class="language-bash">pytest -v
</code></pre>
<h3 id="testing-for-expected-exceptions">Testing for expected exceptions</h3>
<p>What happens when a user passes something that isn&apos;t a <code>datetime</code> object to a <code>Reply</code>?</p>
<p>Well, nothing until <code>__repr__</code> is called. Let&apos;s write a test for that.</p>
<p>Here we will use an assertion helped, <code>pytest.raises</code>, to say that an exception should be raised:</p>
<pre><code class="language-python">def test_repr_without_date():
    body = &quot;Hello, World!&quot;
    user = &quot;user1&quot;
    created = &quot;2021-02-07 12:00&quot;

    reply = Reply(body, user, created)

    with pytest.raises(AttributeError):
        assert repr(reply) == f&quot;&lt;Reply from &apos;{user}&apos; on &apos;2021-02-07 12:00&apos;&gt;&quot;
</code></pre>
<p>Note that <code>with pytest.raises(AttributeError)</code> says that when we call <code>repr(reply)</code>, an <code>AttributeError</code> should be raised because we can&apos;t call <code>created.strftime()</code> (since <code>created</code> isn&apos;t a <code>datetime</code> object).</p>
<p>There are other helpers that are worth knowing about. For example, <code>pytest.approx()</code>, which allows you to do things like:</p>
<pre><code class="language-python">import pytest

def test_almost_equal():
    x = 5
    y = 5.01
    assert x == pytest.approx(y, 0.1)  # True
    assert x == pytest.approx(y, 0.01)  # True
    assert x == pytest.approx(y, 0.001)  # False
</code></pre>
<p>This one checks that the two values are within an acceptable range of each other, given by the second argument. Handy, especially when comparing floats (did you know most programming languages <a href="https://blog.teclado.com/decimal-vs-float-in-python/#floats-in-python">don&apos;t represent floats very well</a> in some cases?).</p>
<h3 id="testing-the-equality-of-two-objects">Testing the equality of two objects</h3>
<p>To write our tests for testing equality, we do something similar to what we&apos;ve been doing! First make variables for the values, then create the objects, and finally compare them:</p>
<pre><code class="language-python">def test_eq_same_values():
    body = &quot;Hello, World!&quot;
    user = &quot;user1&quot;
    created = datetime.datetime.now()

    reply1 = Reply(body, user, created)
    reply2 = Reply(body, user, created)

    assert reply1 == reply2


def test_eq_different_values():
    body1 = &quot;Hello, World!&quot;
    user1 = &quot;user1&quot;
    created1 = datetime.datetime.now()

    body2 = &quot;Goodbye, World!&quot;
    user2 = &quot;user2&quot;
    created2 = created1 + datetime.timedelta(minutes=1)

    reply1 = Reply(body1, user1, created1)
    reply2 = Reply(body2, user2, created2)

    assert reply1 != reply2


def test_eq_different_types():
    body = &quot;Hello, World!&quot;
    user = &quot;user1&quot;
    created = datetime.datetime.now()

    reply = Reply(body, user, created)
    non_reply = &quot;Not a reply object&quot;

    assert reply != non_reply
</code></pre>
<h2 id="testing-a-data-store-using-pytest-and-fixtures">Testing a data store using pytest and fixtures</h2>
<p>In order to showcase what &quot;fixtures&quot; are, let&apos;s add another class to our system:</p>
<pre><code class="language-python">from reply import Reply


class Thread:
    def __init__(self, title: str, user: str, replies: list[Reply] = None) -&gt; None:
        self.title = title
        self.user = user
        self.replies = replies or []
    
    def __repr__(self) -&gt; str:
        return f&quot;&lt;Thread &apos;{self.title}&apos; by &apos;{self.user}&apos;&gt;&quot;
    
    def reply(self, reply: Reply) -&gt; None:
        if reply.created &gt; self.replies[-1].created:
            raise ValueError(&quot;New reply is older than the last reply&quot;)
        self.replies.append(reply)
</code></pre>
<p>Here we&apos;ve got a thread, with a title, the user who created the thread, and a list of replies.</p>
<p>It has one particularly interesting method, <code>reply()</code>, which takes in a <code>Reply</code> object and appends it to the replies list. But it only does so if the new reply is not older than the last reply in the thread. Or does it? I&apos;ve introduced a bug in there to show you how easy it is to make a mistake here. Our testing will help us realise and fix the mistake!</p>
<p>Our first tests are fairly straightforward and introduce nothing new:</p>
<pre><code class="language-python">from thread import Thread
from reply import Reply

import datetime

def test_init():
    title = &quot;Hello, World!&quot;
    user = &quot;user1&quot;
    replies = [
        Reply(&quot;Hello, World!&quot;, &quot;user1&quot;, datetime.datetime.now()),
        Reply(&quot;Goodbye, World!&quot;, &quot;user2&quot;, datetime.datetime.now()),
    ]

    thread = Thread(title, user, replies)

    assert thread.title == title
    assert thread.user == user
    assert thread.replies == replies


def test_repr():
    title = &quot;Hello, World!&quot;
    user = &quot;user1&quot;
    replies = [
        Reply(&quot;Hello, World!&quot;, &quot;user1&quot;, datetime.datetime.now()),
        Reply(&quot;Goodbye, World!&quot;, &quot;user2&quot;, datetime.datetime.now()),
    ]

    thread = Thread(title, user, replies)

    assert repr(thread) == f&quot;&lt;Thread &apos;{title}&apos; by &apos;{user}&apos;&gt;&quot;
</code></pre>
<p>But now let&apos;s test the <code>reply</code> method:</p>
<pre><code class="language-python">def test_reply():
    title = &quot;Hello, World!&quot;
    user = &quot;user1&quot;
    replies = [
        Reply(&quot;Hello, World!&quot;, &quot;user1&quot;, datetime.datetime(2023, 1, 1, 0, 0, 0)),
        Reply(&quot;Goodbye, World!&quot;, &quot;user2&quot;, datetime.datetime(2023, 1, 2, 0, 0, 0)),
    ]

    thread = Thread(title, user, replies)
    new_reply = Reply(&quot;Hello, World!&quot;, &quot;user1&quot;, datetime.datetime(2023, 1, 3, 0, 0, 0))

    thread.reply(new_reply)

    assert new_reply in thread.replies
</code></pre>
<p>If we run this (which by the way, you should be running your tests continuously, certainly after every new test, but preferably after every relevant code change)...</p>
<pre><code class="language-python">self = &lt;Thread &apos;Hello, World!&apos; by &apos;user1&apos;&gt;, reply = &lt;Reply from &apos;user1&apos; on &apos;2023-01-03 00:00&apos;&gt;

    def reply(self, reply: Reply) -&gt; None:
        if reply.created &gt; self.replies[-1].created:
&gt;           raise ValueError(&quot;New reply is older than the last reply&quot;)
E           ValueError: New reply is older than the last reply

simple_tests/thread.py:15: ValueError
===== short test summary info =====
FAILED simple_tests/test_thread.py::test_reply - ValueError: New reply is older than the last reply
</code></pre>
<p>How could this be? Our replies are correct:</p>
<ul>
<li><code>2023-03-01</code></li>
<li><code>2023-03-02</code></li>
<li><code>2023-03-03</code></li>
</ul>
<p>The first thing would be to check that the test is correct. But it seems it is, so maybe we&apos;ve introduced a bug in the code!</p>
<p>After looking through it, you&apos;ll be able to see that we&apos;ve got the <code>&gt;</code> the wrong way round. It should be <code>&lt;</code>:</p>
<pre><code class="language-python">if reply.created &lt; self.replies[-1].created:
</code></pre>
<p>Changing that and re-running the tests shows us they pass. Excellent!</p>
<p>Now let&apos;s write a test that should fail:</p>
<pre><code class="language-python">def test_reply_more_recent():
    title = &quot;Hello, World!&quot;
    user = &quot;user1&quot;
    replies = [
        Reply(&quot;Hello, World!&quot;, &quot;user1&quot;, datetime.datetime(2023, 1, 1, 0, 0, 0)),
        Reply(&quot;Goodbye, World!&quot;, &quot;user2&quot;, datetime.datetime(2023, 1, 2, 0, 0, 0)),
    ]

    thread = Thread(title, user, replies)
    new_reply = Reply(&quot;Hello, World!&quot;, &quot;user1&quot;, datetime.datetime(2023, 1, 1, 0, 0, 0))

    thread.reply(new_reply)
</code></pre>
<p>This one fails, so let&apos;s add <code>pytest.raises(ValueError)</code> at the end:</p>
<pre><code class="language-python"># At top of file
import pytest


def test_reply_more_recent():
    title = &quot;Hello, World!&quot;
    user = &quot;user1&quot;
    replies = [
        Reply(&quot;Hello, World!&quot;, &quot;user1&quot;, datetime.datetime(2023, 1, 1, 0, 0, 0)),
        Reply(&quot;Goodbye, World!&quot;, &quot;user2&quot;, datetime.datetime(2023, 1, 2, 0, 0, 0)),
    ]

    thread = Thread(title, user, replies)
    new_reply = Reply(&quot;Hello, World!&quot;, &quot;user1&quot;, datetime.datetime(2023, 1, 1, 0, 0, 0))

    with pytest.raises(ValueError):
        thread.reply(new_reply)
</code></pre>
<h3 id="using-fixtures-with-pytest">Using fixtures with pytest</h3>
<p>You may have noticed that all tests start the same way, defining the title, user, and replies of our thread. We can extract them to a variable (but it won&apos;t work):</p>
<pre><code class="language-python">import datetime

import pytest
from reply import Reply
from thread import Thread

title = &quot;Hello, World!&quot;
user = &quot;user1&quot;
replies = [
    Reply(&quot;Hello, World!&quot;, &quot;user1&quot;, datetime.datetime(2023, 1, 1, 0, 0, 0)),
    Reply(&quot;Goodbye, World!&quot;, &quot;user2&quot;, datetime.datetime(2023, 1, 2, 0, 0, 0)),
]
thread = Thread(title, user, replies)


def test_init():
    assert thread.title == title
    assert thread.user == user
    assert thread.replies == replies


def test_repr():
    assert repr(thread) == f&quot;&lt;Thread &apos;{title}&apos; by &apos;{user}&apos;&gt;&quot;


def test_reply():
    new_reply = Reply(&quot;Hello, World!&quot;, &quot;user1&quot;, datetime.datetime(2023, 1, 3, 0, 0, 0))

    thread.reply(new_reply)

    assert new_reply in thread.replies


def test_reply_more_recent():
    new_reply = Reply(&quot;Hello, World!&quot;, &quot;user1&quot;, datetime.datetime(2023, 1, 1, 0, 0, 0))

    with pytest.raises(ValueError):
        thread.reply(new_reply)
</code></pre>
<p>Here the more experienced among you will notice that we&apos;ve done something taboo! We&apos;ve got a global variable, <code>thread</code>, and in our tests we&apos;re calling <code>thread.reply()</code>, which <em>modifies</em> the global variable.</p>
<p>This is a recipe for disaster because now <em>the order in which our tests runs matters</em>. You never want your tests to depend on each other.</p>
<p>Think about it, if <code>test_reply</code> runs before <code>test_init</code>, the latter will fail because the thread will have 3 replies in it instead of the expected 2.</p>
<p>So instead, we need to create a variable that is unique to each test. We can do that with a <em>fixture</em>:</p>
<pre><code class="language-python">@pytest.fixture(scope=&quot;function&quot;)
def thread():
    &quot;&quot;&quot;This fixture returns a Thread object&quot;&quot;&quot;
    title = &quot;Hello, World!&quot;
    user = &quot;user1&quot;
    replies = [
        Reply(&quot;Hello, World!&quot;, &quot;user1&quot;, datetime.datetime(2023, 1, 1, 0, 0, 0)),
        Reply(&quot;Goodbye, World!&quot;, &quot;user2&quot;, datetime.datetime(2023, 1, 2, 0, 0, 0)),
    ]
    return Thread(title, user, replies)
</code></pre>
<p>Now each test can gain access to this variable using <em>dependency injection</em>. This is just a fancy way to say: add <code>thread</code> as a parameter to the test, and Pytest will automatically call the <code>thread()</code> function and give the test its value when the test runs.</p>
<p>So let&apos;s modify our tests so they have a <code>thread</code> parameter (note I&apos;ve not used this fixture in <code>test_init</code>):</p>
<pre><code class="language-python">import datetime

import pytest
from reply import Reply
from thread import Thread


@pytest.fixture
def thread():
    &quot;&quot;&quot;This fixture returns a Thread object&quot;&quot;&quot;
    title = &quot;Hello, World!&quot;
    user = &quot;user1&quot;
    replies = [
        Reply(&quot;Hello, World!&quot;, &quot;user1&quot;, datetime.datetime(2023, 1, 1, 0, 0, 0)),
        Reply(&quot;Goodbye, World!&quot;, &quot;user2&quot;, datetime.datetime(2023, 1, 2, 0, 0, 0)),
    ]
    return Thread(title, user, replies)


def test_init():
    title = &quot;Hello, World!&quot;
    user = &quot;user1&quot;
    replies = [
        Reply(&quot;Hello, World!&quot;, &quot;user1&quot;, datetime.datetime(2023, 1, 1, 0, 0, 0)),
        Reply(&quot;Goodbye, World!&quot;, &quot;user2&quot;, datetime.datetime(2023, 1, 2, 0, 0, 0)),
    ]

    thread = Thread(title, user, replies)
    
    assert thread.title == &quot;Hello, World!&quot;
    assert thread.user == &quot;user1&quot;
    assert thread.replies == replies


def test_repr(thread):
    assert assert repr(thread) == &quot;&lt;Thread &apos;Hello, World!&apos; by &apos;user1&apos;&gt;&quot;


def test_reply(thread):
    new_reply = Reply(&quot;Hello, World!&quot;, &quot;user1&quot;, datetime.datetime(2023, 1, 3, 0, 0, 0))

    thread.reply(new_reply)

    assert new_reply in thread.replies


def test_reply_more_recent(thread):
    new_reply = Reply(&quot;Hello, World!&quot;, &quot;user1&quot;, datetime.datetime(2023, 1, 1, 0, 0, 0))

    with pytest.raises(ValueError):
        thread.reply(new_reply)
</code></pre>
<p>So this helps us answer the question: &quot;what are fixtures?&quot;</p>
<p>Fixtures are functions that allow us to reuse data in our tests, and can be called once per test by Pytest.</p>
<p>Note that <code>scope=&quot;function&quot;</code> can take other values! If you want the same fixture value to be reused in the entire file, you can say <code>scope=&quot;module&quot;</code>. If you want it to be reused in the entire test suite, you can use <code>scope=&quot;session&quot;</code>. There&apos;s also <code>scope=&quot;class&quot;</code>, but that only becomes relevant when you use an object-oriented approach to writing your tests, which is something I almost never do.</p>
<p>By default, fixtures can only be used in the file where they are defined. If you want to share a fixture between different test files, you can put the fixtures inside a file called <code>conftest.py</code>. Then the fixtures will be visible in any test file in the folder that contains <code>conftest.py</code>, or any sub-folder.</p>
<h3 id="setup-and-teardown-in-pytest-fixtures">Setup and teardown in Pytest fixtures</h3>
<p>Every fixture in Pytest can run some code before and after returning the value used in the tests.</p>
<p>Here&apos;s a non-useful example of how this works:</p>
<pre><code class="language-python">@pytest.fixture
def my_fixture():
    print(&quot;Hello, fixture!&quot;)
    yield 42
    print(&quot;Bye, fixture!&quot;)


def test_nothing(my_fixture):
    print(&quot;In the test!&quot;)
    print(f&quot;Value from fixture: {my_fixture}&quot;)
</code></pre>
<p>Running this, you&apos;ll see this output is captured:</p>
<pre><code class="language-TeXt">Hello, fixture!
In the test!
Value from fixture: 42
Bye, fixture!
</code></pre>
<p>Obviously, that example isn&apos;t very useful, but I&apos;m sure you can see when it may be useful:</p>
<ul>
<li>If your tests need to interact with a file, you could use the setup and teardown functionality to open and close the file before and after giving it to your tests.</li>
<li>If your tests use a database, you can use this to connect to the database and close the connection when you&apos;re done.</li>
</ul>
<h3 id="how-to-use-multiple-fixtures-in-a-pytest-test">How to use multiple fixtures in a Pytest test</h3>
<p>Using multiple fixtures is easy as pie! Just add multiple parameters to the tests, and Pytest will make sure the fixtures are called and the values passed:</p>
<pre><code class="language-python">def test_with_two_fixtures(fixture_one, fixture_two):
    pass
</code></pre>
<h3 id="tracing-fixture-execution-and-finding-where-fixtures-are-defined">Tracing fixture execution and finding where fixtures are defined</h3>
<p>When you execute the <code>pytest</code> command to run your tests, you can pass the <code>--fixtures</code> flag and it will show you all the fixtures that are available for your test functions to use. This includes fixtures Pytest provides, as well as your own:</p>
<pre><code class="language-python">cache -- .venv/lib/python3.11/site-packages/_pytest/cacheprovider.py:509
    Return a cache object that can persist state between testing sessions.

capsys -- .venv/lib/python3.11/site-packages/_pytest/capture.py:905
    Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``.

capsysbinary -- .venv/lib/python3.11/site-packages/_pytest/capture.py:933
    Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``.

... (there are a few)

----- fixtures defined from test_thread -----
thread -- simple_tests/test_thread.py:9
    This is a Thread object

</code></pre>
<p>If you want to see what fixtures each test used, you can use <code>--fixtures-per-test</code>:</p>
<pre><code class="language-bash">$ pytest --fixtures-per-test -k test_thread
...
----- fixtures used by test_repr -----
----- (simple_tests/test_thread.py:36) -----
thread -- simple_tests/test_thread.py:9
    This fixture returns a Thread object

----- fixtures used by test_reply -----
----- (simple_tests/test_thread.py:40) -----
thread -- simple_tests/test_thread.py:9
    This fixture returns a Thread object

----- fixtures used by test_reply_more_recent -----
----- (simple_tests/test_thread.py:48) -----
thread -- simple_tests/test_thread.py:9
    This fixture returns a Thread object
</code></pre>
<h2 id="conclusion">Conclusion</h2>
<p>In this beginner&apos;s guide to pytest, we&apos;ve covered the basics of how to write tests using pytest, including assertions, fixtures, and selecting tests to run. We&apos;ve also covered some advanced features like fixture scopes and sharing fixtures among multiple files using conftest.py.</p>
<p>Writing tests is an important part of software development, and pytest is a great tool for making testing in Python easy and effective. With the knowledge you&apos;ve gained from this guide, you should be well on your way to writing better tests and building more reliable software!</p>
<p>If you want to learn more about Python generally, consider enrolling in our <a href="https://go.tecla.do/complete-python-sale?ref=blog.teclado.com">Complete Python Course</a>! This comprehensive course teaches from the basics to the advanced, covering topics such as object-oriented programming, async development, GUI programming, and more.</p>
<p>For further reading, I strongly recommend the official documentation. Brian Okken has also written an excellent book:</p>
<ul>
<li><a href="https://docs.pytest.org/en/latest/?ref=blog.teclado.com">The official pytest documentation</a></li>
<li><a href="https://pragprog.com/titles/bopytest2/python-testing-with-pytest-second-edition/?ref=blog.teclado.com">Python Testing with pytest</a> by Brian Okken, an excellent book that covers everything you could want to know about Pytest</li>
</ul>
<p>That&apos;s all for today! Thank you for joining me, I hope you&apos;ve enjoyed the read, and I&apos;ll see you next time.</p>
]]></content:encoded></item><item><title><![CDATA[How to deploy Flask and MongoDB to Render]]></title><description><![CDATA[In this post we show you how to deploy your Flask and MongoDB app for free to Render.com and MongoDB Atlas.]]></description><link>https://blog.teclado.com/how-to-deploy-flask-and-mongodb-to-render/</link><guid isPermaLink="false">63c904ee357378f172be5c38</guid><category><![CDATA[REST APIs]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Jose Salvatierra]]></dc:creator><pubDate>Mon, 27 Feb 2023 11:50:05 GMT</pubDate><media:content url="https://blog.teclado.com/content/images/2023/02/03_Deploy-Flask-and-MongoDB_Tavola-disegno-1-copia-1-.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.teclado.com/content/images/2023/02/03_Deploy-Flask-and-MongoDB_Tavola-disegno-1-copia-1-.jpg" alt="How to deploy Flask and MongoDB to Render"><p>Let&apos;s quickly go over the fastest and cheapest way to deploy your Flask app and a MongoDB database on Render.com!</p>
<p>We will:</p>
<ul>
<li>Prepare the Flask app for deployment.</li>
<li>Acquire a MongoDB database.</li>
<li>Create a Render web service to host our app.</li>
<li>Deploy!</li>
</ul>
<h2 id="prepare-your-flask-app-for-deployment">Prepare your Flask app for deployment</h2>
<p>To deploy any Flask app in Render.com you need to have two things clear:</p>
<ul>
<li>What dependencies does the app have?</li>
<li>What Python version should the app use?</li>
</ul>
<p>Once you know the answers to these questions, you can answer them:</p>
<ul>
<li>Write your app dependencies in the <code>requirements.txt</code> file, if you haven&apos;t already. Make sure to add <code>gunicorn</code> and <code>pymongo[srv]</code> as dependencies, as Render will use them to run your app and connect to MongoDB respectively.</li>
<li>You&apos;ll select your Python version when you create the Render web service in step 3.</li>
</ul>
<h2 id="acquire-a-mongodb-database">Acquire a MongoDB database</h2>
<p>The fastest and cheapest way to acquire a MongoDB database is to use MongoDB Atlas, the official cloud database service from MongoDB. It&apos;s free for small databases!</p>
<p>Go to <a href="https://www.mongodb.com/atlas/database?ref=blog.teclado.com">https://www.mongodb.com/atlas/database</a> and sign up. Then you can create a free Cluster for your Flask app.</p>
<p>After creating a database you&apos;ll need to configure two security settings: <strong>Database Access</strong> and <strong>Network Access</strong>.</p>
<p>Under Database Access, create a user with a password. Make sure to use a secure password for this! For the user&apos;s Role, you can select &quot;Read and write to any database&quot;.</p>
<p>Then go to Network Access, click on the &quot;Add IP Address&quot; button, and then click on &quot;Add Current IP Address&quot;.</p>
<p>By doing this, only your computer will be able to access the database. When you create your Render service, you&apos;ll get another IP address to add to this list.</p>
<p>That&apos;s all the configuration you need for MongoDB! Now you can go back to the &quot;Database&quot; menu and click on &quot;Connect&quot;, and then &quot;Connect your application&quot;. Find the Python version you use, and copy the connection string. This is your <code>MONGODB_URI</code>, but remember to replace <code>&lt;password&gt;</code> with the secure password for the user you generated in the &quot;Database Access&quot; step.</p>
<h2 id="create-a-render-web-service-for-your-flask-app">Create a Render web service for your Flask app</h2>
<p>To deploy to Render, you&apos;ll need to put your app in GitHub. Create a GitHub account and a new GitHub repository. It can be public or private.</p>
<p>If you&apos;re unfamiliar with Git, you can add your project files to GitHub using the UI:</p>
<p><img src="https://blog.teclado.com/content/images/2023/01/upload-files-github.png" alt="How to deploy Flask and MongoDB to Render" loading="lazy"></p>
<p>Once you&apos;re files are in the GitHub repository, you can go over to Render.com.</p>
<p>To create a Render web service you&apos;ll need to make an account, and then click on New -&gt; Web Service at the top right.</p>
<p>If you haven&apos;t already at this point, you&apos;ll need to link your GitHub account with Render. After doing so, you&apos;ll see the list of GitHub repositories in Render. Click &quot;Connect&quot; on your repository, and that will allow Render to download your code from GitHub.</p>
<p>Now comes the settings for Render. For a Flask app, they should almost always look like this (long image warning, right click and open in new tab to zoom in):</p>
<p><img src="https://blog.teclado.com/content/images/2023/01/render-settings.png" alt="How to deploy Flask and MongoDB to Render" loading="lazy"></p>
<p>Here&apos;s what&apos;s going on in that image:</p>
<ul>
<li>You can name your service whatever you want.</li>
<li>For a Region, choose something close to you and your users.</li>
<li>Your branch name should match the branch name selected in GitHub.</li>
<li>The build step installs your dependencies. Since we&apos;re using <code>requirements.txt</code>, this should be <code>pip install -r requirements.txt</code>.</li>
<li>The start command actually runs your application. Here we use <code>gunicorn &quot;app:create_app()&quot;</code> because we assume you are using the Flask app factory pattern. If you aren&apos;t, you can use <code>gunicorn app:app</code> instead.</li>
<li>We select a free server. There are a few limitations to free servers, but they&apos;re good to try out the deployment.</li>
<li>In the environment variables section, we add <code>PYTHON_VERSION</code> to tell Render which Python version to use, and <code>MONGODB_URI</code> to store the connection string to MongoDB which your Flask app uses.</li>
</ul>
<p>When you hit &quot;Create Web Service&quot;, it should start deploying! It can take a few minutes though, so get a tea going and check back in 5!</p>
<h2 id="concurrency-in-render-with-gunicorn">Concurrency in Render with gunicorn</h2>
<p>Once your app is deployed to Render.com, it has access to a certain amount of processing power in the Render server.</p>
<p>If the Flask application doesn&apos;t require all that power, you can run multiple copies of the Flask app together, in the same Render service.</p>
<p>That way you can save quite a bit of money (if you were not using the free option), and improve performance of you app.</p>
<p>And it&apos;s really easy! All you have to do is:</p>
<p>Go to the <strong>Environment</strong> tab in your Render.com service, and create a new environment variable called <code>WEB_CONCURRENCY</code>. You can set this to how many copies of your Flask app you want to run. I&apos;d set it to <code>3</code> to start with.</p>
<p>Keep an eye on your <strong>Metrics</strong>, as you don&apos;t want the <strong>Memory</strong> to get close to your limit. You can see your limit in the <strong>Settings</strong> tab. The free option&apos;s memory is 256MB.</p>
<hr>
<p>That&apos;s everything for this blog post. I wanted to show you how to get going with MongoDB and Flask in Render, but we also have another blog post on deploying <a href="https://blog.teclado.com/how-to-deploy-your-first-rest-api-with-flask-for-free/">Flask and PostgreSQL</a>.</p>
<p>If you want to learn more about web development and Flask, consider enrolling in our <a href="https://go.tecla.do/web-dev-course-sale?ref=blog.teclado.com">Web Developer Bootcamp with Flask and Python</a> course! It&apos;s a long video-course that covers HTML, CSS, Flask, MongoDB, deployments, and more. You&apos;ll get it at its best possible price by going through our link.</p>
<p>Thanks for reading, and I&apos;ll see you next time!</p>
]]></content:encoded></item><item><title><![CDATA[How to customise pages and emails of Flask-Security-Too]]></title><description><![CDATA[When you add login, signup, or other features with Flask-Security-Too, you get some unstyled plain HTML pages. Learn how to customise and style them in this article.]]></description><link>https://blog.teclado.com/customise-pages-emails-flask-security-too/</link><guid isPermaLink="false">63c6823b357378f172be5bc8</guid><category><![CDATA[Web Development]]></category><category><![CDATA[Flask]]></category><dc:creator><![CDATA[Jose Salvatierra]]></dc:creator><pubDate>Mon, 20 Feb 2023 10:00:10 GMT</pubDate><media:content url="https://blog.teclado.com/content/images/2023/01/3.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://blog.teclado.com/content/images/2023/01/3.jpg" alt="How to customise pages and emails of Flask-Security-Too"><p>There are two things we may want to customise when we use Flask-Security-Too: pages and emails. For example, the login page or the account confirmation email. Let&apos;s take a look at how to do it.</p>
<p>This is the final article in this series of blog posts, where I show you how to work with Flask-Security-Too:</p>
<ol>
<li><a href="https://blog.teclado.com/user-authentication-flask-security-too/">User authentication in Flask with Flask-Security-Too</a></li>
<li><a href="https://blog.teclado.com/email-confirmation-flask-security-too/">User email confirmations with Flask-Security-Too</a></li>
<li>Customising templates and emails of Flask-Security-Too (this article)</li>
</ol>
<p>All the page and email templates in Flask-Security-Too are written using HTML and Jinja, and you can find them all inside your virtual environment&apos;s folder, and then inside <code>lib/python3/site-packages/flask_security/templates/security</code>.</p>
<p><img src="https://blog.teclado.com/content/images/2023/01/site-packages-login-user-template.png" alt="How to customise pages and emails of Flask-Security-Too" loading="lazy"></p>
<p>In this article, we will customise three templates:</p>
<ul>
<li><code>login_user.html</code></li>
<li><code>_macros.html</code></li>
<li><code>register_user.html</code></li>
</ul>
<p>Doing this will help you learn how to customise any template, so you can do the same for others that you wish to modify.</p>
<h2 id="customising-the-login-template">Customising the login template</h2>
<p>Begin by opening the <code>login_user.html</code> default template and copying its contents.</p>
<p>Then, inside your Flask app&apos;s <code>templates</code> folder, create a <code>security</code> folder. Inside there, create <code>login_user.html</code>, and paste the contents you copied from the default template.</p>
<p>Just by doing this, your Flask app will use <em>your</em> template instead of the default. For now, they have the same content! Let&apos;s look at how to change the content.</p>
<p>When I wrote this article, this was the content of <code>login_user.html</code>:</p>
<pre><code class="language-html">{% extends &quot;security/base.html&quot; %}
{% from &quot;security/_macros.html&quot; import render_field_with_errors, render_field, render_field_errors, render_form_errors %}

{% block content %}
{% include &quot;security/_messages.html&quot; %}
&lt;h1&gt;{{ _fsdomain(&apos;Login&apos;) }}&lt;/h1&gt;
  &lt;form action=&quot;{{ url_for_security(&apos;login&apos;) }}&quot; method=&quot;POST&quot; name=&quot;login_user_form&quot;&gt;
    {{ login_user_form.hidden_tag() }}
    {{ render_form_errors(login_user_form) }}
    {% if &quot;email&quot; in identity_attributes %}
      {{ render_field_with_errors(login_user_form.email) }}
    {% endif %}
    {% if login_user_form.username and &quot;username&quot; in identity_attributes %}
      {% if &quot;email&quot; in identity_attributes %}
        &lt;h3&gt;{{ _fsdomain(&quot;or&quot;) }}&lt;/h3&gt;
      {% endif %}
      {{ render_field_with_errors(login_user_form.username) }}
    {% endif %}
    &lt;div class=&quot;fs-gap&quot;&gt;
      {{ render_field_with_errors(login_user_form.password) }}&lt;/div&gt;
    {{ render_field_with_errors(login_user_form.remember) }}
    {{ render_field_errors(login_user_form.csrf_token) }}
    {{ render_field(login_user_form.submit) }}
  &lt;/form&gt;
  {% if security.webauthn %}
    &lt;hr class=&quot;fs-gap&quot;&gt;
    &lt;h2&gt;{{ _fsdomain(&quot;Use WebAuthn to Sign In&quot;) }}&lt;/h2&gt;
    &lt;div&gt;
      &lt;form method=&quot;GET&quot; id=&quot;wan-signin-form&quot; name=&quot;wan_signin_form&quot;&gt;
        &lt;input id=&quot;wan_signin&quot; name=&quot;wan_signin&quot; type=&quot;submit&quot; value=&quot;{{ _fsdomain(&apos;Sign in with WebAuthn&apos;) }}&quot;
          formaction=&quot;{{ url_for_security(&quot;wan_signin&quot;) }}&quot;&gt;
      &lt;/form&gt;
    &lt;/div&gt;
  {% endif %}
{% include &quot;security/_menu.html&quot; %}
{% endblock %}
</code></pre>
<p>As you can see, there isn&apos;t much actual HTML in here. Mostly it&apos;s importing other files and using some pre-defined macros. If we want to change the style of the page, we would need to replace everything:</p>
<ul>
<li>The <code>security/_messages.html</code> file.</li>
<li>The macros for rendering fields.</li>
<li>The <code>security/_menu.html</code> file.</li>
</ul>
<p>Lately, I&apos;ve been using TailwindCSS for all my CSS, so if you&apos;re using that, here are two templates you can use.</p>
<pre><code class="language-html">{% block content %}
&lt;div class=&quot;flex flex-col min-h-full items-center justify-center py-12 px-4 sm:px-6 lg:px-8&quot;&gt;
  &lt;div class=&quot;w-full max-w-md space-y-8&quot;&gt;
    {% set messages = get_flashed_messages() %}
    {% if messages %}
      &lt;div class=&quot;w-full my-6&quot;&gt;
        {% for category, message in get_flashed_messages(with_categories=true) %}
          &lt;p&gt;{{ message }}&lt;/p&gt;
        {% endfor %}
      &lt;/div&gt;
    {% endif %}
    &lt;div&gt;
      &lt;h2 class=&quot;mt-6 text-center text-3xl font-bold tracking-tight text-gray-900&quot;&gt;Sign in to your account&lt;/h2&gt;
      &lt;p class=&quot;mt-2 text-center text-sm text-gray-600&quot;&gt;
        Or
        &lt;a href=&quot;{{ url_for_security(&apos;register&apos;) }}&quot; class=&quot;font-medium text-indigo-600 hover:text-indigo-500&quot;&gt;sign up instead?&lt;/a&gt;
      &lt;/p&gt;
    &lt;/div&gt;
    &lt;form action=&quot;{{ url_for_security(&apos;login&apos;) }}&quot; method=&quot;POST&quot; class=&quot;mt-8 space-y-6&quot;&gt;
      {{ login_user_form.hidden_tag() }}
      {{ render_signup_login_field_w_errors(login_user_form.email, placeholder=&quot;Email&quot;, autocomplete=&quot;email&quot;) }}
      {{ render_signup_login_field_w_errors(login_user_form.password, placeholder=&quot;Password&quot;, autocomplete=&quot;current-password&quot;) }}
      {{ render_checkbox_with_errors(login_user_form.remember) }}
    
      &lt;div&gt;
        &lt;button
          type=&quot;submit&quot;
          class=&quot;w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500&quot;
        &gt;
          Sign in
        &lt;/button&gt;
      &lt;/div&gt;
    &lt;/form&gt;
  &lt;/div&gt;
&lt;/div&gt;
{% endblock %}
</code></pre>
<p>And in <code>security/_macros.html</code>:</p>
<pre><code class="language-html">{% macro render_checkbox_with_errors(field) %}
  &lt;div class=&quot;relative flex items-start&quot;&gt;
    &lt;div class=&quot;flex items-center h-5&quot;&gt;
    {{ field(class_=&quot;focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded&quot;, **kwargs)|safe }}
    &lt;/div&gt;
    &lt;div class=&quot;ml-3 text-sm&quot;&gt;
    {{ field.label(class_=&quot;text-gray-700&quot;) }}&lt;/div&gt;
    {% if field.errors %}
      &lt;ul&gt;
      {% for error in field.errors %}
        &lt;li&gt;{{ error }}&lt;/li&gt;
      {% endfor %}
      &lt;/ul&gt;
    {% endif %}
  &lt;/div&gt;
{% endmacro %}

{% macro render_signup_login_field_w_errors(field, class_) %}
  &lt;p class=&quot;{{ class_ }}&quot;&gt;
    {{ field.label(class_=&quot;sr-only&quot;) }} {{ field(class_=&quot;relative block w-full appearance-none rounded-md border border-gray-300 px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm&quot;, **kwargs)|safe }}
    {% if field.errors %}
      &lt;ul&gt;
      {% for error in field.errors %}
        &lt;li&gt;{{ error }}&lt;/li&gt;
      {% endfor %}
      &lt;/ul&gt;
    {% endif %}
  &lt;/p&gt;
{% endmacro %}
</code></pre>
<p>Then in <code>security/register_user.html</code>:</p>
<pre><code class="language-html">{% extends &quot;security/base.html&quot; %}
{% from &quot;security/_macros.html&quot; import render_signup_login_field_w_errors %}

{% block content %}
&lt;div class=&quot;flex min-h-full items-center justify-center py-12 px-4 sm:px-6 lg:px-8&quot;&gt;
  &lt;div class=&quot;w-full max-w-md space-y-8&quot;&gt;
    &lt;div&gt;
      &lt;h2 class=&quot;mt-6 text-center text-3xl font-bold tracking-tight text-gray-900&quot;&gt;Create your account&lt;/h2&gt;
      &lt;p class=&quot;mt-2 text-center text-sm text-gray-600&quot;&gt;
        Or
        &lt;a href=&quot;{{ url_for_security(&apos;login&apos;) }}&quot; class=&quot;font-medium text-indigo-600 hover:text-indigo-500&quot;&gt;log in instead?&lt;/a&gt;
      &lt;/p&gt;
    &lt;/div&gt;
    &lt;form action=&quot;{{ url_for_security(&apos;register&apos;) }}&quot; method=&quot;POST&quot; class=&quot;mt-8 space-y-6&quot;&gt;
      {{ register_user_form.hidden_tag() }}
      {{ render_signup_login_field_w_errors(register_user_form.full_name, placeholder=&quot;Full name&quot;, autocomplete=&quot;name&quot;) }}
      {{ render_signup_login_field_w_errors(register_user_form.email, placeholder=&quot;Email&quot;, autocomplete=&quot;email&quot;) }}
      {% if security.username_enable %}
      {{ render_signup_login_field_w_errors(register_user_form.username, placeholder=&quot;Full name&quot;, autocomplete=&quot;name&quot;) }}
      {% endif %}
      {{ render_signup_login_field_w_errors(register_user_form.password, placeholder=&quot;Password&quot;, autocomplete=&quot;current-password&quot;) }}
      {% if register_user_form.password_confirm %}
      {{ render_signup_login_field_w_errors(register_user_form.password_confirm, placeholder=&quot;Confirm password&quot;) }}
      {% endif %}

    
      &lt;div&gt;
        &lt;button
          type=&quot;submit&quot;
          class=&quot;w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500&quot;
        &gt;
          Create your account
        &lt;/button&gt;
        &lt;p class=&quot;mt-3 text-center text-sm text-slate-700&quot;&gt;By creating an account, you agree to our &lt;a class=&quot;font-semibold&quot; href=&quot;{{ url_for(&apos;resource_page&apos;, page_slug=&apos;terms-of-service&apos;) }}&quot;&gt;Terms of Service&lt;/a&gt; and &lt;a class=&quot;font-semibold&quot; href=&quot;{{ url_for(&apos;resource_page&apos;, page_slug=&apos;privacy-policy&apos;) }}&quot;&gt;Privacy Policy&lt;/a&gt;.&lt;/p&gt;
      &lt;/div&gt;
    &lt;/form&gt;
  &lt;/div&gt;
&lt;/div&gt;
{% endblock %}
</code></pre>
<h3 id="customising-templates-by-linking-a-stylesheet">Customising templates by linking a stylesheet</h3>
<p>If you wanted to add a CSS stylesheet instead of changing the template, that would be much easier. You&apos;d copy and paste the original file contents, and link the CSS stylesheet like so:</p>
<pre><code class="language-html">{% extends &quot;security/base.html&quot; %}
{% from &quot;security/_macros.html&quot; import render_field_with_errors, render_field, render_field_errors, render_form_errors %}

{% block styles %}
&lt;link rel=&quot;stylesheet&quot; href=&quot;{{ url_for(&apos;static&apos;, filename=&apos;css/auth.css) }}&quot;&gt;
{% endblock %}

{% block content %}
{% include &quot;security/_messages.html&quot; %}
&lt;h1&gt;{{ _fsdomain(&apos;Login&apos;) }}&lt;/h1&gt;
  &lt;form action=&quot;{{ url_for_security(&apos;login&apos;) }}&quot; method=&quot;POST&quot; name=&quot;login_user_form&quot;&gt;
    {{ login_user_form.hidden_tag() }}
    {{ render_form_errors(login_user_form) }}
    {% if &quot;email&quot; in identity_attributes %}
      {{ render_field_with_errors(login_user_form.email) }}
    {% endif %}
    {% if login_user_form.username and &quot;username&quot; in identity_attributes %}
      {% if &quot;email&quot; in identity_attributes %}
        &lt;h3&gt;{{ _fsdomain(&quot;or&quot;) }}&lt;/h3&gt;
      {% endif %}
      {{ render_field_with_errors(login_user_form.username) }}
    {% endif %}
    &lt;div class=&quot;fs-gap&quot;&gt;
      {{ render_field_with_errors(login_user_form.password) }}&lt;/div&gt;
    {{ render_field_with_errors(login_user_form.remember) }}
    {{ render_field_errors(login_user_form.csrf_token) }}
    {{ render_field(login_user_form.submit) }}
  &lt;/form&gt;
  {% if security.webauthn %}
    &lt;hr class=&quot;fs-gap&quot;&gt;
    &lt;h2&gt;{{ _fsdomain(&quot;Use WebAuthn to Sign In&quot;) }}&lt;/h2&gt;
    &lt;div&gt;
      &lt;form method=&quot;GET&quot; id=&quot;wan-signin-form&quot; name=&quot;wan_signin_form&quot;&gt;
        &lt;input id=&quot;wan_signin&quot; name=&quot;wan_signin&quot; type=&quot;submit&quot; value=&quot;{{ _fsdomain(&apos;Sign in with WebAuthn&apos;) }}&quot;
          formaction=&quot;{{ url_for_security(&quot;wan_signin&quot;) }}&quot;&gt;
      &lt;/form&gt;
    &lt;/div&gt;
  {% endif %}
{% include &quot;security/_menu.html&quot; %}
{% endblock %}
</code></pre>
<p>The only thing that was added to the original content in the code above is this under the imports. This assumes that the CSS file name is under <code>static/css/auth.css</code>:</p>
<pre><code class="language-html">{% block styles %}
&lt;link rel=&quot;stylesheet&quot; href=&quot;{{ url_for(&apos;static&apos;, filename=&apos;css/auth.css) }}&quot;&gt;
{% endblock %}
</code></pre>
<p>Remember that this original template extends the <code>security/base.html</code> template, so you can look at that one to see what different <code>block</code> elements are available.</p>
<h2 id="customising-the-confirmation-email-template">Customising the confirmation email template</h2>
<p>Next up, I like changing the look of the email confirmation template. The default template is basic, with just text. You can find it inside the <code>templates/email/welcome.html</code> file inside the <code>flask_security</code> folder in your virtual environment.</p>
<p>You&apos;ll also be able to find <code>welcome.txt</code>, which is a text representation of the email content. This will be used in those email clients that don&apos;t support HTML rendering (or have it disabled).</p>
<p>Create a <code>templates/email/welcome.html</code> file.</p>
<p>This is what my template looks like:</p>
<pre><code class="language-html">{# This template receives the following context:
  confirmation_link - the link that should be fetched (GET) to confirm
  confirmation_token - this token is part of confirmation link - but can be used to
    construct arbitrary URLs for redirecting.
  user - the entire user model object
  security - the Flask-Security configuration
#}
&lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Transitional//EN&quot; &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd&quot;&gt;
&lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;font-family: &apos;Helvetica Neue&apos;, Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;&quot;&gt;
&lt;head&gt;
&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width&quot; /&gt;
&lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=UTF-8&quot; /&gt;
&lt;title&gt;Confirm your email&lt;/title&gt;


&lt;style type=&quot;text/css&quot;&gt;
img {
max-width: 100%;
}
body {
-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em;
}
body {
background-color: #f6f6f6;
}
@media only screen and (max-width: 640px) {
  body {
    padding: 0 !important;
  }
  h1 {
    font-weight: 800 !important; margin: 20px 0 5px !important;
  }
  h2 {
    font-weight: 800 !important; margin: 20px 0 5px !important;
  }
  h3 {
    font-weight: 800 !important; margin: 20px 0 5px !important;
  }
  h4 {
    font-weight: 800 !important; margin: 20px 0 5px !important;
  }
  h1 {
    font-size: 22px !important;
  }
  h2 {
    font-size: 18px !important;
  }
  h3 {
    font-size: 16px !important;
  }
  .container {
    padding: 0 !important; width: 100% !important;
  }
  .content {
    padding: 0 !important;
  }
  .content-wrap {
    padding: 10px !important;
  }
  .invoice {
    width: 100% !important;
  }
}
&lt;/style&gt;
&lt;/head&gt;

&lt;body itemscope itemtype=&quot;http://schema.org/EmailMessage&quot; style=&quot;font-family: &apos;Helvetica Neue&apos;,Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;&quot; bgcolor=&quot;#f6f6f6&quot;&gt;

&lt;table class=&quot;body-wrap&quot; style=&quot;font-family: &apos;Helvetica Neue&apos;,Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; background-color: #f6f6f6; margin: 0;&quot; bgcolor=&quot;#f6f6f6&quot;&gt;&lt;tr style=&quot;font-family: &apos;Helvetica Neue&apos;,Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;&quot;&gt;&lt;td style=&quot;font-family: &apos;Helvetica Neue&apos;,Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;&quot; valign=&quot;top&quot;&gt;&lt;/td&gt;
        &lt;td class=&quot;container&quot; width=&quot;600&quot; style=&quot;font-family: &apos;Helvetica Neue&apos;,Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; display: block !important; max-width: 600px !important; clear: both !important; margin: 0 auto;&quot; valign=&quot;top&quot;&gt;
            &lt;div class=&quot;content&quot; style=&quot;font-family: &apos;Helvetica Neue&apos;,Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; max-width: 600px; display: block; margin: 0 auto; padding: 20px;&quot;&gt;
                &lt;table class=&quot;main&quot; width=&quot;100%&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; itemprop=&quot;action&quot; itemscope itemtype=&quot;http://schema.org/ConfirmAction&quot; style=&quot;font-family: &apos;Helvetica Neue&apos;,Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background-color: #fff; margin: 0; border: 1px solid #e9e9e9;&quot; bgcolor=&quot;#fff&quot;&gt;&lt;tr style=&quot;font-family: &apos;Helvetica Neue&apos;,Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;&quot;&gt;&lt;td class=&quot;content-wrap&quot; style=&quot;font-family: &apos;Helvetica Neue&apos;,Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;&quot; valign=&quot;top&quot;&gt;
                            &lt;meta itemprop=&quot;name&quot; content=&quot;Confirm Email&quot; style=&quot;font-family: &apos;Helvetica Neue&apos;,Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;&quot; /&gt;&lt;table width=&quot;100%&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; style=&quot;font-family: &apos;Helvetica Neue&apos;,Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;&quot;&gt;&lt;tr style=&quot;font-family: &apos;Helvetica Neue&apos;,Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;&quot;&gt;&lt;td class=&quot;content-block&quot; style=&quot;font-family: &apos;Helvetica Neue&apos;,Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;&quot; valign=&quot;top&quot;&gt;
                                        Hey, {{user.username}}! {{ _fsdomain(&apos;Please confirm your email through the link below:&apos;) }}
                                    &lt;/td&gt;
                                &lt;/tr&gt;&lt;tr style=&quot;font-family: &apos;Helvetica Neue&apos;,Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;&quot;&gt;&lt;td class=&quot;content-block&quot; itemprop=&quot;handler&quot; itemscope itemtype=&quot;http://schema.org/HttpActionHandler&quot; style=&quot;font-family: &apos;Helvetica Neue&apos;,Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;&quot; valign=&quot;top&quot;&gt;
                                        &lt;a href=&quot;{{ confirmation_link }}&quot; class=&quot;btn-primary&quot; itemprop=&quot;url&quot; style=&quot;font-family: &apos;Helvetica Neue&apos;,Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2em; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; text-transform: capitalize; background-color: #4F46E5; margin: 0; border-color: #4F46E5; border-style: solid; border-width: 6px 20px;&quot;&gt;{{ _fsdomain(&apos;Confirm my account&apos;) }}&lt;/a&gt;
                                    &lt;/td&gt;
                                &lt;/tr&gt;&lt;tr style=&quot;font-family: &apos;Helvetica Neue&apos;,Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;&quot;&gt;&lt;td class=&quot;content-block&quot; style=&quot;font-family: &apos;Helvetica Neue&apos;,Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;&quot; valign=&quot;top&quot;&gt;
                                        If clicking the button doesn&apos;t work, please copy this link and paste it into your browser&apos;s address bar:
                                    &lt;/td&gt;
                                &lt;/tr&gt;&lt;tr style=&quot;font-family: &apos;Helvetica Neue&apos;,Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;&quot;&gt;&lt;td class=&quot;content-block&quot; style=&quot;font-family: &apos;Helvetica Neue&apos;,Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px; max-width: 20em;&quot; valign=&quot;top&quot;&gt;
                                        &lt;a href=&quot;{{ confirmation_link }}&quot;&gt;{{ confirmation_link }}&lt;/a&gt;
                                    &lt;/td&gt;
                                &lt;/tr&gt;
                                &lt;tr style=&quot;font-family: &apos;Helvetica Neue&apos;,Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;&quot;&gt;&lt;td class=&quot;content-block&quot; style=&quot;font-family: &apos;Helvetica Neue&apos;,Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;&quot; valign=&quot;top&quot;&gt;
                                        &amp;mdash; Jose and the Teclado team
                                    &lt;/td&gt;
                                &lt;/tr&gt;&lt;/table&gt;&lt;/td&gt;
                    &lt;/tr&gt;&lt;/table&gt;&lt;div class=&quot;footer&quot; style=&quot;font-family: &apos;Helvetica Neue&apos;,Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;&quot;&gt;
                    &lt;table width=&quot;100%&quot; style=&quot;font-family: &apos;Helvetica Neue&apos;,Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;&quot;&gt;&lt;tr style=&quot;font-family: &apos;Helvetica Neue&apos;,Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;&quot;&gt;&lt;td class=&quot;aligncenter content-block&quot; style=&quot;font-family: &apos;Helvetica Neue&apos;,Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0; padding: 0 0 20px;&quot; align=&quot;center&quot; valign=&quot;top&quot;&gt;Follow &lt;a href=&quot;http://twitter.com/tecladocode&quot; style=&quot;font-family: &apos;Helvetica Neue&apos;,Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;&quot;&gt;@tecladocode&lt;/a&gt; on Twitter.&lt;/td&gt;
                        &lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;/td&gt;
        &lt;td style=&quot;font-family: &apos;Helvetica Neue&apos;,Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;&quot; valign=&quot;top&quot;&gt;&lt;/td&gt;
    &lt;/tr&gt;&lt;/table&gt;&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>As you can see, it&apos;s long and rather confusing! That&apos;s because writing emails using HTML is really difficult. There are so many email clients, and they don&apos;t all support the same CSS.</p>
<p>My email is taken from Mailgun&apos;s official templates that you can see <a href="https://github.com/mailgun/transactional-email-templates/tree/master/templates/inlined?ref=blog.teclado.com">here</a>, and I&apos;ve adapted it to my needs. Mailgun has a nice writeup on HTML emails that you can read <a href="https://www.mailgun.com/blog/email/transactional-html-email-templates/?ref=blog.teclado.com">here</a>.</p>
<p>Alright, that&apos;s everything for now! Thank you very much for reading, and I hope this series of articles has been useful. If you&apos;d like to learn more about web development using Flask, consider enrolling in our <a href="https://go.tecla.do/web-dev-course-sale?ref=blog.teclado.com">Web Developer Bootcamp with Flask and Python</a>! It&apos;s a complete video course that covers building multiple web apps and deploying them, all using Flask.</p>
<p>See you next time!</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[User email confirmations with Flask-Security-Too]]></title><description><![CDATA[Requiring user confirmation via email is traditionally not a simple thing to do. Flask extension Flask-Security-Too makes it much simpler.]]></description><link>https://blog.teclado.com/email-confirmation-flask-security-too/</link><guid isPermaLink="false">63c6816c357378f172be5baa</guid><category><![CDATA[Flask]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Jose Salvatierra]]></dc:creator><pubDate>Mon, 13 Feb 2023 10:00:34 GMT</pubDate><media:content url="https://blog.teclado.com/content/images/2023/01/2.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://blog.teclado.com/content/images/2023/01/2.jpg" alt="User email confirmations with Flask-Security-Too"><p>To prevent bot signups and reduce spam, you can require that users click a link in an email they receive when they register to your web apps. Flask-Security-Too can take care of this with some configuration.</p>
<p>This is article 2 of 3 in this series covering Flask-Security-Too:</p>
<ol>
<li><a href="https://blog.teclado.com/user-authentication-flask-security-too/">User authentication in Flask with Flask-Security-Too</a></li>
<li>User email confirmations with Flask-Security-Too (this article)</li>
<li><a href="https://blog.teclado.com/customise-pages-emails-flask-security-too/">Customising templates and emails of Flask-Security-Too</a></li>
</ol>
<p>Let&apos;s get started!</p>
<p>The tricky bit about user confirmation is the email delivery. To actually send emails and have them reach the user&apos;s inbox (and not end up in spam), you need a few things: a domain name, an email delivery service, and proper configuration. We talk about this in depth in our <a href="https://go.tecla.do/rest-apis-sale?ref=blog.teclado.com">REST APIs with Flask and Python</a> course, and you can read that section of the course e-book <a href="https://rest-apis-flask.teclado.com/docs/task_queues_emails/send_emails_python_mailgun/?ref=blog.teclado.com">here</a>.</p>
<p>I like the email delivery service Mailgun. Create an account with them, and you&apos;ll be given a sandbox domain. You can use this domain to send emails to &quot;authorised recipients&quot;. You can add yourself as an authorised recipient, so you can test out the email confirmation functionality of Flask-Security-Too. A step-by-step guide on how to do this is <a href="https://rest-apis-flask.teclado.com/docs/task_queues_emails/send_emails_python_mailgun/?ref=blog.teclado.com#setting-up-for-mailgun">here</a>. Read the &quot;Setting up for Mailgun&quot; section before continuing!</p>
<p>In the future, if you want to purchase a domain, you can use it to send emails with Mailgun (though that isn&apos;t free).</p>
<p>Now that you&apos;ve got your Mailgun account, let&apos;s get the SMTP details. You can do this by clicking the &quot;Select&quot; button under &quot;SMTP&quot;:</p>
<p><img src="https://blog.teclado.com/content/images/2023/01/image-1-sandbox-domain-details.png" alt="User email confirmations with Flask-Security-Too" loading="lazy"></p>
<p>Let&apos;s add these to our <code>.env</code> file:</p>
<pre><code>DATABASE_URL=&quot;sqlite:///data.db&quot;
MAIL_SERVER=smtp.mailgun.org
MAIL_PORT=587
MAIL_USERNAME=
MAIL_PASSWORD=
</code></pre>
<p>Now we can tell Flask-Security-Too to use email confirmation with one configuration setting:</p>
<pre><code class="language-python">app.config[&quot;SECURITY_CONFIRMABLE&quot;] = True
</code></pre>
<p>Doing this makes it so user accounts aren&apos;t valid until they are marked as <code>confirmed</code> (this is a column in our <code>UserModel</code> class, which you can see in <code>models/auth.py</code>). User accounts will be <code>confirmed</code> when the user clicks a link in an email they receive after registration.</p>
<p>Next, we&apos;ll configure the Flask-Mailman library, which Flask-Security-Too uses to send emails. In <code>app.py</code>:</p>
<pre><code class="language-python"># At top of file
from flask_mailman import Mail

# After &apos;Create app&apos;
app.config[&quot;MAIL_SERVER&quot;] = os.getenv(&quot;MAIL_SERVER&quot;)
app.config[&quot;MAIL_PORT&quot;] = os.getenv(&quot;MAIL_PORT&quot;)
app.config[&quot;MAIL_USE_SSL&quot;] = False
app.config[&quot;MAIL_USE_TLS&quot;] = True
app.config[&quot;MAIL_USERNAME&quot;] = os.getenv(&quot;MAIL_USERNAME&quot;)
app.config[&quot;MAIL_PASSWORD&quot;] = os.getenv(&quot;MAIL_PASSWORD&quot;)
mail = Mail(app)
</code></pre>
<p>Note that the <code>MAIL_USE_SSL</code> and <code>MAIL_USE_TLS</code> values will depend on which provider you use. Mailgun uses TLS, so that&apos;s why we set SSL to <code>False</code> and TLS to <code>True</code>.</p>
<p>Now, when you register an account, you&apos;ll get a confirmation email with a link you must click. If you don&apos;t click the link, the account will not count as valid.</p>
<p>We can test this out by creating a protected endpoint (one that requires login). We can then register but not click the email link, and see if we can access it.</p>
<p>To create a protected endpoint, we use the <code>@login_required</code> decorator:</p>
<pre><code class="language-python"># At top of file
from flask_security import login_required

# At bottom of file
@app.route(&quot;/protected&quot;)
@login_required
def protected():
    return &quot;You&apos;re logged in!&quot;
</code></pre>
<p>Now let&apos;s try it out. Delete your <code>data.db</code> file and re-create it using <code>flask shell</code>:</p>
<pre><code>&gt;&gt;&gt; from app import app, db
&gt;&gt;&gt; with app.app_context():
	 	db.create_all()
</code></pre>
<p>Then start the app with <code>flask run</code> and fill in your registration form. It&apos;s interesting to note that when we enable email confirmation, Flask-Security-Too automatically disables password confirmation in the signup form. Now there&apos;s just one password field instead of two:</p>
<p><img src="https://blog.teclado.com/content/images/2023/01/image-2-register-page.png" alt="User email confirmations with Flask-Security-Too" loading="lazy"></p>
<p>Fill this in, making sure to use the email address that you used in your &quot;authorised recipients&quot; for Mailgun.</p>
<p>Then you should get an email (which might be in your Spam folder since we&apos;re using the Mailgun sandbox domain):</p>
<p><img src="https://blog.teclado.com/content/images/2023/01/image-3-confirmation-email.png" alt="User email confirmations with Flask-Security-Too" loading="lazy"></p>
<p>Don&apos;t click the link yet. Instead, navigate to the <code>/protected</code> endpoint with your browser: <a href="http://127.0.0.1:5000/protected?ref=blog.teclado.com">http://127.0.0.1:5000/protected</a>. You should get redirected to the login page and you should see an error:</p>
<p><img src="https://blog.teclado.com/content/images/2023/01/image-4-login-with-errors.png" alt="User email confirmations with Flask-Security-Too" loading="lazy"></p>
<p>These errors use Flask&apos;s <a href="https://flask.palletsprojects.com/en/2.2.x/patterns/flashing/?ref=blog.teclado.com">flashed messages</a>, and you can see there are two messages:</p>
<ul>
<li>Thank you. Confirmation instructions have been sent to (my email).</li>
<li>Please log in to access this page.</li>
</ul>
<p>They both look like errors, but the first one is just a message that Flask-Security-Too flashes when registration is successful. Flashed messages must be displayed once, and since there was no place for it to be shown until now, it stuck around. Ideally, you would use message flashing in the page that you redirect the user to after registration to show this message.</p>
<p>The second message <em>is</em> the error we were expecting!</p>
<p>Now click the confirmation link in your email, and try again. It should work!</p>
<p><img src="https://blog.teclado.com/content/images/2023/01/image-5-logged-in-page.png" alt="User email confirmations with Flask-Security-Too" loading="lazy"></p>
<p>This is how you can use Flask-Security-Too to easily add use registration and confirmation to your Flask applications. Naturally, you&apos;d want to style the login, register, and confirmation pages to match the rest of your application. We&apos;ll look at that in part 3 of this series.</p>
<p>Thank you for reading! If you&apos;d like to learn more about web development using Flask, consider enrolling in our <a href="https://go.tecla.do/web-dev-course-sale?ref=blog.teclado.com">Web Developer Bootcamp with Flask and Python</a>! It&apos;s a complete video course that covers building multiple web apps and deploying them, all using Flask.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Query string arguments in REST APIs with Flask]]></title><description><![CDATA[Query string arguments allow clients to send us extra data in the URL. Learn how to access query string arguments in a plain Flask route and in a Flask-Smorest API.]]></description><link>https://blog.teclado.com/query-string-arguments-in-flask-rest-apis/</link><guid isPermaLink="false">63c873b8357378f172be5c22</guid><category><![CDATA[Flask]]></category><category><![CDATA[REST APIs]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Jose Salvatierra]]></dc:creator><pubDate>Mon, 06 Feb 2023 09:30:02 GMT</pubDate><media:content url="https://blog.teclado.com/content/images/2023/02/2023-02-03-query-strings.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://blog.teclado.com/content/images/2023/02/2023-02-03-query-strings.jpg" alt="Query string arguments in REST APIs with Flask"><p>Query string arguments are values added at the end of the URL which follow the format of <code>name1=value1&amp;name2=value2</code>. You can separate multiple values with an ampersand (as shown). The query string arguments are separated from the main part of the URL by a question mark.</p>
<p>So a complete URL with query string arguments might look like this:</p>
<pre><code>http://your-api.com/item/?tag=furniture
</code></pre>
<p>To get multiple values, you&apos;d include two or more of the same query string argument:</p>
<pre><code>http://your-api.com/item/?tag=furniture&amp;tag=office
</code></pre>
<p>In my REST APIs with Flask and Python course, we build REST APIs using the Flask-Smorest library. In this blog post, let me show you how to use query string arguments together with Flask-Smorest.</p>
<p>If you&apos;d rather watch a video instead, check it out here:</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/oJGRvtJbnmU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<h2 id="how-to-access-query-string-values-in-a-flask-endpoint">How to access query string values in a Flask endpoint</h2>
<p>Accessing the query string values in a Flask endpoint is relatively straightforward:</p>
<ul>
<li>Import the <code>request</code> proxy from <code>flask</code></li>
<li>Query string arguments are stored in a dict-like value under <code>request.args</code>.</li>
</ul>
<p>Here&apos;s a sample endpoint that returns all the stores in your database.</p>
<pre><code class="language-python">@app.route(&quot;/store&quot;)
def get_stores():
    return [{&quot;name&quot;: store.name, &quot;id&quot;: store.id} for store in StoreModel.query.all()]
</code></pre>
<p>Now let&apos;s say you want to add a query string argument value to filter stores by name. Our API users will send us <code>name=Tec</code>, and the API should respond with stores whose name starts with <code>Tec</code>.</p>
<p>To get the query string argument value, we use <code>request.args.get(&quot;name&quot;)</code>:</p>
<pre><code class="language-python"># at top of file
from flask import app, request


# in endpoint
@app.route(&quot;/store&quot;)
def get_stores():
    name = request.args.get(&quot;name&quot;)
    return [{&quot;name&quot;: store.name, &quot;id&quot;: store.id} for store in StoreModel.query.all()]
</code></pre>
<p>Then we can add a filter to <code>.query</code> before calling <code>.all()</code>:</p>
<pre><code class="language-python"># at top of file
from flask import request


# in endpoint
@app.route(&quot;/store&quot;)
def get_stores():
    name = request.args.get(&quot;name&quot;)
    return [
        {&quot;name&quot;: store.name, &quot;id&quot;: store.id}
        for store in StoreModel.query.filter(StoreModel.name.startswith(name)).all()
    ]
</code></pre>
<p>As an aside, if instead of <code>.startswith</code> you wanted to search for the given value anywhere in the store name, you could use this:</p>
<pre><code class="language-python">StoreModel.query.filter(StoreModel.name.like(f&quot;%{name}%&quot;)).all()
</code></pre>
<h2 id="accessing-a-list-of-values-in-a-query-string-argument">Accessing a list of values in a query string argument</h2>
<p>First, ensure the user is sending the query string like so:</p>
<pre><code class="language-text">?tags=furniture&amp;tags=office
</code></pre>
<p>Then in Flask, access the values like so:</p>
<pre><code class="language-python">request.args.getlist(&quot;tags&quot;)  # [&quot;furniture&quot;, &quot;office&quot;]
</code></pre>
<p>If you use <code>request.args.get(&quot;tags&quot;)</code>, then only the first tag value will be returned.</p>
<p>Alternatively, you <em>could</em> (but I don&apos;t recommend it), do this to work with comma-separated values:</p>
<pre><code class="language-python">request.args.get(&quot;tags&quot;).split(&quot;,&quot;)
</code></pre>
<p>The issue I can foresee with this is potentially if a value is received that contains a comma, you&apos;d run into trouble. This probably wouldn&apos;t happen with <em>tags</em>, but it might happen with other values. You&apos;re better off using the recommended approach, which is getting multiple query string arguments and use <code>.getlist()</code>.</p>
<h2 id="how-to-access-query-string-arguments-in-a-flask-smorest-resource">How to access query string arguments in a Flask-Smorest Resource</h2>
<p>Here&apos;s a sample <code>Resource</code> class that returns all the stores in your database. This is similar to the endpoint we showed earlier.</p>
<pre><code class="language-python">@blp.route(&quot;/store&quot;)
class StoreList(MethodView):
    @blp.response(200, StoreSchema(many=True))
    def get(self):
        return StoreModel.query.all()
</code></pre>
<p>Now you want to add the same query string argument as earlier, to filter the stores by name. You could simply do it as in vanilla Flask, by importing <code>request</code> and then using <code>request.args.get()</code>.</p>
<p>But one of the key benefits of Flask-Smorest is the documentation and validation, so let&apos;s try to do better than that.</p>
<p>First we&apos;ll define a schema for our query string arguments:</p>
<pre><code class="language-python">class StoreSearchQueryArgs(BaseSchema):
    name: str | None
</code></pre>
<p>Then we decorate the <code>get</code> method with a <code>@blp.arguments()</code> decorator, making sure to pass <code>location=&quot;query&quot;</code>, like so:</p>
<pre><code class="language-python">@blp.route(&quot;/store&quot;)
class StoreList(MethodView):
    @blp.arguments(StoreSearchQueryArgs, location=&quot;query&quot;)
    @blp.response(200, StoreSchema(many=True))
    def get(self, search_values):
        return StoreModel.query.all()
</code></pre>
<p>Then we can add the filter to <code>.query</code> before calling <code>.all()</code>:</p>
<pre><code class="language-python">@blp.route(&quot;/store&quot;)
class StoreList(MethodView):
    @blp.arguments(StoreSearchQueryArgs, location=&quot;query&quot;)
    @blp.response(200, StoreSchema(many=True))
    def get(self, search_values):
        return StoreModel.query.filter(StoreModel.name.startswith(search_values.get(&quot;name&quot;, &quot;&quot;))).all()
</code></pre>
<p>For a complete Flask-Smorest application that you can see in action, check out <a href="https://gist.github.com/jslvtr/0c3d8ea4360c02fa6c025a586ec4948f?ref=blog.teclado.com">this Gist</a>.</p>
<p>Alright, that&apos;s everything for this blog post! I just wanted to show you how to work with query string parameters in Flask, focusing on REST APIs with Flask-Smorest.</p>
<p>If you want to learn more about REST API development with Flask, consider looking at our course: <a href="https://go.tecla.do/rest-apis-sale?ref=blog.teclado.com">REST APIs with Flask and Python</a>. It&apos;s a complete video-course that covers everything you need, from the basics to working with databases, deployments, background tasks, and much more!</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[User authentication in Flask with Flask-Security-Too]]></title><description><![CDATA[Learn how to easily add user login and signup to any Flask app using the popular extension Flask-Security-Too!]]></description><link>https://blog.teclado.com/user-authentication-flask-security-too/</link><guid isPermaLink="false">63c67f22357378f172be5b9c</guid><category><![CDATA[Flask]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Jose Salvatierra]]></dc:creator><pubDate>Mon, 30 Jan 2023 10:48:50 GMT</pubDate><media:content url="https://blog.teclado.com/content/images/2023/01/1.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://blog.teclado.com/content/images/2023/01/1.jpg" alt="User authentication in Flask with Flask-Security-Too"><p>Adding user authentication to a web application makes it able to tell which user is making a request. It can then return different responses to different users. This is called &quot;personalisation&quot;.</p>
<p>This is article 1 of 3 in this series of blog posts, where I&apos;ll show you how to work with Flask-Security-Too:</p>
<ol>
<li>User authentication in Flask with Flask-Security-Too (this article)</li>
<li><a href="https://blog.teclado.com/email-confirmation-flask-security-too/">User email confirmations with Flask-Security-Too</a></li>
<li><a href="https://blog.teclado.com/customise-pages-emails-flask-security-too/">Customising templates and emails of Flask-Security-Too</a></li>
</ol>
<p>To help you understand user authentication, it&apos;s useful to remind ourselves of the separation between client and server. If you&apos;re developing a Flask application, then you&apos;re developing the server. Your clients are browsers or mobile apps making the requests to your application.</p>
<p>For this series, let&apos;s assume you are developing a Flask application that uses <code>render_template</code> to send HTML to your browser clients.</p>
<p>Before learning about Flask-Security-Too, it&apos;s probably going to be helpful to learn how to code your own user authentication manually. It&apos;s not terribly difficult, and will really aid in understanding how Flask-Security-Too works. We&apos;ve got two blog posts that I recommend reading in order:</p>
<ul>
<li><a href="https://blog.teclado.com/how-to-add-user-logins-to-your-flask-website/">How to add user logins to your Flask website</a></li>
<li><a href="https://blog.teclado.com/protecting-endpoints-in-flask-apps-by-requiring-login/">Protecting endpoints in Flask apps by requiring login</a></li>
</ul>
<p>Read those two and work through the examples provided. When you&apos;re done, you should have a Flask app that supports user signup and login. That&apos;s what we can replace most of our custom logic with Flask-Security-Too!</p>
<h2 id="add-user-registration-and-authentication-using-flask-security-too">Add user registration and authentication using Flask-Security-Too</h2>
<p>This is the bread and butter of Flask-Security-Too! Here&apos;s what you need:</p>
<ul>
<li>A database to store user data (username, password, that kind of thing).</li>
<li>A few configuration options in your Flask app (secret key and password hashing salt).</li>
<li>To actually initialise Flask-Security-too when you create your <code>Flask</code> object.</li>
</ul>
<p>For our database interactions, I will use SQLAlchemy. We don&apos;t have any introductory blog posts on using SQLAlchemy, but our <a href="https://rest-apis-flask.teclado.com/docs/sql_storage_sqlalchemy/create_simple_sqlalchemy_model/?ref=blog.teclado.com">free REST APIs e-book</a> does cover it in detail.</p>
<p>In any case, SQLAlchemy is <em>relatively</em> straightforward: you define a Python class which represents a table in your database. When you create new <em>objects</em> of said class, they can be inserted into the database as separate rows. You can also retrieve data from the database in the form of Python objects.</p>
<p>The aim of SQLAlchemy is to simplify working with data using Python by referring to everything as Python objects, rather than writing SQL yourself.</p>
<p>To work with SQLAlchemy using Flask apps, it&apos;s common to use the <code>flask-sqlalchemy</code> library.</p>
<p>Let&apos;s begin by installing it and <code>flask-security-too</code>. I&apos;ll add this to my <code>requirements.txt</code> file:</p>
<pre><code>flask
flask-sqlalchemy
flask-security-too
flask-mailman
bcrypt
python-dotenv
</code></pre>
<p>The <code>python-dotenv</code> library is added to make it easier to load <code>.env</code> files when running our Flask app. More on that shortly! The <code>flask-mailman</code> library is used for sending confirmation emails (in part 2 of this series). The <code>bcrypt</code> library is used for password hashing.</p>
<p>Install the libraries:</p>
<pre><code class="language-bash">pip install -r requirements.txt
</code></pre>
<p>Now let&apos;s create the <code>SQLAlchemy</code> object, which we will use to connect to the database. Inside <code>database.py</code>, write the following:</p>
<pre><code class="language-python">from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()
</code></pre>
<p>Next up, let&apos;s create our database models. These are the Python classes which represent tables in our database.</p>
<p>Inside a <code>models</code> folder, create <code>auth.py</code> and write the following:</p>
<pre><code class="language-python">from database import db
from flask_security import UserMixin, RoleMixin
from sqlalchemy import Boolean, DateTime, Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship, backref

class RolesUsers(db.Model):
    __tablename__ = &quot;roles_users&quot;
    id = Column(Integer(), primary_key=True)
    user_id = Column(&quot;user_id&quot;, Integer(), ForeignKey(&quot;user.id&quot;))
    role_id = Column(&quot;role_id&quot;, Integer(), ForeignKey(&quot;role.id&quot;))


class Role(db.Model, RoleMixin):
    __tablename__ = &quot;role&quot;
    id = Column(Integer(), primary_key=True)
    name = Column(String(80), unique=True)
    description = Column(String(255))


class User(db.Model, UserMixin):
    __tablename__ = &quot;user&quot;
    id = Column(Integer(), primary_key=True)
    email = Column(String(255), unique=True)
    username = Column(String(255), unique=True, nullable=True)
    password = Column(String(255), nullable=False)
    last_login_at = Column(DateTime())
    current_login_at = Column(DateTime())
    last_login_ip = Column(String(100))
    current_login_ip = Column(String(100))
    login_count = Column(Integer)
    active = Column(Boolean())
    premium = Column(Boolean())
    fs_uniquifier = Column(String(255), unique=True, nullable=False)
    confirmed_at = Column(DateTime())
    roles = relationship(
        &quot;Role&quot;, secondary=&quot;roles_users&quot;, backref=backref(&quot;users&quot;, lazy=&quot;dynamic&quot;)
    )
</code></pre>
<p>As you can see, this defines three tables: one for users, one for roles (similar to permissions), and one for linking users and roles. We won&apos;t be using roles in this tutorial, but Flask-Security-Too requires it.</p>
<p>Finally, in <code>app.py</code> let&apos;s create our Flask app and set up Flask-Security-Too:</p>
<pre><code class="language-python">import os
from flask import Flask
from flask_security import SQLAlchemySessionUserDatastore, Security
from dotenv import load_dotenv
from database import db
from models.auth import User, Role

load_dotenv()

app = Flask(__name__)

app.config[&quot;SECRET_KEY&quot;] = os.environ.get(
    &quot;SECRET_KEY&quot;, &quot;0aedgaii451cef0af8bd6432ec4b317c8999a9f8g77f5f3cb49fb9a8acds51d&quot;
)
app.config[&quot;SECURITY_PASSWORD_SALT&quot;] = os.environ.get(
    &quot;SECURITY_PASSWORD_SALT&quot;,
    &quot;ab3d3a0f6984c4f5hkao41509b097a7bd498e903f3c9b2eea667h16&quot;,
)
app.config[&quot;SQLALCHEMY_TRACK_MODIFICATIONS&quot;] = False
app.config[&quot;SECURITY_REGISTERABLE&quot;] = True

uri = os.getenv(&quot;DATABASE_URL&quot;)
app.config[&quot;SQLALCHEMY_DATABASE_URI&quot;] = uri
db.init_app(app)
user_datastore = SQLAlchemySessionUserDatastore(db.session, User, Role)
security = Security(app, user_datastore)

@app.route(&quot;/&quot;)
def home():
	return &quot;Hello, world!&quot;
</code></pre>
<p>Now that we&apos;ve got this, we need to set up our environment variables. At the very least, we need the <code>DATABASE_URL</code>, to tell our Flask app which database to connect to.</p>
<p>In a new file called <code>.env</code>, write this:</p>
<pre><code>DATABASE_URL=&quot;sqlite:///data.db&quot;
</code></pre>
<p>Now let&apos;s create our tables. In the console (having your virtual environment activated), type:</p>
<pre><code class="language-bash">flask shell
</code></pre>
<p>And there, type:</p>
<pre><code>&gt;&gt;&gt; from app import app, db
&gt;&gt;&gt; with app.app_context():
	 	db.create_all()
</code></pre>
<p>This will create your database using the app configuration, which should create an <code>instance</code> folder, and <code>data.db</code> inside it. If later on you delete <code>instance/data.db</code>, just re-run <code>flask shell</code> and type those commands in to re-create it.</p>
<p>Now you can run your Flask app! First exit the shell (by pressing CTRL+D) and then type:</p>
<pre><code class="language-bash">flask run
</code></pre>
<p>Now you can access the login and signup pages that Flask-Security-Too has added to your Flask app for you. For example, going to <a href="http://127.0.0.1:5000/login?ref=blog.teclado.com">http://127.0.0.1:5000/login</a> should show you something like this:</p>
<p><img src="https://blog.teclado.com/content/images/2023/01/image-1-login-page.png" alt="User authentication in Flask with Flask-Security-Too" loading="lazy"></p>
<p>If you use the menu in that page to go to the &quot;Register&quot; page (or navigate manually to <a href="http://127.0.0.1:5000/register?ref=blog.teclado.com">http://127.0.0.1:5000/register</a>) then you&apos;ll see a very similar form, which adds a password confirmation field.</p>
<p>These pages already include error handling and cookie support for the &quot;Remember Me&quot; checkbox.</p>
<p>However, they don&apos;t look very good. To fix that, you can either add some CSS code to target the existing elements, or you can code your own login and register pages. We won&apos;t talk about that in this blog post, but leave a comment at the end if it&apos;s something you&apos;d like me to write about!</p>
<p>Now that we&apos;ve added user authentication, we can move on to email confirmation in the next article of this series. Thank you for reading, and I&apos;ll see you there!</p>
<p>If you&apos;d like to learn more about web development using Flask, consider enrolling in our <a href="https://go.tecla.do/web-dev-course-sale?ref=blog.teclado.com">Web Developer Bootcamp with Flask and Python</a>! It&apos;s a complete video course that covers building multiple web apps and deploying them, all using Flask.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[New Free Course: FastAPI for Beginners]]></title><description><![CDATA[We've finally published our latest course: FastAPI for Beginners. It's a free course to help you get started with FastAPI.]]></description><link>https://blog.teclado.com/new-free-course-fastapi-for-beginners/</link><guid isPermaLink="false">63a1974e357378f172be455b</guid><category><![CDATA[REST APIs]]></category><dc:creator><![CDATA[Jose Salvatierra]]></dc:creator><pubDate>Tue, 20 Dec 2022 11:21:01 GMT</pubDate><media:content url="https://blog.teclado.com/content/images/2022/12/07_Rest_Api_with_fast_Api_1920x1080_green_v.3_Tavola_disegno_1_copia-1.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.teclado.com/content/images/2022/12/07_Rest_Api_with_fast_Api_1920x1080_green_v.3_Tavola_disegno_1_copia-1.jpg" alt="New Free Course: FastAPI for Beginners"><p>Ever since I made my course on REST APIs with Flask and Python, students have been asking for a FastAPI course.</p><p>After months of work, it&apos;s here! &#x1F389; We&apos;ve just published our <strong><a href="https://teclado.com/fastapi-for-beginners?ref=blog.teclado.com">FastAPI for Beginners</a></strong> course!</p><p>This course helps you learn the ropes of FastAPI so you can create REST APIs using this modern framework. Plus it teaches:</p><ul><li>How to set up a FastAPI project.</li><li>How to work with async databases using the <code>encode/databases</code> package.</li><li>How to perform user authentication with JWTs using FastAPI.</li><li>How to write pydantic models for different use cases.</li></ul><p>In the course, you <strong>build a REST API project using FastAPI</strong> so it&apos;s all based on examples.</p><p>And did I mention that, at least for now, the course is <strong>free</strong>?</p><div class="kg-card kg-button-card kg-align-center"><a href="https://teclado.com/fastapi-for-beginners?ref=blog.teclado.com" class="kg-btn kg-btn-accent">Join the course</a></div><p></p><p>Click the button above to join the course, or use this link: <a href="https://teclado.com/fastapi-for-beginners?ref=blog.teclado.com">https://teclado.com/fastapi-for-beginners</a></p><p>I hope you&apos;ll enjoy the course. I&apos;ll see you on the inside!</p>]]></content:encoded></item><item><title><![CDATA[How to use pyenv to manage Python versions]]></title><description><![CDATA[If you need to install multiple Python versions, going the ol' installer route isn't the best idea. In this article I show you how I manage multiple Python versions, using pyenv.]]></description><link>https://blog.teclado.com/how-to-use-pyenv-manage-python-versions/</link><guid isPermaLink="false">638f557f1a98e73cfd9fecea</guid><category><![CDATA[Learn Python Programming]]></category><dc:creator><![CDATA[Jose Salvatierra]]></dc:creator><pubDate>Mon, 28 Nov 2022 20:20:18 GMT</pubDate><media:content url="https://blog.teclado.com/content/images/2022/11/bench-accounting-C3V88BOoRoM-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.teclado.com/content/images/2022/11/bench-accounting-C3V88BOoRoM-unsplash.jpg" alt="How to use pyenv to manage Python versions"><p>As a Python developer, I install new Python versions when they come out. We get new features and performance improvements, but we don&apos;t always want to use the latest version for everything. Some projects use older Python versions, and other projects must use a specific Python version.</p><p>If you need to install multiple Python versions, going the ol&apos; installer route isn&apos;t the best idea. You end up with multiple Python executables in your <code>PATH</code>, such as <code>python</code>, <code>python2</code>, <code>python3</code>, <code>python3.7</code>, and so on. Also it becomes really tricky to distinguish minor versions, such as <code>python3.10.3</code> from <code>python3.10.4</code>.</p><p>So, <a href="https://github.com/pyenv/pyenv?ref=blog.teclado.com" rel="noopener"><code>pyenv</code></a> to the rescue! This command-line tool will handle installing and running specific Python versions!</p><p>Their README file actually explains <a href="https://github.com/pyenv/pyenv?ref=blog.teclado.com#how-it-works" rel="noopener">how it works</a> and <a href="https://github.com/pyenv/pyenv?ref=blog.teclado.com#installation" rel="noopener">how to install it</a> really well. Give it a read!</p><p>As a note, for Windows I&apos;ve often use the <a href="https://github.com/pyenv-win/pyenv-win?ref=blog.teclado.com" rel="noopener"><code>pyenv-win</code> project</a>, which has worked well for me.</p><p>To install <code>pyenv</code> on MacOS, I&apos;ve just gone for Homebrew:</p><pre><code class="language-bash">brew update
brew install pyenv
</code></pre><p>After installing you&apos;ll have to add a few things to your shell, to configure it. Detailed instructions are in <a href="https://github.com/pyenv/pyenv?ref=blog.teclado.com#set-up-your-shell-environment-for-pyenv" rel="noopener">the README</a>.</p><h2 id="how-i-actually-use-pyenv">How I actually use <code>pyenv</code></h2><p>The great thing about <code>pyenv</code> for me is that it just works, and I only use it at the very beginning of a project, to create a virtual environment.</p><p>Once I&apos;ve created a virtual environment using a specific Python version (which I get from <code>pyenv</code>), from then on I just activate the virtual environment to get that version, and I don&apos;t have to faff around with <code>pyenv</code> any more.</p><p>So, how do you do this?</p><p>First, see if the version of Python you want is available (you may have to update <code>pyenv</code> to see recent versions, which I do with <code>brew update &amp;&amp; brew upgrade pyenv</code>):</p><pre><code class="language-bash">pyenv install --list
</code></pre><p>This will show you a long output, which may contain things like the following:</p><pre><code class="language-text">...
3.10.1
3.10.2
3.10.3
3.10.4
3.10.5
3.10.6
3.10.7
3.11.0rc2
3.11-dev
3.12-dev
...
</code></pre><p>So now you know, you can install from these versions (and again, reminder to update to see the most recently-added versions).</p><p>To install a Python version:</p><pre><code class="language-bash">pyenv install 3.10.7
</code></pre><h2 id="selecting-a-python-version">Selecting a Python version</h2><p>You can see the currently-selected Python version with this command:</p><pre><code class="language-bash">pyenv version
</code></pre><p>And you can see all versions (including the currently selected one) with:</p><pre><code class="language-bash">pyenv versions
</code></pre><p>You can have a <strong>globally selected</strong> Python version, and then <strong>locally selected</strong> Python versions. The global version is used if no local version is selected.</p><p>You can select a local version with:</p><pre><code class="language-bash">pyenv local 3.10.7
</code></pre><p>This creates a <code>.python-version</code> file in your current folder, which tells <code>pyenv</code> to use that Python version.</p><p>If you want to change the global Python version:</p><pre><code class="language-bash">pyenv global 3.10.7
</code></pre><h2 id="using-the-selected-python-version">Using the selected Python version</h2><p>Now, let&apos;s create a virtual environment using <code>pyenv</code>:</p><pre><code class="language-bash">pyenv exec python -m venv .venv
</code></pre><p>This uses <code>pyenv exec</code> to run the <code>python</code> command, which will use Python 3.10.7 in our case. The <code>-m venv .venv</code> argument passed to <code>python</code> tells it to run the <code>venv</code> module, and gives it the name of <code>.venv</code>.</p><p>This will create a virtual environment in a folder called <code>.venv</code>.</p><p>And to be honest, this is how I use <code>pyenv</code>. That&apos;s it, nothing more!</p><p>If you want to test your applications in multiple Python versions, you can also do so easily. Real Python has a great in-depth <a href="https://realpython.com/intro-to-pyenv/?ref=blog.teclado.com#activating-multiple-versions-simultaneously" rel="noopener">blog post</a> that covers that, so feel free to check it out.</p><p>That&apos;s everything! I hope you&apos;ve found this interesting, and you&apos;ve learned something new. If you want to delve deeper into Python and everything it has to offer, please consider joining our <a href="https://go.tecla.do/complete-python-sale?ref=blog.teclado.com" rel="noopener">Complete Python Course</a>. This mega-course covers many Python topics, and it&apos;s currently on sale!</p><p>Thanks for reading, and I&apos;ll see you next time.</p>]]></content:encoded></item></channel></rss>