Build, Test, and Deploy HTTP APIs with FastAPI

aka.ms/pyday-apis

Tips for navigating the slides:
  • Use the left-side menu for a table of contents.
  • Press the copy icon on the upper right of code blocks to copy the code
  • Visit this link for a nice printable version

Who am I?

Pamela smiling with an Olaf statue

Python Cloud Advocate at Microsoft

Formerly: UC Berkeley, Coursera, Khan Academy, Google


Find me online at:

Mastodon @pamelafox@fosstodon.org
Twitter @pamelafox
GitHub www.github.com/pamelafox
Website pamelafox.org

Who are you?

In the chat, share:

  • Where are you watching from?
  • What do you do?


πŸ“‹ Answer our survey!
aka.ms/pydaysurvey


πŸ™πŸ» Help each other learn today! πŸ™πŸΎ

What we'll cover

Bit (the raccoon) lecturing
  • What are HTTP APIs?
  • Building HTTP APIs with FastAPI
  • Hosting HTTP APIs on Azure

HTTP APIs

APIs

API: Application Programming Interface.
A way for one program to talk to another program.

Example:


                from weather import forecast

                todays_forecast = forecast(94702)
                

The weather module's API includes a forecast function that takes a zip code and returns a forecast.

HTTP API

HTTP API: An API that uses HTTP as its communication protocol.


                GET /forecast?zip=94530 HTTP/1.1
                Host: api.weather.com
                

The server sends back an HTTP response:


                HTTP/1.1 200 OK
                Content-Type: text/json; charset=UTF-8
                Content-Length: 30
                {"temperature": 70, "wind": 5}
                

HTTP API response formats

  • JSON
    
                        {"temperature": 70, "wind": 5}
                        
  • XML (including RSS/ATOM)
    
                        <weather-response>
                          <temperature>70</temperature>
                          <wind>5</wind>
                        </weather-response>
                        
  • Image

Read more: Most popular API data formats

HTTP APIs: GET vs. POST

GET: retrieve data from a server.
Often used with query parameters.


                GET /weather?zip=94530 HTTP/1.1
                Host: api.example.com
                

POST: send data to a server.
Data is often in JSON or form-encoded.


                POST /scores HTTP/1.1
                Host: api.example.com
                player=pamela&score=50
                

Processing APIs in Python

Use urllib, requests, or urllib3 to make HTTP requests.


                import urllib3

                resp = urllib3.request('GET',
                    'https://api.zippopotam.us/us/94530')
                result = resp.json()
                

Free APIs to try: Zippopotamus, Sunrise/Sunset, Reddit

Popular APIs

Some examples:

πŸ”‘ Most of the popular APIs require you to sign up for a key so that they can track your usage and limit calls based on your payment level.

Building an HTTP API
...in Python!

Bit (the raccoon) in front of a computer and Python logo

FastAPI

FastAPI is a Python framework designed specifically for building HTTP APIs.

A simple API in FastAPI


                import random
                import fastapi

                app = fastapi.FastAPI()

                @app.get("/generate_name")
                async def generate_name():
                    names = ["Minnie", "Margaret", "Myrtle", "Noa", "Nadia"]
                    random_name = random.choice(names)
                    return {"name": random_name}
                

πŸ‘©πŸΌβ€πŸ’» Want to follow along? Starter repo:
aka.ms/fastapi-starter
https://github.com/pamelafox/pyday-fastapi-starter

How can you follow along?

Option 1: Online development with GitHub Codespaces

Option 2: Local development with VS Code

Option 3: Local development

😍 GitHub Codespaces 😍

Codespaces is an online development environment.

Open a GitHub repo in Codespaces by clicking Code button, selecting Codespaces tab, and clicking Create codespace on main.

Then wait patiently... ☺️

Screenshot of Codespace tab

60 hours of free usage each month. πŸ”— Tips for optimizing quotas

Running FastAPI locally

1. Put code in api/main.py

2. Install requirements


                pip install fastapi
                pip install "uvicorn[standard]"
                

3. Run the server


                uvicorn api.main:app --reload --port=8000
                

4. Try the API and docs
http://127.0.0.1:8000/generate_name
http://127.0.0.1:8000/docs

Adding query parameters


                import random
                import fastapi

                app = fastapi.FastAPI()

                @app.get("/generate_name")
                async def generate_name(max_len: int = None):
                    names = ["Minnie", "Margaret", "Myrtle", "Noa", "Nadia"]
                    if max_len:
                        names = [n for n in names if len(n) <= max_len]
                    random_name = random.choice(names)
                    return {"name": random_name}
                

FastAPI also supports passing parameters in the path, cookies, headers, or body.

Returning errors in FastAPI


                import random
                import fastapi

                app = fastapi.FastAPI()

                @app.get("/generate_name")
                async def generate_name(max_len:int = None):
                    names = ["Minnie", "Margaret", "Myrtle", "Noa", "Nadia"]
                    if max_len:
                        names = [n for n in names if len(n) <= max_len]
                    if len(names) == 0:
                        raise fastapi.HTTPException(status_code=404, detail="No name found")
                    random_name = random.choice(names)
                    return {"name": random_name}
                

Testing
FastAPI apps

Bit (the raccoon) in the clouds next to Azure logo

Configuring pytest and coverage

Create a requirements-dev.txt file:


                -r api/requirements.txt
                fastapi[all]
                pytest
                pytest-cov
                coverage
                

