Aplicaciones más grandes - Múltiples archivos¶
🌐 Traducción por IA y humanos
Esta traducción fue hecha por IA guiada por humanos. 🤝
Podría tener errores al interpretar el significado original, o sonar poco natural, etc. 🤖
Puedes mejorar esta traducciónayudándonos a guiar mejor al LLM de IA.
Si estás construyendo una aplicación o una API web, rara vez podrás poner todo en un solo archivo.
FastAPI proporciona una herramienta conveniente para estructurar tu aplicación manteniendo toda la flexibilidad.
Información
Si vienes de Flask, esto sería el equivalente a los Blueprints de Flask.
Un ejemplo de estructura de archivos¶
Digamos que tienes una estructura de archivos como esta:
.├── app│ ├── __init__.py│ ├── main.py│ ├── dependencies.py│ └── routers│ │ ├── __init__.py│ │ ├── items.py│ │ └── users.py│ └── internal│ ├── __init__.py│ └── admin.pyConsejo
Hay varios archivos__init__.py: uno en cada directorio o subdirectorio.
Esto es lo que permite importar código de un archivo a otro.
Por ejemplo, enapp/main.py podrías tener una línea como:
from app.routers import items- El directorio
appcontiene todo. Y tiene un archivo vacíoapp/__init__.py, por lo que es un "paquete de Python" (una colección de "módulos de Python"):app. - Contiene un archivo
app/main.py. Como está dentro de un paquete de Python (un directorio con un archivo__init__.py), es un "módulo" de ese paquete:app.main. - También hay un archivo
app/dependencies.py, al igual queapp/main.py, es un "módulo":app.dependencies. - Hay un subdirectorio
app/routers/con otro archivo__init__.py, por lo que es un "subpaquete de Python":app.routers. - El archivo
app/routers/items.pyestá dentro de un paquete,app/routers/, por lo que es un submódulo:app.routers.items. - Lo mismo con
app/routers/users.py, es otro submódulo:app.routers.users. - También hay un subdirectorio
app/internal/con otro archivo__init__.py, por lo que es otro "subpaquete de Python":app.internal. - Y el archivo
app/internal/admin.pyes otro submódulo:app.internal.admin.
La misma estructura de archivos con comentarios:
.├──app# "app" es un paquete de Python│ ├──__init__.py# este archivo hace que "app" sea un "paquete de Python"│ ├──main.py# módulo "main", por ejemplo import app.main│ ├──dependencies.py# módulo "dependencies", por ejemplo import app.dependencies│ └──routers# "routers" es un "subpaquete de Python"│ │├──__init__.py# hace que "routers" sea un "subpaquete de Python"│ │├──items.py# submódulo "items", por ejemplo import app.routers.items│ │└──users.py# submódulo "users", por ejemplo import app.routers.users│ └──internal# "internal" es un "subpaquete de Python"│ ├──__init__.py# hace que "internal" sea un "subpaquete de Python"│ └──admin.py# submódulo "admin", por ejemplo import app.internal.adminAPIRouter¶
Digamos que el archivo dedicado solo a manejar usuarios es el submódulo en/app/routers/users.py.
Quieres tener laspath operations relacionadas con tus usuarios separadas del resto del código, para mantenerlo organizado.
Pero todavía es parte de la misma aplicación/web API deFastAPI (es parte del mismo "paquete de Python").
Puedes crear laspath operations para ese módulo usandoAPIRouter.
ImportarAPIRouter¶
Lo importas y creas una "instance" de la misma manera que lo harías con la claseFastAPI:
fromfastapiimportAPIRouterrouter=APIRouter()@router.get("/users/",tags=["users"])asyncdefread_users():return[{"username":"Rick"},{"username":"Morty"}]@router.get("/users/me",tags=["users"])asyncdefread_user_me():return{"username":"fakecurrentuser"}@router.get("/users/{username}",tags=["users"])asyncdefread_user(username:str):return{"username":username}Path operations conAPIRouter¶
Y luego lo usas para declarar tuspath operations.
Úsalo de la misma manera que usarías la claseFastAPI:
fromfastapiimportAPIRouterrouter=APIRouter()@router.get("/users/",tags=["users"])asyncdefread_users():return[{"username":"Rick"},{"username":"Morty"}]@router.get("/users/me",tags=["users"])asyncdefread_user_me():return{"username":"fakecurrentuser"}@router.get("/users/{username}",tags=["users"])asyncdefread_user(username:str):return{"username":username}Puedes pensar enAPIRouter como una clase "miniFastAPI".
Se soportan todas las mismas opciones.
Todos los mismosparameters,responses,dependencies,tags, etc.
Consejo
En este ejemplo, la variable se llamarouter, pero puedes nombrarla como quieras.
Vamos a incluir esteAPIRouter en la aplicación principal deFastAPI, pero primero, revisemos las dependencias y otroAPIRouter.
Dependencias¶
Vemos que vamos a necesitar algunas dependencias usadas en varios lugares de la aplicación.
Así que las ponemos en su propio módulodependencies (app/dependencies.py).
Ahora utilizaremos una dependencia simple para leer un headerX-Token personalizado:
fromtypingimportAnnotatedfromfastapiimportHeader,HTTPExceptionasyncdefget_token_header(x_token:Annotated[str,Header()]):ifx_token!="fake-super-secret-token":raiseHTTPException(status_code=400,detail="X-Token header invalid")asyncdefget_query_token(token:str):iftoken!="jessica":raiseHTTPException(status_code=400,detail="No Jessica token provided")Consejo
Estamos usando un header inventado para simplificar este ejemplo.
Pero en casos reales obtendrás mejores resultados usando lasutilidades de Seguridad integradas.
Otro módulo conAPIRouter¶
Digamos que también tienes los endpoints dedicados a manejar "items" de tu aplicación en el móduloapp/routers/items.py.
Tienespath operations para:
/items//items/{item_id}
Es toda la misma estructura que conapp/routers/users.py.
Pero queremos ser más inteligentes y simplificar un poco el código.
Sabemos que todas laspath operations en este módulo tienen el mismo:
- Prefijo de path:
/items. tags: (solo una etiqueta:items).responsesextra.dependencies: todas necesitan esa dependenciaX-Tokenque creamos.
Entonces, en lugar de agregar todo eso a cadapath operation, podemos agregarlo alAPIRouter.
fromfastapiimportAPIRouter,Depends,HTTPExceptionfrom..dependenciesimportget_token_headerrouter=APIRouter(prefix="/items",tags=["items"],dependencies=[Depends(get_token_header)],responses={404:{"description":"Not found"}},)fake_items_db={"plumbus":{"name":"Plumbus"},"gun":{"name":"Portal Gun"}}@router.get("/")asyncdefread_items():returnfake_items_db@router.get("/{item_id}")asyncdefread_item(item_id:str):ifitem_idnotinfake_items_db:raiseHTTPException(status_code=404,detail="Item not found")return{"name":fake_items_db[item_id]["name"],"item_id":item_id}@router.put("/{item_id}",tags=["custom"],responses={403:{"description":"Operation forbidden"}},)asyncdefupdate_item(item_id:str):ifitem_id!="plumbus":raiseHTTPException(status_code=403,detail="You can only update the item: plumbus")return{"item_id":item_id,"name":"The great Plumbus"}Como el path de cadapath operation tiene que empezar con/, como en:
@router.get("/{item_id}")asyncdefread_item(item_id:str):......el prefijo no debe incluir un/ final.
Así que, el prefijo en este caso es/items.
También podemos agregar una lista detags yresponses extra que se aplicarán a todas laspath operations incluidas en este router.
Y podemos agregar una lista dedependencies que se añadirá a todas laspath operations en el router y se ejecutarán/solucionarán por cada request que les haga.
Consejo
Nota que, al igual quedependencias en decoradores depath operations, ningún valor será pasado a tupath operation function.
El resultado final es que los paths de item son ahora:
/items//items/{item_id}
...como pretendíamos.
- Serán marcados con una lista de tags que contiene un solo string
"items". - Estos "tags" son especialmente útiles para los sistemas de documentación interactiva automática (usando OpenAPI).
- Todos incluirán las
responsespredefinidas. - Todas estaspath operations tendrán la lista de
dependenciesevaluadas/ejecutadas antes de ellas. - Si también declaras dependencias en unapath operation específica,también se ejecutarán.
- Las dependencias del router se ejecutan primero, luego las
dependenciesen el decorador, y luego las dependencias de parámetros normales. - También puedes agregardependencias de
Securityconscopes.
Consejo
Tenerdependencies en elAPIRouter puede ser usado, por ejemplo, para requerir autenticación para un grupo completo depath operations. Incluso si las dependencias no son añadidas individualmente a cada una de ellas.
Revisa
Los parámetrosprefix,tags,responses, ydependencies son (como en muchos otros casos) solo una funcionalidad deFastAPI para ayudarte a evitar la duplicación de código.
Importar las dependencias¶
Este código vive en el móduloapp.routers.items, el archivoapp/routers/items.py.
Y necesitamos obtener la función de dependencia del móduloapp.dependencies, el archivoapp/dependencies.py.
Así que usamos un import relativo con.. para las dependencias:
fromfastapiimportAPIRouter,Depends,HTTPExceptionfrom..dependenciesimportget_token_headerrouter=APIRouter(prefix="/items",tags=["items"],dependencies=[Depends(get_token_header)],responses={404:{"description":"Not found"}},)fake_items_db={"plumbus":{"name":"Plumbus"},"gun":{"name":"Portal Gun"}}@router.get("/")asyncdefread_items():returnfake_items_db@router.get("/{item_id}")asyncdefread_item(item_id:str):ifitem_idnotinfake_items_db:raiseHTTPException(status_code=404,detail="Item not found")return{"name":fake_items_db[item_id]["name"],"item_id":item_id}@router.put("/{item_id}",tags=["custom"],responses={403:{"description":"Operation forbidden"}},)asyncdefupdate_item(item_id:str):ifitem_id!="plumbus":raiseHTTPException(status_code=403,detail="You can only update the item: plumbus")return{"item_id":item_id,"name":"The great Plumbus"}Cómo funcionan los imports relativos¶
Consejo
Si sabes perfectamente cómo funcionan los imports, continúa a la siguiente sección abajo.
Un solo punto., como en:
from.dependenciesimportget_token_headersignificaría:
- Partiendo en el mismo paquete en el que este módulo (el archivo
app/routers/items.py) habita (el directorioapp/routers/)... - busca el módulo
dependencies(un archivo imaginario enapp/routers/dependencies.py)... - y de él, importa la función
get_token_header.
Pero ese archivo no existe, nuestras dependencias están en un archivo enapp/dependencies.py.
Recuerda cómo se ve nuestra estructura de aplicación/archivo:
Los dos puntos.., como en:
from..dependenciesimportget_token_headersignifican:
- Partiendo en el mismo paquete en el que este módulo (el archivo
app/routers/items.py) habita (el directorioapp/routers/)... - ve al paquete padre (el directorio
app/)... - y allí, busca el módulo
dependencies(el archivo enapp/dependencies.py)... - y de él, importa la función
get_token_header.
¡Eso funciona correctamente! 🎉
De la misma manera, si hubiéramos usado tres puntos..., como en:
from...dependenciesimportget_token_headereso significaría:
- Partiendo en el mismo paquete en el que este módulo (el archivo
app/routers/items.py) habita (el directorioapp/routers/)... - ve al paquete padre (el directorio
app/)... - luego ve al paquete padre de ese paquete (no hay paquete padre,
appes el nivel superior 😱)... - y allí, busca el módulo
dependencies(el archivo enapp/dependencies.py)... - y de él, importa la función
get_token_header.
Eso se referiría a algún paquete arriba deapp/, con su propio archivo__init__.py, etc. Pero no tenemos eso. Así que, eso lanzaría un error en nuestro ejemplo. 🚨
Pero ahora sabes cómo funciona, para que puedas usar imports relativos en tus propias apps sin importar cuán complejas sean. 🤓
Agregar algunostags,responses, ydependencies personalizados¶
No estamos agregando el prefijo/items ni lostags=["items"] a cadapath operation porque los hemos añadido alAPIRouter.
Pero aún podemos agregarmástags que se aplicarán a unapath operation específica, y también algunasresponses extra específicas para esapath operation:
fromfastapiimportAPIRouter,Depends,HTTPExceptionfrom..dependenciesimportget_token_headerrouter=APIRouter(prefix="/items",tags=["items"],dependencies=[Depends(get_token_header)],responses={404:{"description":"Not found"}},)fake_items_db={"plumbus":{"name":"Plumbus"},"gun":{"name":"Portal Gun"}}@router.get("/")asyncdefread_items():returnfake_items_db@router.get("/{item_id}")asyncdefread_item(item_id:str):ifitem_idnotinfake_items_db:raiseHTTPException(status_code=404,detail="Item not found")return{"name":fake_items_db[item_id]["name"],"item_id":item_id}@router.put("/{item_id}",tags=["custom"],responses={403:{"description":"Operation forbidden"}},)asyncdefupdate_item(item_id:str):ifitem_id!="plumbus":raiseHTTPException(status_code=403,detail="You can only update the item: plumbus")return{"item_id":item_id,"name":"The great Plumbus"}Consejo
Esta última path operation tendrá la combinación de tags:["items", "custom"].
Y también tendrá ambas responses en la documentación, una para404 y otra para403.
ElFastAPI principal¶
Ahora, veamos el módulo enapp/main.py.
Aquí es donde importas y usas la claseFastAPI.
Este será el archivo principal en tu aplicación que conecta todo.
Y como la mayor parte de tu lógica ahora vivirá en su propio módulo específico, el archivo principal será bastante simple.
ImportarFastAPI¶
Importas y creas una claseFastAPI como normalmente.
Y podemos incluso declarardependencias globales que se combinarán con las dependencias para cadaAPIRouter:
fromfastapiimportDepends,FastAPIfrom.dependenciesimportget_query_token,get_token_headerfrom.internalimportadminfrom.routersimportitems,usersapp=FastAPI(dependencies=[Depends(get_query_token)])app.include_router(users.router)app.include_router(items.router)app.include_router(admin.router,prefix="/admin",tags=["admin"],dependencies=[Depends(get_token_header)],responses={418:{"description":"I'm a teapot"}},)@app.get("/")asyncdefroot():return{"message":"Hello Bigger Applications!"}Importar elAPIRouter¶
Ahora importamos los otros submódulos que tienenAPIRouters:
fromfastapiimportDepends,FastAPIfrom.dependenciesimportget_query_token,get_token_headerfrom.internalimportadminfrom.routersimportitems,usersapp=FastAPI(dependencies=[Depends(get_query_token)])app.include_router(users.router)app.include_router(items.router)app.include_router(admin.router,prefix="/admin",tags=["admin"],dependencies=[Depends(get_token_header)],responses={418:{"description":"I'm a teapot"}},)@app.get("/")asyncdefroot():return{"message":"Hello Bigger Applications!"}Como los archivosapp/routers/users.py yapp/routers/items.py son submódulos que son parte del mismo paquete de Pythonapp, podemos usar un solo punto. para importarlos usando "imports relativos".
Cómo funciona la importación¶
La sección:
from.routersimportitems,userssignifica:
- Partiendo en el mismo paquete en el que este módulo (el archivo
app/main.py) habita (el directorioapp/)... - busca el subpaquete
routers(el directorio enapp/routers/)... - y de él, importa el submódulo
items(el archivo enapp/routers/items.py) yusers(el archivo enapp/routers/users.py)...
El móduloitems tendrá una variablerouter (items.router). Este es el mismo que creamos en el archivoapp/routers/items.py, es un objetoAPIRouter.
Y luego hacemos lo mismo para el módulousers.
También podríamos importarlos así:
fromapp.routersimportitems,usersInformación
La primera versión es un "import relativo":
from.routersimportitems,usersLa segunda versión es un "import absoluto":
fromapp.routersimportitems,usersPara aprender más sobre Paquetes y Módulos de Python, leela documentación oficial de Python sobre Módulos.
Evitar colisiones de nombres¶
Estamos importando el submóduloitems directamente, en lugar de importar solo su variablerouter.
Esto se debe a que también tenemos otra variable llamadarouter en el submódulousers.
Si hubiéramos importado uno después del otro, como:
from.routers.itemsimportrouterfrom.routers.usersimportrouterelrouter deusers sobrescribiría el deitems y no podríamos usarlos al mismo tiempo.
Así que, para poder usar ambos en el mismo archivo, importamos los submódulos directamente:
fromfastapiimportDepends,FastAPIfrom.dependenciesimportget_query_token,get_token_headerfrom.internalimportadminfrom.routersimportitems,usersapp=FastAPI(dependencies=[Depends(get_query_token)])app.include_router(users.router)app.include_router(items.router)app.include_router(admin.router,prefix="/admin",tags=["admin"],dependencies=[Depends(get_token_header)],responses={418:{"description":"I'm a teapot"}},)@app.get("/")asyncdefroot():return{"message":"Hello Bigger Applications!"}Incluir losAPIRouters parausers yitems¶
Ahora, incluyamos losrouters de los submódulosusers yitems:
fromfastapiimportDepends,FastAPIfrom.dependenciesimportget_query_token,get_token_headerfrom.internalimportadminfrom.routersimportitems,usersapp=FastAPI(dependencies=[Depends(get_query_token)])app.include_router(users.router)app.include_router(items.router)app.include_router(admin.router,prefix="/admin",tags=["admin"],dependencies=[Depends(get_token_header)],responses={418:{"description":"I'm a teapot"}},)@app.get("/")asyncdefroot():return{"message":"Hello Bigger Applications!"}Información
users.router contiene elAPIRouter dentro del archivoapp/routers/users.py.
Yitems.router contiene elAPIRouter dentro del archivoapp/routers/items.py.
Conapp.include_router() podemos agregar cadaAPIRouter a la aplicación principal deFastAPI.
Incluirá todas las rutas de ese router como parte de ella.
Detalles Técnicos
En realidad creará internamente unapath operation para cadapath operation que fue declarada en elAPIRouter.
Así, detrás de escena, funcionará como si todo fuera la misma única app.
Revisa
No tienes que preocuparte por el rendimiento al incluir routers.
Esto tomará microsegundos y solo sucederá al inicio.
Así que no afectará el rendimiento. ⚡
Incluir unAPIRouter con unprefix,tags,responses, ydependencies personalizados¶
Ahora, imaginemos que tu organización te dio el archivoapp/internal/admin.py.
Contiene unAPIRouter con algunaspath operations de administración que tu organización comparte entre varios proyectos.
Para este ejemplo será súper simple. Pero digamos que porque está compartido con otros proyectos en la organización, no podemos modificarlo y agregar unprefix,dependencies,tags, etc. directamente alAPIRouter:
fromfastapiimportAPIRouterrouter=APIRouter()@router.post("/")asyncdefupdate_admin():return{"message":"Admin getting schwifty"}Pero aún queremos configurar unprefix personalizado al incluir elAPIRouter para que todas suspath operations comiencen con/admin, queremos asegurarlo con lasdependencies que ya tenemos para este proyecto, y queremos incluirtags yresponses.
Podemos declarar todo eso sin tener que modificar elAPIRouter original pasando esos parámetros aapp.include_router():
fromfastapiimportDepends,FastAPIfrom.dependenciesimportget_query_token,get_token_headerfrom.internalimportadminfrom.routersimportitems,usersapp=FastAPI(dependencies=[Depends(get_query_token)])app.include_router(users.router)app.include_router(items.router)app.include_router(admin.router,prefix="/admin",tags=["admin"],dependencies=[Depends(get_token_header)],responses={418:{"description":"I'm a teapot"}},)@app.get("/")asyncdefroot():return{"message":"Hello Bigger Applications!"}De esa manera, elAPIRouter original permanecerá sin modificar, por lo que aún podemos compartir ese mismo archivoapp/internal/admin.py con otros proyectos en la organización.
El resultado es que, en nuestra app, cada una de laspath operations del móduloadmin tendrá:
- El prefix
/admin. - El tag
admin. - La dependencia
get_token_header. - La response
418. 🍵
Pero eso solo afectará a eseAPIRouter en nuestra app, no en ningún otro código que lo utilice.
Así, por ejemplo, otros proyectos podrían usar el mismoAPIRouter con un método de autenticación diferente.
Incluir unapath operation¶
También podemos agregarpath operations directamente a la app deFastAPI.
Aquí lo hacemos... solo para mostrar que podemos 🤷:
fromfastapiimportDepends,FastAPIfrom.dependenciesimportget_query_token,get_token_headerfrom.internalimportadminfrom.routersimportitems,usersapp=FastAPI(dependencies=[Depends(get_query_token)])app.include_router(users.router)app.include_router(items.router)app.include_router(admin.router,prefix="/admin",tags=["admin"],dependencies=[Depends(get_token_header)],responses={418:{"description":"I'm a teapot"}},)@app.get("/")asyncdefroot():return{"message":"Hello Bigger Applications!"}y funcionará correctamente, junto con todas las otraspath operations añadidas conapp.include_router().
Detalles Muy Técnicos
Nota: este es un detalle muy técnico que probablemente puedessimplemente omitir.
LosAPIRouters no están "montados", no están aislados del resto de la aplicación.
Esto se debe a que queremos incluir suspath operations en el esquema de OpenAPI y las interfaces de usuario.
Como no podemos simplemente aislarlos y "montarlos" independientemente del resto, laspath operations se "clonan" (se vuelven a crear), no se incluyen directamente.
Revisa la documentación automática de la API¶
Ahora, ejecuta tu app:
$fastapidevapp/main.py<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)Y abre la documentación enhttp://127.0.0.1:8000/docs.
Verás la documentación automática de la API, incluyendo los paths de todos los submódulos, usando los paths correctos (y prefijos) y los tags correctos:

Incluir el mismo router múltiples veces con diferentesprefix¶
También puedes usar.include_router() múltiples veces con elmismo router usando diferentes prefijos.
Esto podría ser útil, por ejemplo, para exponer la misma API bajo diferentes prefijos, por ejemplo,/api/v1 y/api/latest.
Este es un uso avanzado que quizás no necesites realmente, pero está allí en caso de que lo necesites.
Incluir unAPIRouter en otro¶
De la misma manera que puedes incluir unAPIRouter en una aplicaciónFastAPI, puedes incluir unAPIRouter en otroAPIRouter usando:
router.include_router(other_router)Asegúrate de hacerlo antes de incluirrouter en la app deFastAPI, para que laspath operations deother_router también se incluyan.







