Python Cloud Advocate at Microsoft
Formerly: UC Berkeley, Coursera, Khan Academy, Google
Find me online at:
Mastodon | @pamelafox@fosstodon.org |
@pamelafox | |
GitHub | www.github.com/pamelafox |
Website | pamelafox.org |
In the chat, share:
π Answer our survey!
aka.ms/pydaysurvey
ππ» Help each other learn today! ππΎ
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: 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}
{"temperature": 70, "wind": 5}
<weather-response>
<temperature>70</temperature>
<wind>5</wind>
</weather-response>
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
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
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.
FastAPI is a Python framework designed specifically for building HTTP APIs.
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
Option 1: Online development with GitHub Codespaces
Option 2: Local development with VS Code
Option 3: Local development
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... βΊοΈ
60 hours of free usage each month. π Tips for optimizing quotas
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
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.
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}
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 = ['.']
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"}
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
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
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
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 π
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!
App Service doesn't yet know how to automatically run FastAPI apps, so we must tell it.
Either use the Portal:
python3 -m gunicorn main:app
Or use the Azure CLI:
az webapp config set --resource-group <resource-group> --name <app-name> \
--startup-file "python3 -m gunicorn main:app"
Azure API Management provides features of a public API: subscription keys, rate limiting, IP blocking, etc.
π Demo: fastapi-azf-apim-xualjueoqwvlo-function-app-apim.azure-api.net/public/docs
π©πΌβπ» Code: github.com/pamelafox/fastapi-azure-function-apim
Azure CDN provides a global network of servers to cache your API responses.
π Demo: staticmaps-rk5lctcdqzvbs-cdn-endpoint.azureedge.net
π©πΌβπ» Code: github.com/pamelafox/staticmaps-function
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