Configure inside pyproject.toml:


                [tool.pytest.ini_options]
                addopts = "-ra --cov api"
                testpaths = [
                    "tests"
                ]
                pythonpath = ['.']
                

API unit tests with pytest

Use the TestClient to make requests to the API:


                import random

                from fastapi.testclient import TestClient

                from api.main import app

                def test_generate_name():
                    with TestClient(app) as client:
                        random.seed(123)
                        response = client.get("/generate_name")
                        assert response.status_code == 200
                        assert response.json() == {"name": "Minnie"}
                

Property-based tests with schemathesis

Add schemathesis to requirements-dev.txt.

Generate tests based on the OpenAPI spec:


                import schemathesis

                from api.main import app

                schema = schemathesis.from_asgi("/openapi.json", app)

                @schema.parametrize()
                def test_api(case):
                    response = case.call_asgi()
                    case.validate_response(response)
                

Run the tests:


                pytest -v tests/property_based_tests.py
                

Productionizing
FastAPI apps

Bit (the raccoon) in the clouds next to Azure logo

Gunicorn

Gunicorn is a production-level server that can run multiple worker processes.

Add gunicorn to requirements.txt:


                fastapi==0.95.1
                uvicorn[standard]==0.22.0
                gunicorn==20.1.0
                

Use gunicorn to run FastAPI app using Uvicorn worker:


                python3 -m gunicorn api.main:app --workers 4 \
                   --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000
                

Configuring gunicorn

Gunicorn can be configured with a gunicorn.conf.py file to adjust worker count based on CPU cores.


                import multiprocessing

                max_requests = 1000
                max_requests_jitter = 50
                log_file = "-"
                bind = "0.0.0.0:8000"
                worker_class = "uvicorn.workers.UvicornWorker"
                workers = (multiprocessing.cpu_count() * 2) + 1
                

The run command can be simplified to:


                python3 -m gunicorn main:app
                

Hosting an HTTP API
on Azure!

Bit (the raccoon) in the clouds next to Azure logo

How can you follow along?

Open the branch with all the changes:
github.com/pamelafox/pyday-fastapi-starter/tree/deploy-to-azure
aka.ms/fastapi-ready2deploy

Easiest option: 😍 GitHub Codespaces 😍

Hosting considerations

  • How much traffic do you expect?
  • How variable will the traffic be?
  • Do you need scale-to-zero?
  • What's your budget?
  • Is it public facing?
  • How will you manage API use?

Azure hosting options

Cloud Azure
Environment Containers PaaS
Azure Kubernetes Service Container Management Azure App Service Serverless
Azure Container Apps Azure Functions

All are good options, depending on your needs.

For today, let's try Azure App Service!

Ways to deploy to Azure App Service

  • VS Code extension
  • Azure Portal (with GitHub integration)
  • Azure CLI
  • Azure Developer CLI with Bicep

Deploying to App Service with VS Code

  • Download the Azure Tools pack extension
  • Select "Create resource" > "Create App Service Web App" Screenshot of options in VS Code to Create App Service Web App
    • Enter a name
    • Select runtime stack (Python 3.11)
    • Select tier (Free - F1)
  • Once that's done, select "Deploy" and select "api" as the path to deploy.

Customizing App Service for FastAPI

App Service doesn't yet know how to automatically run FastAPI apps, so we must tell it.

Either use the Portal:

  • Select "Settings" > "Configuration" in left nav, then select "General settings" tab.
  • In "Startup Command" field, enter
    python3 -m gunicorn main:app
  • Save and wait for server to restart.

Or use the Azure CLI:


                az webapp config set --resource-group <resource-group> --name <app-name> \
                    --startup-file "python3 -m gunicorn main:app"
                

More API examples

Raccoons with laptops

FastAPI + API Management

Azure API Management provides features of a public API: subscription keys, rate limiting, IP blocking, etc.

FastAPI API architecture diagram: Azure Functions, Storage account, API Management policy

πŸ‘€ Demo: fastapi-azf-apim-xualjueoqwvlo-function-app-apim.azure-api.net/public/docs

πŸ‘©πŸΌβ€πŸ’» Code: github.com/pamelafox/fastapi-azure-function-apim

FastAPI + CDN

Azure CDN provides a global network of servers to cache your API responses.

FastAPI API architecture diagram: Functions, CDN, Monitoring

πŸ‘€ Demo: staticmaps-rk5lctcdqzvbs-cdn-endpoint.azureedge.net

πŸ‘©πŸΌβ€πŸ’» Code: github.com/pamelafox/staticmaps-function

FastAPI + scikitlearn

A parameterized API based on a sklearn model.


                @app.get("/model_predict")
                async def model_predict(years_code: int, years_code_pro: int,
                    ed_level: categories.EdLevel, country: categories.Country):
                    X_new = numpy.array([[years_code, years_code_pro, ed_level.value, country.value]])
                    result = models["predicter"].predict(X_new)
                    return {"prediction": round(result[0], 2)}
                

                @asynccontextmanager
                async def lifespan(app: fastapi.FastAPI):
                    models["predicter"] = joblib.load(f"{os.path.dirname(os.path.realpath(__file__))}/model.pkl")
                    yield
                    models.clear()
                

πŸ‘€ Demo: sklearn-model-x72rx6bit7sbe-appservice.azurewebsites.net/docs

πŸ‘©πŸΌβ€πŸ’» Code: github.com/pamelafox/scikitlearn-model-to-fastapi-app

Any questions?

A bunch of raccoon students with computers