Spaces:
Sleeping
Sleeping
update api
Browse files- .dockerignore +24 -0
- .gitignore +17 -0
- Dockerfile +27 -0
- README.md +96 -12
- app.py +134 -0
- requirements.txt +3 -0
.dockerignore
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Ignorar el entorno virtual de Python
|
| 2 |
+
venv/
|
| 3 |
+
.venv/
|
| 4 |
+
|
| 5 |
+
# Ignorar cachés de Python
|
| 6 |
+
__pycache__/
|
| 7 |
+
*.pyc
|
| 8 |
+
*.pyo
|
| 9 |
+
*.pyd
|
| 10 |
+
|
| 11 |
+
# Ignorar archivos específicos del editor o sistema operativo
|
| 12 |
+
.vscode/
|
| 13 |
+
.idea/
|
| 14 |
+
.DS_Store
|
| 15 |
+
|
| 16 |
+
# Ignorar archivos de entorno (¡muy importante para la seguridad!)
|
| 17 |
+
.env
|
| 18 |
+
|
| 19 |
+
# Ignorar el directorio de git
|
| 20 |
+
.git/
|
| 21 |
+
.gitignore
|
| 22 |
+
|
| 23 |
+
# Archivos de configuración de herramientas
|
| 24 |
+
.pytest_cache/
|
.gitignore
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Ignorar el entorno virtual de Python
|
| 2 |
+
venv/
|
| 3 |
+
.venv/
|
| 4 |
+
|
| 5 |
+
# Ignorar cachés de Python
|
| 6 |
+
__pycache__/
|
| 7 |
+
*.pyc
|
| 8 |
+
*.pyo
|
| 9 |
+
*.pyd
|
| 10 |
+
|
| 11 |
+
# Ignorar archivos específicos del editor o sistema operativo
|
| 12 |
+
.vscode/
|
| 13 |
+
.idea/
|
| 14 |
+
.DS_Store
|
| 15 |
+
|
| 16 |
+
# Ignorar archivos de entorno (¡muy importante para la seguridad!)
|
| 17 |
+
.env
|
Dockerfile
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Dockerfile
|
| 2 |
+
|
| 3 |
+
# 1. Usar una imagen base oficial de Python
|
| 4 |
+
# Usamos una versión "slim" para que la imagen sea más ligera
|
| 5 |
+
FROM python:3.9-slim
|
| 6 |
+
|
| 7 |
+
# 2. Establecer el directorio de trabajo dentro del contenedor
|
| 8 |
+
WORKDIR /app
|
| 9 |
+
|
| 10 |
+
# 3. Copiar el archivo de requerimientos al directorio de trabajo
|
| 11 |
+
COPY requirements.txt .
|
| 12 |
+
|
| 13 |
+
# 4. Instalar las dependencias de Python
|
| 14 |
+
# --no-cache-dir reduce el tamaño de la imagen
|
| 15 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 16 |
+
|
| 17 |
+
# 5. Copiar todo el código de la aplicación al directorio de trabajo
|
| 18 |
+
COPY . .
|
| 19 |
+
|
| 20 |
+
# 6. Exponer el puerto en el que se ejecutará la aplicación
|
| 21 |
+
# Hugging Face Spaces espera que la app se ejecute en el puerto 7860, pero con Docker se puede remapear.
|
| 22 |
+
# Usaremos el 8000 que es el estándar de uvicorn.
|
| 23 |
+
EXPOSE 8000
|
| 24 |
+
|
| 25 |
+
# 7. Comando para ejecutar la aplicación cuando se inicie el contenedor
|
| 26 |
+
# --host 0.0.0.0 es crucial para que sea accesible desde fuera del contenedor
|
| 27 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
CHANGED
|
@@ -1,12 +1,96 @@
|
|
| 1 |
-
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
-
sdk: docker
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Mi API Completa con FastAPI
|
| 3 |
+
emoji: 🚀
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: green
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 8000
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
# 🚀 Mi API Completa con FastAPI
|
| 11 |
+
|
| 12 |
+
Este es un Space de demostración para una API creada con FastAPI que incluye todos los métodos HTTP comunes.
|
| 13 |
+
|
| 14 |
+
La API se ejecuta dentro de un contenedor Docker y te permite gestionar una lista de ítems.
|
| 15 |
+
|
| 16 |
+
## Documentación Interactiva de la API
|
| 17 |
+
|
| 18 |
+
FastAPI genera automáticamente una documentación interactiva. Puedes acceder a ella agregando `/docs` a la URL de este Space.
|
| 19 |
+
|
| 20 |
+
- **Swagger UI**: [URL_DE_TU_SPACE/docs](URL_DE_TU_SPACE/docs)
|
| 21 |
+
- **ReDoc**: [URL_DE_TU_SPACE/redoc](URL_DE_TU_SPACE/redoc)
|
| 22 |
+
|
| 23 |
+
## Cómo usar los Endpoints
|
| 24 |
+
|
| 25 |
+
Puedes usar `curl` o cualquier cliente HTTP para interactuar con la API. Reemplaza `URL_DE_TU_SPACE` con la URL de tu aplicación.
|
| 26 |
+
|
| 27 |
+
### `GET /items` - Obtener todos los ítems
|
| 28 |
+
```bash
|
| 29 |
+
curl -X 'GET' 'URL_DE_TU_SPACE/items' -H 'accept: application/json'
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
### POST /items - Crear un nuevo ítem
|
| 33 |
+
```bash
|
| 34 |
+
|
| 35 |
+
curl -X 'POST' \
|
| 36 |
+
'URL_DE_TU_SPACE/items' \
|
| 37 |
+
-H 'accept: application/json' \
|
| 38 |
+
-H 'Content-Type: application/json' \
|
| 39 |
+
-d '{
|
| 40 |
+
"id": 3,
|
| 41 |
+
"name": "Monitor Ultrawide",
|
| 42 |
+
"description": "Un monitor de 34 pulgadas",
|
| 43 |
+
"price": 499.99
|
| 44 |
+
}'
|
| 45 |
+
```
|
| 46 |
+
### GET /items/{item_id} - Obtener un ítem por ID
|
| 47 |
+
```bash
|
| 48 |
+
|
| 49 |
+
curl -X 'GET' 'URL_DE_TU_SPACE/items/1' -H 'accept: application/json'
|
| 50 |
+
PUT /items/{item_id} - Actualizar un ítem completamente
|
| 51 |
+
Bash
|
| 52 |
+
|
| 53 |
+
curl -X 'PUT' \
|
| 54 |
+
'URL_DE_TU_SPACE/items/1' \
|
| 55 |
+
-H 'accept: application/json' \
|
| 56 |
+
-H 'Content-Type: application/json' \
|
| 57 |
+
-d '{
|
| 58 |
+
"id": 1,
|
| 59 |
+
"name": "Laptop Gamer",
|
| 60 |
+
"description": "Un potente portátil con RTX 4080",
|
| 61 |
+
"price": 2500.00
|
| 62 |
+
}'
|
| 63 |
+
```
|
| 64 |
+
### DELETE /items/{item_id} - Eliminar un ítem
|
| 65 |
+
```bash
|
| 66 |
+
|
| 67 |
+
curl -X 'DELETE' 'URL_DE_TU_SPACE/items/2' -H 'accept: application/json'
|
| 68 |
+
```
|
| 69 |
+
### Cómo Subirlo a Hugging Face Spaces
|
| 70 |
+
|
| 71 |
+
1. **Crea una cuenta** en [Hugging Face](https://huggingface.co/).
|
| 72 |
+
2. Ve a tu perfil y haz clic en **"New Space"**.
|
| 73 |
+
3. Dale un **nombre** a tu Space (por ejemplo, `mi-api-fastapi`).
|
| 74 |
+
4. Elige una **licencia**.
|
| 75 |
+
5. Selecciona **"Docker"** como el SDK del Space.
|
| 76 |
+
6. Haz clic en **"Create Space"**.
|
| 77 |
+
7. Sigue las instrucciones para subir tus archivos. La forma más sencilla es:
|
| 78 |
+
* Ir a la pestaña **"Files and versions"**.
|
| 79 |
+
* Hacer clic en **"Add file"** y luego **"Upload files"**.
|
| 80 |
+
* Arrastra y suelta los cuatro archivos (`main.py`, `requirements.txt`, `Dockerfile`, `README.md`) que creaste.
|
| 81 |
+
* Escribe un mensaje de commit (ej. "Versión inicial") y haz clic en **"Commit changes to main"**.
|
| 82 |
+
|
| 83 |
+
Hugging Face detectará tu `Dockerfile` y `README.md`, construirá la imagen y desplegará tu aplicación. ¡En unos minutos, tu API estará en línea y lista para usar!
|
| 84 |
+
### Probar en el local
|
| 85 |
+
python -m venv venv
|
| 86 |
+
.\venv\Scripts\Activate.ps1
|
| 87 |
+
pip install -r requirements.txt
|
| 88 |
+
uvicorn main:app --reload
|
| 89 |
+
http://127.0.0.1:8000/docs
|
| 90 |
+
|
| 91 |
+
### Docker
|
| 92 |
+
docker build -t my_fast_api .
|
| 93 |
+
docker run -d -p 8000:8000 --name mi-api-container my_fast_api
|
| 94 |
+
|
| 95 |
+
http://127.0.0.1:8000/docs
|
| 96 |
+
http://127.0.0.1:8000/items
|
app.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# main.py
|
| 2 |
+
from fastapi import FastAPI, HTTPException, status
|
| 3 |
+
from pydantic import BaseModel
|
| 4 |
+
from typing import Optional, List
|
| 5 |
+
|
| 6 |
+
# --- Modelos de Datos con Pydantic ---
|
| 7 |
+
# Pydantic se encarga de la validación y serialización de datos
|
| 8 |
+
|
| 9 |
+
class Item(BaseModel):
|
| 10 |
+
"""Modelo base para un ítem. Todos los campos son requeridos."""
|
| 11 |
+
id: int
|
| 12 |
+
name: str
|
| 13 |
+
description: Optional[str] = None
|
| 14 |
+
price: float
|
| 15 |
+
|
| 16 |
+
class UpdateItem(BaseModel):
|
| 17 |
+
"""Modelo para actualizar un ítem. Todos los campos son opcionales."""
|
| 18 |
+
name: Optional[str] = None
|
| 19 |
+
description: Optional[str] = None
|
| 20 |
+
price: Optional[float] = None
|
| 21 |
+
|
| 22 |
+
# --- Instancia de la Aplicación FastAPI ---
|
| 23 |
+
app = FastAPI(
|
| 24 |
+
title="API de Ejemplo Completa",
|
| 25 |
+
description="Una API de ejemplo con todos los métodos HTTP para demostrar FastAPI, Docker y Hugging Face Spaces.",
|
| 26 |
+
version="1.0.0",
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
# --- "Base de Datos" en Memoria ---
|
| 30 |
+
# Un diccionario simple para simular una base de datos.
|
| 31 |
+
db = {
|
| 32 |
+
1: Item(id=1, name="Laptop", description="Un potente portátil para desarrollo", price=1200.50),
|
| 33 |
+
2: Item(id=2, name="Teclado Mecánico", description="Un teclado con switches Cherry MX Blue", price=150.75),
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
# --- Endpoints de la API ---
|
| 37 |
+
|
| 38 |
+
@app.get("/", tags=["Root"])
|
| 39 |
+
def read_root():
|
| 40 |
+
"""Endpoint principal de bienvenida."""
|
| 41 |
+
return {"message": "¡Bienvenido a la API de ejemplo con FastAPI!"}
|
| 42 |
+
|
| 43 |
+
# --- Métodos para la colección de ítems ---
|
| 44 |
+
|
| 45 |
+
@app.get("/items", response_model=List[Item], tags=["Items"])
|
| 46 |
+
def get_all_items():
|
| 47 |
+
"""
|
| 48 |
+
GET: Obtiene una lista de todos los ítems en la base de datos.
|
| 49 |
+
"""
|
| 50 |
+
return list(db.values())
|
| 51 |
+
|
| 52 |
+
@app.post("/items", response_model=Item, status_code=status.HTTP_201_CREATED, tags=["Items"])
|
| 53 |
+
def create_item(item: Item):
|
| 54 |
+
"""
|
| 55 |
+
POST: Crea un nuevo ítem.
|
| 56 |
+
El ID del ítem en el cuerpo de la petición debe ser único.
|
| 57 |
+
"""
|
| 58 |
+
if item.id in db:
|
| 59 |
+
raise HTTPException(
|
| 60 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
| 61 |
+
detail=f"El ítem con ID {item.id} ya existe."
|
| 62 |
+
)
|
| 63 |
+
db[item.id] = item
|
| 64 |
+
return item
|
| 65 |
+
|
| 66 |
+
# --- Métodos para un ítem específico ---
|
| 67 |
+
|
| 68 |
+
@app.get("/items/{item_id}", response_model=Item, tags=["Items"])
|
| 69 |
+
def get_item_by_id(item_id: int):
|
| 70 |
+
"""
|
| 71 |
+
GET: Obtiene un ítem específico por su ID.
|
| 72 |
+
"""
|
| 73 |
+
if item_id not in db:
|
| 74 |
+
raise HTTPException(
|
| 75 |
+
status_code=status.HTTP_404_NOT_FOUND,
|
| 76 |
+
detail=f"El ítem con ID {item_id} no fue encontrado."
|
| 77 |
+
)
|
| 78 |
+
return db[item_id]
|
| 79 |
+
|
| 80 |
+
@app.put("/items/{item_id}", response_model=Item, tags=["Items"])
|
| 81 |
+
def update_item_completely(item_id: int, item: Item):
|
| 82 |
+
"""
|
| 83 |
+
PUT: Actualiza un ítem completamente.
|
| 84 |
+
Reemplaza toda la información del ítem existente con la nueva.
|
| 85 |
+
"""
|
| 86 |
+
if item_id not in db:
|
| 87 |
+
raise HTTPException(
|
| 88 |
+
status_code=status.HTTP_404_NOT_FOUND,
|
| 89 |
+
detail=f"El ítem con ID {item_id} no fue encontrado."
|
| 90 |
+
)
|
| 91 |
+
# El ID no debe cambiar en una operación PUT
|
| 92 |
+
if item_id != item.id:
|
| 93 |
+
raise HTTPException(
|
| 94 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
| 95 |
+
detail="El ID del ítem en la URL no coincide con el ID en el cuerpo de la petición."
|
| 96 |
+
)
|
| 97 |
+
db[item_id] = item
|
| 98 |
+
return db[item_id]
|
| 99 |
+
|
| 100 |
+
@app.patch("/items/{item_id}", response_model=Item, tags=["Items"])
|
| 101 |
+
def update_item_partially(item_id: int, item_update: UpdateItem):
|
| 102 |
+
"""
|
| 103 |
+
PATCH: Actualiza un ítem parcialmente.
|
| 104 |
+
Solo actualiza los campos proporcionados en el cuerpo de la petición.
|
| 105 |
+
"""
|
| 106 |
+
if item_id not in db:
|
| 107 |
+
raise HTTPException(
|
| 108 |
+
status_code=status.HTTP_404_NOT_FOUND,
|
| 109 |
+
detail=f"El ítem con ID {item_id} no fue encontrado."
|
| 110 |
+
)
|
| 111 |
+
|
| 112 |
+
stored_item_data = db[item_id].dict()
|
| 113 |
+
update_data = item_update.dict(exclude_unset=True) # Obtiene solo los campos que se enviaron
|
| 114 |
+
|
| 115 |
+
updated_item_data = stored_item_data.copy()
|
| 116 |
+
updated_item_data.update(update_data)
|
| 117 |
+
|
| 118 |
+
db[item_id] = Item(**updated_item_data)
|
| 119 |
+
return db[item_id]
|
| 120 |
+
|
| 121 |
+
@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT, tags=["Items"])
|
| 122 |
+
def delete_item(item_id: int):
|
| 123 |
+
"""
|
| 124 |
+
DELETE: Elimina un ítem por su ID.
|
| 125 |
+
Devuelve un código 204 sin contenido si tiene éxito.
|
| 126 |
+
"""
|
| 127 |
+
if item_id not in db:
|
| 128 |
+
raise HTTPException(
|
| 129 |
+
status_code=status.HTTP_404_NOT_FOUND,
|
| 130 |
+
detail=f"El ítem con ID {item_id} no fue encontrado."
|
| 131 |
+
)
|
| 132 |
+
del db[item_id]
|
| 133 |
+
# No se devuelve contenido, solo el código de estado 204
|
| 134 |
+
return
|
requirements.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# requirements.txt
|
| 2 |
+
fastapi
|
| 3 |
+
uvicorn[standard]
|