Un servidor web es pot configurar com un servidor de recursos que s'accedeixen mijançant un conjunt de funcions "externes" o endpoints (API)
- Entorn de treball
- Primers passos
- OpenAPI
- Decorador d’operació de path
- Paràmetres
- Paràmetres de consulta
- Múltiples paràmetres de path i de consulta
- Request Body
- Activitat
- WebSite
Entorn de treball
Crea un projecte amb uv
uv init python-restcd python-restEl primer pas és instal·lar FastAPI.
uv add "fastapi[standard]"Quan instal·les amb uv add "fastapi[standard]" ve amb algunes dependències estàndard opcionals per defecte, incloent fastapi-cloud-cli, que et permet desplegar a FastAPI Cloud.
Si no vols tenir aquestes dependències opcionals, pots instal·lar en canvi uv add fastapi.
Si vols instal·lar les dependències estàndard però sense el fastapi-cloud-cli, pots instal·lar amb uv add "fastapi[standard-no-fastapi-cloud-cli]"
Primers passos
The app variable is the “instance” of the class FastAPI and will be the main point of interaction to create all your API.
from fastapi import FastAPI
app = FastAPI()
@app.get("/")async def root(): return {"message": "Hello World"}Executa el servidor amb fastapi dev:
fastapi dev main.pyStarting development server 🚀Server started at http://127.0.0.1:8000Documentation at http://127.0.0.1:8000/docsObre el teu navegador a http://127.0.0.1:8000.
Veuràs la resposta JSON com:
{"message": "Hello World"}OpenAPI
FastAPI genera un “schema” amb tota la teva API utilitzant l’estàndard OpenAPI per definir APIs.
Ves a http://127.0.0.1:8000/docs.
Veuràs la documentació interactiva automàtica de l’API (proporcionada per Swagger UI)
I ara, ves a http://127.0.0.1:8000/redoc.
Veuràs la documentació automàtica alternativa (proporcionada per ReDoc).
-
Schema. Un “schema” és una definició o descripció d’alguna cosa. No el codi que ho implementa, sinó només una descripció abstracta.
-
API “schema”. En aquest cas, OpenAPI és una especificació que dicta com definir un schema de la teva API. Aquesta definició del schema inclou els paths de la teva API, els possibles paràmetres que accepten, etc.
-
Data “schema”. El terme “schema” també pot referir-se a la forma d’algunes dades, com un contingut JSON. En aquest cas, significaria els atributs JSON i els tipus de dades que tenen, etc.
OpenAPI defineix un schema d’API per a la teva API. I aquest schema inclou definicions (o “schemas”) de les dades enviades i rebudes per la teva API utilitzant JSON Schema, l’estàndard per schemas de dades JSON.
Si tens curiositat per veure com és el schema OpenAPI en cru, FastAPI genera automàticament un JSON (schema) amb les descripcions de tota la teva API.
Pots veure-ho directament a: http://127.0.0.1:8000/openapi.json.
Mostrarà un JSON que comença amb alguna cosa com:
{ "openapi": "3.1.0", "info": { "title": "FastAPI", "version": "0.1.0" }, "paths": { "/items/": { "get": { "responses": { "200": { "description": "Successful Response", "content": { "application/json": {
...Per a què serveix OpenAPI ?
El schema OpenAPI és el que alimenta els dos sistemes de documentació interactiva inclosos.
I hi ha dotzenes d’alternatives, totes basades en OpenAPI. Pots afegir fàcilment qualsevol d’aquestes alternatives a la teva aplicació construïda amb FastAPI.
També pots utilitzar-lo per generar codi automàticament, per a clients que es comuniquen amb la teva API. Per exemple, aplicacions frontend, mòbils o IoT.
Decorador d’operació de path
@app.get("/")async def root(): return {"message": "Hello World"}Path
“Path” aquí es refereix a l’última part de l’URL començant des del primer /.
Així, en un URL com https://xtec.dev/item/4 el path seria /item/4.
Un “path” també es coneix habitualment com a “endpoint” o “ruta”.
Operació
“Operació” aquí es refereix a un dels “mètodes” HTTP.
Un de: POST, GET, PUT, DELETE.
… i els més exòtics: PATCH, OPTIONS, HEAD, TRACE
En el protocol HTTP, pots comunicar-te amb cada path utilitzant un (o més) d’aquests “mètodes”.
Quan construeixes APIs, normalment utilitzes aquests mètodes HTTP específics per realitzar una acció específica.
Normalment utilitzes:
POST: per crear dades.GET: per llegir dades.PUT: per actualitzar dades.DELETE: per esborrar dades.
Així, a OpenAPI, cada un dels mètodes HTTP s’anomena una “operació”.
Defineix un decorador d’operació de path
@app.get("/")async def root(): return {"message": "Hello World"}El @app.get("/") indica a FastAPI que la funció just a sota s’encarrega de gestionar les peticions que van a:
- el path
/ - utilitzant una operació
get
Aquesta sintaxi @something a Python s’anomena un “decorador”.
El poses a sobre d’una funció. Com un barret decoratiu maco (suposo que d’aquí ve el terme).
Un “decorador” pren la funció de sota i fa alguna cosa amb ella.
En el nostre cas, aquest decorador indica a FastAPI que la funció de sota correspon al path / amb una operació get.
És el “decorador d’operació de path”
També pots utilitzar les altres operacions: @app.post(), @app.put(), @app.delete()
I les més exòtiques: @app.options(), @app.head(), @app.patch(), @app.trace()
Ets lliure d’utilitzar cada operació (mètode HTTP) com vulguis.
FastAPI no imposa cap significat específic.
La informació aquí es presenta com una guia, no com un requisit.
Per exemple, quan utilitzes GraphQL normalment realitzes totes les accions utilitzant només operacions POST.
Defineix la funció d’operació de path
Aquesta és la nostra “funció d’operació de path”:
- path: és
/. - operació: és
get. - funció: és la funció sota el “decorador” (sota
@app.get("/")).
@app.get("/")async def root(): return {"message": "Hello World"}Aquesta és una funció Python.
Serà cridada per FastAPI cada vegada que rebi una petició a l’URL "/" utilitzant una operació GET.
En aquest cas, és una funció asíncrona.
Retorna el contingut
@app.get("/")async def root(): return {"message": "Hello World"}Pots retornar un dict, list, valors singulars com str, int, etc.
També pots retornar models Pydantic (veuràs més sobre això més endavant).
Hi ha molts altres objectes i models que es convertiran automàticament a JSON (incloent ORMs, etc). Prova d’utilitzar els teus favorits, és molt probable que ja estiguin suportats.
Paràmetres
Pots declarar “paràmetres” o “variables” de path amb la mateixa sintaxi utilitzada pels format strings de Python:
from fastapi import FastAPI
app = FastAPI()
@app.get("/item/{id}")async def read_item(id): return {"id": id}El valor del paràmetre de path item_id es passarà a la teva funció com l’argument item_id.
Així, si executes aquest exemple i vas a http://127.0.0.1:8000/item/foo, veuràs una resposta de:
{"id":"foo"}Paràmetres de path amb tipus
Pots declarar el tipus d’un paràmetre de path a la funció, utilitzant les anotacions de tipus estàndard de Python:
from fastapi import FastAPI
app = FastAPI()
@app.get("/item/{id}")async def read_item(id: int): return {"id": id}En aquest cas, id es declara com un int.
Conversió de dades
Si executes aquest exemple i obres el teu navegador a http://127.0.0.1:8000/item/3, veuràs una resposta de:
{"item_id":3}Fixa’t que el valor que la teva funció ha rebut (i retornat) és 3, com un int de Python, no una string "3".
Així, amb aquesta declaració de tipus, FastAPI et dóna “parsing” automàtic de peticions.
Validació de dades
Però si vas al navegador a http://127.0.0.1:8000/item/foo, veuràs un error HTTP ben explicat:
{ "detail": [ { "type": "int_parsing", "loc": [ "path", "id" ], "msg": "Input should be a valid integer, unable to parse string as an integer", "input": "foo" } ]}perquè el paràmetre de path id tenia un valor de "foo", que no és un int.
El mateix error apareixeria si proporcionessis un float en lloc d’un int, com a: http://127.0.0.1:8000/item/4.2
Així, amb la mateixa declaració de tipus de Python, FastAPI et dóna validació de dades.
Fixa’t que l’error també indica exactament el punt on la validació no ha passat.
Això és increïblement útil mentre desenvolupes i depures codi que interactua amb la teva API.
Documentació
I quan obres el teu navegador a http://127.0.0.1:8000/docs, veuràs una documentació automàtica, interactiva de l’API com:

Fixa’t que el paràmetre id es declara com un enter.
Pydantic
Tota la validació de dades es realitza sota el capó per Python - Pydantic, així que obtens tots els beneficis d’això. I saps que estàs en bones mans.
Pots utilitzar les mateixes declaracions de tipus amb str, float, bool i molts altres tipus de dades complexos.
L’ordre importa
En crear operacions de path, pots trobar situacions en què tens un path fix.
Com /user/me, diguem que és per obtenir dades sobre l’usuari actual.
I després també pots tenir un path /user/{id} per obtenir dades sobre un usuari específic segons algun ID d’usuari.
Com que les operacions de path s’avaluen en ordre, necessites assegurar-te que el path per a /user/me es declara abans que el de /user/{id}:
@app.get("/user/me")async def read_user_me(): return {"user_id": "the current user"}
@app.get("/user/{id}")async def read_user(id: str): return {"id": id}En cas contrari, el path per a /user/{id} coincidiria també per a /user/me, “pensant” que està rebent un paràmetre id amb un valor de “me”.
De manera similar, no pots redefinir una operació de path:
@app.get("/user")async def read_users(): return ["Rick", "Morty"]
@app.get("/user")async def read_users2(): return ["Bean", "Elfo"]La primera sempre s’utilitzarà ja que el path coincideix primer.
Valors predefinits
Si tens una operació de path que rep un paràmetre de path, però vols que els possibles valors vàlids del paràmetre de path estiguin predefinits, pots utilitzar un Enum estàndard de Python.
Importa Enum i crea una subclasse que hereti de str i de Enum.
Heretant de str, la documentació de l’API podrà saber que els valors han de ser de tipus string i es renderitzaran correctament.
Després crea atributs de classe amb valors fixos, que seran els valors vàlids disponibles:
class OrderType(str, Enum): buy = "buy" sell = "sell"Després crea un paràmetre de path amb una anotació de tipus utilitzant la classe enum que has creat (OrderType en aquest cas):
@app.get("/stock/google/{type}")async def stock_get(type: OrderType): match type: case OrderType.buy: return {"google": 101.10} case OrderType.sell: return {"google": 101.53}Com que els valors disponibles per al paràmetre de path estan predefinits, la documentació interactiva els pot mostrar de manera elegant.
Pots retornar membres enum des de la teva operació de path, fins i tot imbricats en un cos JSON (per exemple, un dict).
Es convertiran als seus valors corresponents (strings en aquest cas) abans de retornar-los al client:
@app.get("/stock/google/{type}")async def stock_get(type: OrderType): match type: case OrderType.buy: return {"symbol": "google", "price": 101.10, "type": type} case OrderType.sell: return {"symbol": "google", "price": 101.53, "type": type}Paràmetres de consulta
Quan declares altres paràmetres de funció que no formen part dels paràmetres de path, s’interpreten automàticament com a paràmetres de “consulta”.
persons = ["Jordi", "Montserrat", "Pere", "Núria", "Marc", "Carme", "Pau", "Mercè", "Josep", "Eulàlia"]
@app.get("/person")async def person_get(skip: int = 0, limit: int = 5): return [{"name": person} for person in persons[skip:skip + limit]]La consulta és el conjunt de parells clau-valor que van després del ? en un URL, separats per caràcters &.
Per exemple, a la URL http://127.0.0.1:8000/person?skip=2&limit=3 els paràmetres de consulta són:
skip: amb un valor de 2limit: amb un valor de 10
Com que formen part de l’URL, són “naturalment” strings.
Però quan els declares amb tipus de Python (a l’exemple anterior, com int), es converteixen a aquest tipus i es validen contra ell.
Tot el mateix procés que s’aplica als paràmetres de path també s’aplica als paràmetres de consulta: “parsing” de dades, validació de dades i documentació automàtica.
Valors per defecte
Com que els paràmetres de consulta no són una part fixa d’un path, poden ser opcionals i poden tenir valors per defecte.
A l’exemple anterior tenen valors per defecte de skip=0 i limit=5.
Així, anar a la URL http://127.0.0.1:8000/person seria el mateix que anar a http://127.0.0.1:8000/items/?skip=0&limit=5
Però si vas a, per exemple http://127.0.0.1:8000/person?skip=8 els valors dels paràmetres a la teva funció seran:
skip=8: perquè ho has establert a l’URLlimit=5: perquè aquest era el valor per defecte
Paràmetres opcionals
De la mateixa manera, pots declarar paràmetres de consulta opcionals, establint el seu valor per defecte a None:
@app.get("/item/{id}")async def item_get(id: str, q: str | None = None): if q: return {"id": id, "q": q} return {"id": id}En aquest cas, el paràmetre de funció q serà opcional, i serà None per defecte.
També observa que FastAPI és prou intel·ligent per adonar-se que el paràmetre de path id és un paràmetre de path i q no ho és, així que és un paràmetre de consulta.
Conversió de tipus de paràmetre de consulta
També pots declarar tipus bool, i es convertiran:
@app.get("/item/{id}")async def item_get(id: str, q: str | None = None, short: bool = False): item = {"id": id} if q: item.update({"q": q}) if not short: item.update( {"description": "This is an amazing item that has a long description"} ) return itemEn aquest cas, si vas a:
http://127.0.0.1:8000/item/foo?short=1http://127.0.0.1:8000/item/foo?short=Truehttp://127.0.0.1:8000/item/foo?short=truehttp://127.0.0.1:8000/item/foo?short=onhttp://127.0.0.1:8000/item/foo?short=yes
o qualsevol altra variació de majúscules i minúscules (majúscules, primera lletra en majúscula, etc.), la teva funció veurà el paràmetre short amb un valor bool de True. En cas contrari com a False.
Múltiples paràmetres de path i de consulta
Pots declarar múltiples paràmetres de path i de consulta alhora, FastAPI sap quin és quin.
I no has de declarar-los en cap ordre específic.
Es detectaran pel nom:
@app.get("/user/{user_id}/item/{item_id}")async def user_item_get( user_id: int, item_id: str, q: str | None = None, short: bool = False): item = {"item_id": item_id, "owner_id": user_id} if q: item.update({"q": q}) if not short: item.update( {"description": "This is an amazing item that has a long description"} ) return itemParàmetres de consulta obligatoris
Quan declares un valor per defecte per a paràmetres que no són de path (de moment, només hem vist paràmetres de consulta), llavors no és obligatori.
Si no vols afegir un valor específic però simplement fer-lo opcional, estableix el valor per defecte com a None.
Però quan vols fer un paràmetre de consulta obligatori, pots simplement no declarar cap valor per defecte:
@app.get("/item/{item_id}")async def item_get(item_id: str, needy: str): item = {"item_id": item_id, "needy": needy} return itemAquí el paràmetre de consulta needy és un paràmetre de consulta obligatori de tipus str.
Si obres al teu navegador una URL com http://127.0.0.1:8000/item/foo sense afegir el paràmetre obligatori needy, veuràs un error com:
{ "detail": [ { "type": "missing", "loc": [ "query", "needy"], "msg": "Field required", "input": null }]}Com que needy és un paràmetre obligatori, hauries d’establir-lo a la URL http://127.0.0.1:8000/item/foo?needy=sooooneedy això funcionaria:
{ "item_id": "foo", "needy": "sooooneedy"}I, per descomptat, pots definir alguns paràmetres com a obligatoris, alguns amb un valor per defecte, i alguns completament opcionals:
@app.get("/item/{item_id}")async def item_get( item_id: str, needy: str, skip: int = 0, limit: int | None = None): item = {"item_id": item_id, "needy": needy, "skip": skip, "limit": limit} return itemEn aquest cas, hi ha 3 paràmetres de consulta:
needy, un str obligatori.skip, unintamb un valor per defecte de0.limit, unintopcional.
També podries utilitzar Enums de la mateixa manera que amb els Paràmetres de Path.
Request Body
Quan necessites enviar dades d’un client (diguem, un navegador) a la teva API, les envies com a cos de la petició.
Un cos de petició són dades enviades pel client a la teva API. Un cos de resposta són les dades que la teva API envia al client.
La teva API gairebé sempre ha d’enviar un cos de resposta. Però els clients no necessàriament han d’enviar cossos de petició sempre, de vegades només sol·liciten un path, potser amb alguns paràmetres de consulta, però no envien un cos.
Per declarar un cos de petició, utilitzes models de Python - Pydantic amb tot el seu poder i beneficis.
Per enviar dades, hauries d’utilitzar un de: POST (el més comú), PUT, DELETE o PATCH.
Enviar un cos amb una petició GET té un comportament indefinit en les especificacions, no obstant això, està suportat per FastAPI, només per a casos d’ús molt complexos/extrems.
Com que està desaconsellat, la documentació interactiva amb Swagger UI no mostrarà la documentació del cos quan s’utilitza GET, i els proxies al mig poden no suportar-ho.
Importar BaseModel de Pydantic
Primer, necessites importar BaseModel de pydantic:
from pydantic import BaseModel
class Item(BaseModel): name: str description: str | None = None price: float tax: float | None = None
@app.post("/item")async def item_post(item: Item): return itemCrea el teu model de dades
Després declares el teu model de dades com una classe que hereta de BaseModel.
Utilitza tipus estàndard de Python per a tots els atributs:
class Item(BaseModel): name: str description: str | None = None price: float tax: float | None = NoneIgual que quan declares paràmetres de consulta, quan un atribut del model té un valor per defecte, no és obligatori. En cas contrari, és obligatori. Utilitza None per fer-lo només opcional.
Per exemple, aquest model anterior declara un “objecte” JSON (o dict de Python) com:
{ "name": "Foo", "description": "An optional description", "price": 45.2, "tax": 3.5}…com que description i tax són opcionals (amb un valor per defecte de None), aquest “objecte” JSON també seria vàlid:
{ "name": "Foo", "price": 45.2}Declara’l com a paràmetre
Per afegir-lo a la teva operació de path, declara’l de la mateixa manera que vas declarar els paràmetres de path i de consulta:
@app.post("/item")async def item_post(item: Item): return item…i declara el seu tipus com el model que has creat, Item.
Documentació automàtica
Els esquemes JSON dels teus models formaran part del teu esquema OpenAPI generat, i es mostraran en la documentació interactiva de l’API.
I també s’utilitzaran en la documentació de l’API dins de cada operació de path que els necessiti.
Utilitza el model
Dins de la funció, pots accedir a tots els atributs de l’objecte del model directament:
@app.post("/item")async def item_post(item: Item): item_dict = item.model_dump() if item.tax is not None: price_with_tax = item.price + item.tax item_dict.update({"price_with_tax": price_with_tax}) return item_dictRequest body + path parameters
Pots declarar paràmetres de path i cos de petició alhora.
FastAPI reconeixerà que els paràmetres de la funció que coincideixen amb paràmetres de path s’han de prendre del path, i que els paràmetres de la funció que es declaren com a models Pydantic s’han de prendre del cos de la petició.
from fastapi import FastAPIfrom pydantic import BaseModel
class Item(BaseModel): name: str description: str | None = None price: float tax: float | None = None
@app.put("/item/{item_id}")async def item_put(item_id: int, item: Item): return {"item_id": item_id, **item.dict()}Cos de petició + path + paràmetres de consulta
També pots declarar paràmetres de cos, path i consulta, tots alhora.
FastAPI reconeixerà cadascun d’ells i prendrà les dades del lloc correcte.
@app.put("/item/{item_id}")async def item_put(item_id: int, item: Item, q: str | None = None): result = {"item_id": item_id, **item.dict()} if q: result.update({"q": q}) return resultEls paràmetres de la funció es reconeixeran de la manera següent:
- Si el paràmetre també es declara al path, s’utilitzarà com a paràmetre de path.
- Si el paràmetre és d’un tipus singular (com
int,float,str,bool, etc.) s’interpretarà com a paràmetre de consulta. - Si el paràmetre es declara com del tipus d’un model Pydantic, s’interpretarà com a cos de petició.
FastAPI sabrà que el valor de q no és obligatori pel valor per defecte = None.
El str | None no l’utilitza FastAPI per determinar que el valor no és obligatori, sabrà que no és obligatori perquè té un valor per defecte de = None.
Però afegir les anotacions de tipus permetrà que el teu editor et doni un millor suport i detecti errors.
Activitat
Crea un API per gestionar un center mèdic.
A continuació tens la base de dades:
Has de tenir endpoints per doctor, patient, visit
Not Found
Si accedeixes a un dict i al clau que no existeix, és produeix en error 500 Internal Server Error perquè estem accedint al dict amb l’operador [] sense verificar que existeix una entrada per la id que ens han passat com argument.
El que has de fer és modificar el codi per tornar un error 404 Not Found:
@app.get("/employee/{id}")def employee(id: int):
if id not in employees.keys(): raise HTTPException(status_code=404, detail="Employee not found")
return {"name": employees[id], "job": "clerk"}WebSite
Ves a Render - Dashboard
Desplega la teva aplicació en el nivell gratuït.
El Start Command és:
fastapi run main.py