Miguel Amaya Camacho
Ingeniero Informático. Socio fundador de Tallanix S.A.C y de Xprende Tech. Activista del Software Libre y miembro fundador de la Comunidad Piurana de Software Libre VICUX y de la Comunidad de Programadores Python Piura.
Ing. José Miguel Amaya Camacho
miguel.amaya99@gmail.com
Recursos: todo se considera un recurso. Cada recurso tiene una URL (Uniform Resource Locator) única.
Verbos HTTP: operaciones sobre recursos se realizan utilizando los verbos HTTP estándar: GET (recuperar datos), POST (crear nuevos recursos), PUT (actualizar recursos existentes) y DELETE (eliminar recursos).
Estado Stateless: Cada solicitud HTTP debe contener toda la información necesaria para comprender y procesar la solicitud. La API no almacena información sobre el estado del cliente entre solicitudes.
Formatos de Datos: Los datos se transfieren entre el cliente y el servidor en formatos estándar: JSON o XML.
Niveles de Abstracción: ofrece diferentes niveles de abstracción, se puede acceder a recursos específicos o hacer operaciones más generales.
Endpoints: Los recursos se acceden a través de endpoints, que son URLs específicas. Cada endpoint corresponde a un recurso o una colección de recursos.
Respuestas de Estado y Datos: código de estado HTTP que indica el resultado de la solicitud y los datos solicitados (si corresponde):
200 OK, 201 Created, 204 No Content
400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found
500 Internal Server Error, 503 Service Unavailable
Separación entre el cliente y el servidor: mejora la portabilidad de la interfaz, facilita tener en servidores distintos el frontend y el backend, los componentes del desarrollo evolucionen de forma independiente.
Visibilidad, fiabilidad y escalabilidad. se puede migrar a otros servidores o cambiar la base de datos, siempre y cuando los datos de cada una de las peticiones se envíen de forma correcta.
La API REST siempre es independiente del tipo de plataformas o lenguajes
Es un web framework moderno para construir APIs con Python 3.6+.
Basado en las anotaciones de tipos estándar de Python.
Usado por Microsoft, Uber, Netflix, etc.
Es bastante rápido, primer lugar frente a frameworks de Python como Django y Flask y otros frameworks de PHP y Javascript.
Creamos nuestro entorno virtual, usaremos Pycharm como IDE.
pip install fastapi
pip install "uvicorn[standard]"
Son anotaciones que se agregan a las definiciones de funciones y variables para indicar qué tipos de datos se esperan. No son obligatorios, pero proporcionan documentación adicional.
# Sin type hints
name = "Alice"
age = 30
is_active = True
# Con type hints
name: str = "Alice"
age: int = 30
is_active: bool = True
def greet(name: str) -> None:
print(f"Hello, {name}!")
def add(x: int, y: int) -> int:
return x + y
from pydantic import BaseModel
class Usuario(BaseModel):
id: int
nombre: str
edad: int
email: str
Biblioteca para validación de datos y gestión de modelos de datos. Proporciona una forma sencilla y eficiente de definir datos y garantizar que cumplan con ciertos esquemas.
try:
usuario = Usuario(id='uno',
nombre='Juan',
edad='veinticinco',
email='juan@example.com')
except ValueError as e:
print(e)
Pydantic valida los datos al crear una instancia del modelo. Si los datos no cumplen con los tipos especificados, se lanzará una excepción:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def root():
return {"Hola mundo"}
main.py
Lo corremos con uvicorn:
uvicorn main:app --reload
FastAPI es una clase de Python que provee toda la funcionalidad para nuestra API.
from fastapi import FastAPI
app = FastAPI()
app, instancia de la clase FastAPI. Punto de interacción principal de toda la API.
Es la misma a la que nos referimos cuando usamos el comando de uvicorn:
El decorador le dice a FastAPI que la función que tiene justo debajo está a cargo de manejar las peticiones que van a:
El path "/"
Usando una operación get
@app.get("/")
Será llamada por FastAPI cada vez que reciba una petición en la URL "/" usando una operación GET.
Procesamos y devolvemos un resultado, se puede devolver dict, list, valores singulares como un str, int, etc.
def root():
return {message: "Hola mundo"}
@app.get("/items/{item_id}")
def read_item(item_id: int, query_param: str = None):
return {"item_id": item_id, "query_param": query_param}
{}
) dentro de la ruta de la aplicación.
@app.get("/items/{item_id}")
async def read_item(item_id: int):
return {"item_id": item_id}
@app.get("/items/")
async def read_item(name: str, price: float):
return {"name": name, "price": price}
@app.post()
@app.put()
@app.patch()
@app.delete()
@app.options()
@app.head()
@app.trace()
Request Body: Son datos enviados por el cliente a la API.
Response Body: Un cuerpo de respuesta son los datos que la API envía al cliente.
Para el envío de datos se utilizan los modelos de Pydantic.
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str = None
@app.post("/items/")
def create_item(item: Item):
return item
Es la técnica de proporcionar automáticamente las dependencias necesarias a las rutas de una aplicación web.
Estas pueden ser funciones que proporcionan acceso a bases de datos, autenticación, servicios web externos o cualquier otra funcionalidad que una ruta pueda necesitar para funcionar correctamente.
Automaticidad: FastAPI maneja automáticamente la resolución y la inyección de dependencias.
Reutilización de Código: Puedes definir una dependencia una vez y reutilizarla en múltiples rutas.
Testing: Facilita la escritura de pruebas unitarias, ya que puedes proporcionar fácilmente dependencias simuladas o de prueba cuando realizas pruebas de tus rutas.
Estructura Limpia: separa la lógica de la ruta de las operaciones que requieren dependencias externas.
from fastapi import FastAPI, Depends
def get_current_user():
return {"username": "johndoe"}
# Uso de la dependencia en una ruta
@app.get("/users/me")
def read_current_user(
current_user: dict = Depends(get_current_user)
):
return current_user
Vamos a explorar cómo FastAPI permite agregar capas de seguridad a tus APIs, lo que incluye autenticación y autorización. Esto es esencial para proteger tus recursos y garantizar que solo usuarios autorizados puedan acceder a ciertas partes de tu API.
Debemos instalar python-multipart:
pip install python-multipart
Esquema de seguridad: definimos el esquema oauth2_scheme
que se utiliza para autenticar a los usuarios y obtener un token de acceso. El argumento tokenUrl
especifica la URL donde los clientes pueden solicitar un token de acceso.
from fastapi.security import OAuth2PasswordBearer
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
Ruta protegida: definimos la ruta /items/ que requiere autenticación de acceso. El parámetro token se inyecta automáticamente en la función cuando un usuario realiza una solicitud. FastAPI valida este token y se asegur que el usuario esté autenticado antes de permitir el acceso a la ruta. Si el token no es válido o no se proporciona, FastAPI devolverá un error HTTP.
from fastapi import FastAPI, Depends
@app.get("/items/")
async def read_items(token: str = Depends(oauth2_scheme)):
return {"token": token}
Creamos un modelo de usuario Pydantic.
from typing import Optional
from pydantic import BaseModel, Field
class User(BaseModel):
username: str
email: str
full_name: Optional[str]
disabled: bool = Field(default=False)
Creamos la dependencia get_current_user, que a su vez tendrá una dependencia con oauth2_scheme. Y utilizará la función fake_decode_token, que toma un token y devuelve el modelo de usuario:
def fake_decode_token(token):
return User(username=token + "fakedecoded",
email="john@example.com",
full_name="John Doe")
async def get_current_user(token: str = Depends(oauth2_scheme)):
user = fake_decode_token(token)
return user
Ahora podemos usar el mismo Depends con nuestro get_current_user en la operación path:
@app.get("/users/me")
async def me(current_user: User = Depends(get_current_user)):
return current_user
Ruta para obtener un token de acceso: definimos una ruta /token/
que permite a los usuarios obtener un token de acceso. El token de acceso se utiliza para autenticar futuras solicitudes a rutas protegidas.
from fastapi.security import OAuth2PasswordRequestForm
@app.post("/token/")
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
# Lógica de autenticación aquí (comprobar usuario y contraseña)
return {"access_token": form_data.username, "token_type": "bearer"}
La integración de bases de datos es esencial para muchas aplicaciones web, y FastAPI simplifica este proceso al proporcionar una forma ordenada de configurar y utilizar bases de datos en nuestras aplicaciones.
Esto permite realizar operaciones CRUD de registros en la base de datos de manera eficiente.
Usaremos sqlalchemy y psycopg para conectarnos a una base de datos previamente creada en postgresql:
pip install sqlalchemy
pip install psycopg2-binary
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
DATABASE_URL = "postgresql://charla:s0p0rt3ccpp@localhost/charla"
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(
autocommit=False, autoflush=False, bind=engine
)
Base = declarative_base()
Archivo database.py
from sqlalchemy import Column, Integer, String
from database import Base
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
description = Column(String)
Archivo models.py
from pydantic import BaseModel
class ItemBase(BaseModel):
name: str
description: str
class Item(ItemBase):
id: int
class Config:
orm_mode = True
class ItemCreate(ItemBase):
pass
Archivo schemas.py
from sqlalchemy.orm import Session
from models import Item
from schemas import ItemCreate
def create_item_db(db: Session, item: ItemCreate):
db_item = Item(**item.model_dump())
db.add(db_item)
db.commit()
db.refresh(db_item)
return db_item
def get_items(db: Session, skip: int = 0, limit: int = 100):
return db.query(Item).offset(skip).limit(limit).all()
Archivo crud.py
from sqlalchemy.orm import Session
from crud import create_item_db, get_items
from database import SessionLocal, Base, engine
from schemas import ItemCreate, Item
Base.metadata.create_all(bind=engine)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
Archivo main.py
@app.post("/items/", response_model=Item)
def create_item(item: ItemCreate, db: Session = Depends(get_db)):
return create_item_db(db=db, item=item)
@app.get("/items_db/")
def items_db(
skip: int = 0,
limit: int = 10,
db: Session = Depends(get_db)
):
items = get_items(db, skip=skip, limit=limit)
return items
Archivo main.py
Se basa en requests.
Se puede usar pytest directamente.
Debemos instalar ambos.
pip install requests
pip install pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
app = FastAPI()
@app.get("/")
def root():
return {"Hola mundo"}
client = TestClient(app)
def test_root():
response = client.get("/")
assert response.status_code == 200
assert response.json() == ["Hola mundo"]
Importamos TestClient.
Creamos una instancia de TestClient pasando la "app "como argumento.
Creamos una función cuyo nombre empiece con test_ (convenciones estándar de pytest).
Usamos el objeto TestClient igual que si fuera requests.
Escribimos declaraciones "assert" simples con las expresiones estándar de Python que necesitamos verificar (pytest estándar).
Corremos el ejemplo:
pytest
Separamos los tests en un archivo diferente dentro de app:
main.py, contiene el código a testear, el mismo del ejemplo anterior.
test_main.py, contiene el código del test
main.py:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def read_main():
return {"msg": "Hello World"}
test_main.py:
from fastapi.testclient import TestClient
from .main import app
client = TestClient(app)
def test_read_main():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"msg": "Hello World"}
Asincronismo
Manejo de FormData
Carga de archivos
Manejo de Cookies
Middleware
CORS
Preguntas
By Miguel Amaya Camacho
Creando APIs en 5 Minutos con FastAPI
Ingeniero Informático. Socio fundador de Tallanix S.A.C y de Xprende Tech. Activista del Software Libre y miembro fundador de la Comunidad Piurana de Software Libre VICUX y de la Comunidad de Programadores Python Piura.