diff --git a/part-01-hello-world/README.md b/part-01-hello-world/README.md index 61e6803..fb8b776 100644 --- a/part-01-hello-world/README.md +++ b/part-01-hello-world/README.md @@ -1,11 +1,248 @@ -## Part 1 Local Setup +# FastAPI -1. `pip install poetry` (or safer, follow the instructions: https://python-poetry.org/docs/#installation) -2. Install dependencies `cd` into the directory where the `pyproject.toml` is located then `poetry install` -3. [UNIX]: Run the FastAPI server via poetry with the bash script: `poetry run ./run.sh` -4. [WINDOWS]: Run the FastAPI server via poetry with the Python command: `poetry run python app/main.py` -5. Open http://localhost:8001/ +## FastAPI course that I deliver to my colleagues. -To stop the server, press CTRL+C +- pip install poetry (or safer, follow the instructions: https://python-poetry.org/docs/#installation) +- pip install requeriments.txt +- Install dependencies cd into the directory where the pyproject.toml is located then poetry install +- [UNIX]: Run the FastAPI server via poetry with the bash script: poetry run ./run.sh +- [WINDOWS]: Run the FastAPI server via poetry with the Python command: poetry run python app/main.py +- Open http://localhost:8001/ +- To stop the server, press CTRL+C -If you get stuck, checkout the [troubleshooting readme](../troubleshooting/README.md) \ No newline at end of file +--- + +## Entornos + +Para crear un entorno virtual en Python en Windows, puedes seguir los pasos a continuación. Usar entornos virtuales es una buena práctica, ya que te permite aislar las dependencias de diferentes proyectos. + +### Pasos para crear un entorno virtual en Windows: + +#### 1. **Instalar Python (si aún no lo tienes)** + +- Primero, asegúrate de tener Python instalado en tu sistema. Puedes verificarlo abriendo el símbolo del sistema (CMD) y ejecutando: + +`python --version` + +Si Python no está instalado, descárgalo de la página oficial: [https://www.python.org/downloads/](https://www.python.org/downloads/) y asegúrate de seleccionar la opción **"Add Python to PATH"** durante la instalación. + +#### 2. **Abrir el Símbolo del Sistema (CMD)** + +- Presiona `Windows + R`, escribe `cmd` y presiona `Enter`. + +#### 3. **Instalar `virtualenv` (opcional)** + +- Python 3.3 y versiones superiores ya incluyen el módulo `venv` para crear entornos virtuales, por lo que no es necesario instalar `virtualenv` a menos que prefieras usarlo. +- Si prefieres usar `virtualenv`, instálalo ejecutando: + + ``` + pip install virtualenv + ``` + +#### 4. **Crear un entorno virtual** + +- Navega a la carpeta de tu proyecto o a la carpeta donde deseas crear el entorno virtual. Usa el comando `cd` para cambiar de directorio, por ejemplo: + + ``` + cd C:\ruta\de\mi\proyecto + ``` + +. Crea el entorno virtual usando el siguiente comando: + +- Crea el entorno virtual usando el siguiente comando: + + - Si usas el módulo `venv` incluido en Python 3: + - ```bash + python -m venv nombre_del_entorno + ``` + + - Si prefieres virtualenv (después de instalarlo): + - ````bash + virtualenv nombre_del_entorno + ``` + ```` + +Esto creará una carpeta llamada nombre_del_entorno en el directorio actual. Dentro de esta carpeta estarán los archivos necesarios para ejecutar el entorno virtual. + +#### 5. **Activar el entorno virtual** + +- Una vez creado el entorno virtual, debes activarlo. Ejecuta el siguiente comando: + + ```bash + nombre_del_entorno\Scripts\activate + ``` + +Si el entorno se activa correctamente, verás el nombre del entorno virtual al principio del prompt del símbolo del sistema. Por ejemplo: + +```bash +(nombre_del_entorno) C:\ruta\de\mi\proyecto> + +``` + +#### 6. **Instalar paquetes en el entorno** + +- Ahora que el entorno está activo, puedes instalar paquetes usando pip y estarán aislados en este entorno. Por ejemplo: + +```bash +pip install nombre_paquete + +``` + +aca hacemos pip install requeriments.txt + +#### 7. **Desactivar el entorno virtual** + +- Cuando termines de trabajar en el entorno virtual, puedes desactivarlo ejecutando: + +```bash +deactivate + +``` + +#### 8. **Ejemplo completo:** + +8.1. Crear el entorno virtual: + +```bash +python -m venv mi_entorno +``` + +8.2. Activar el entorno: + +```bash +mi_entorno\Scripts\activate +``` + +8.3. Instalar paquetes (por ejemplo, FastAPI): + +```bash +pip install fastap +``` + +8.4. Desactivar el entorno: + +```bash +deactivate +``` + +## Introducción a los Tipos de Python + +Estos type hints son una nueva sintaxis, desde Python 3.6+, que permite declarar el tipo de una variable. + +Usando las declaraciones de tipos para tus variables, los editores y otras herramientas pueden proveerte un soporte mejor. + +Este es solo un tutorial corto sobre los Python type hints. Solo cubre lo mínimo necesario para usarlos con FastAPI... realmente es muy poco lo que necesitas. + +Todo FastAPI está basado en estos type hints, lo que le da muchas ventajas y beneficios. + +Pero, así nunca uses FastAPI te beneficiarás de aprender un poco sobre los type hints. + +[Lectura Obligatoria](https://fastapi.tiangolo.com/es/python-types/) + +--- + +## algo mas de Python + +### 1. **Tupla (`tuple`)** + +Una **tupla** es una secuencia ordenada de elementos inmutables. No puedes modificar, añadir o eliminar elementos de una tupla una vez que ha sido creada. + +- **Características** : +- Inmutable: No se puede modificar una vez creada. +- Puede contener elementos duplicados. +- Puede almacenar cualquier tipo de datos. + +```python +#Definición de una tupla +mi_tupla = (1, 2, 3, "a", "b") +print(mi_tupla) # Salida: (1, 2, 3, 'a', 'b') + +#Accediendo a un elemento +print(mi_tupla[0]) # Salida: 1 + +#Intentar modificar una tupla lanzará un error +mi_tupla[0] = 100 # Esto generaría un error +``` + +### 2. **Conjunto (`set`)** + +Un **conjunto** es una colección desordenada de elementos únicos. No permite duplicados, y sus elementos no están indexados ni ordenados. + +- **Características** : +- Desordenado: No tiene un orden específico. +- No se permite duplicados. +- Mutable: Puedes agregar y eliminar elementos. + +```python +# Definición de un set +mi_set = {1, 2, 3, 4, 5, 3, 2} +print(mi_set) # Salida: {1, 2, 3, 4, 5} (Los duplicados se eliminan) + +# Agregar un elemento +mi_set.add(6) +print(mi_set) # Salida: {1, 2, 3, 4, 5, 6} + +# Eliminar un elemento +mi_set.remove(3) +print(mi_set) # Salida: {1, 2, 4, 5, 6} + +``` + +### 3. **Lista (`list`)** + +Una **lista** es una colección ordenada y mutable de elementos. A diferencia de las tuplas, las listas pueden modificarse: puedes agregar, eliminar o cambiar sus elementos. + +- **Características** : +- Ordenada: Mantiene el orden de inserción de los elementos. +- Mutable: Los elementos pueden ser modificados. +- Puede contener elementos duplicados. + +```python +# Definición de una lista +mi_lista = [1, 2, 3, "a", "b", "a"] +print(mi_lista) # Salida: [1, 2, 3, 'a', 'b', 'a'] + +# Agregar un elemento +mi_lista.append(4) +print(mi_lista) # Salida: [1, 2, 3, 'a', 'b', 'a', 4] + +# Eliminar un elemento +mi_lista.remove("a") +print(mi_lista) # Salida: [1, 2, 3, 'b', 'a', 4] + +# Modificar un elemento +mi_lista[0] = 100 +print(mi_lista) # Salida: [100, 2, 3, 'b', 'a', 4] + +``` + +### 4. **Diccionario (`dict`)** + +Un **diccionario** es una colección desordenada de pares clave-valor. Cada clave debe ser única, y se usa para acceder a su valor asociado. + +- **Características** : +- Desordenado (aunque en versiones recientes de Python mantiene el orden de inserción). +- Mutable: Se pueden agregar, eliminar y modificar los pares clave-valor. +- Las claves deben ser únicas, pero los valores pueden repetirse. + +```python +# Definición de un diccionario +mi_dict = {"nombre": "Cristian", "edad": 30, "ciudad": "Buenos Aires"} +print(mi_dict) # Salida: {'nombre': 'Cristian', 'edad': 30, 'ciudad': 'Buenos Aires'} + +# Acceder a un valor por su clave +print(mi_dict["nombre"]) # Salida: Cristian + +# Agregar un nuevo par clave-valor +mi_dict["profesion"] = "Programador" +print(mi_dict) # Salida: {'nombre': 'Cristian', 'edad': 30, 'ciudad': 'Buenos Aires', 'profesion': 'Programador'} + +# Modificar un valor +mi_dict["edad"] = 31 +print(mi_dict) # Salida: {'nombre': 'Cristian', 'edad': 31, 'ciudad': 'Buenos Aires', 'profesion': 'Programador'} + +# Eliminar un par clave-valor +del mi_dict["ciudad"] +print(mi_dict) # Salida: {'nombre': 'Cristian', 'edad': 31, 'profesion': 'Programador'} + +``` diff --git a/part-01-hello-world/app/main.py b/part-01-hello-world/app/main.py index 231e708..dcc624b 100644 --- a/part-01-hello-world/app/main.py +++ b/part-01-hello-world/app/main.py @@ -1,4 +1,7 @@ from fastapi import FastAPI, APIRouter +from datetime import date +from datetime import datetime +import uuid app = FastAPI(title="Recipe API", openapi_url="/openapi.json") @@ -11,7 +14,91 @@ def root() -> dict: """ Root GET """ - return {"msg": "Hello, World!"} + return {"msg": "Hello, Policies"} + + +@api_router.get("/health", status_code=200) +def health() -> dict: + """ + Health Check + """ + return {"status": "ok"} + +@api_router.get("/quiensos", status_code=200) +def health() -> dict: + """ + Quien sos Check + """ + return {"mensaje": "I'm a API an this is one endpoint"} + +@api_router.get("/quiensos/{name}", status_code=200) +async def health(name: str) -> dict: + """ + interactuando con un parametro Check + """ + return {"mensaje": "I'm a FastAPI y vos " + name + " esta enviando un request al que estoy respondiendo"} + + +@api_router.get("/hoy", status_code=200) +async def get_cowsay() -> dict: + """ + Fecha Check + """ + #Día actual + today = date.today() + + #Fecha actual + now = datetime.now() + + return {"status": "ok", "today": today, "now": now} + +@api_router.get("/fibo/{n}", status_code=200) +async def get_fibo(n: int) -> dict: + """ + Devuelve un diccionario con la serie de Fibonacci hasta el n-ésimo término. + + :param n: El número de términos de la serie que se desea obtener. + :return: Diccionario con los términos de la serie Fibonacci. + """ + if n < 1: + return {"error": "El número debe ser mayor o igual a 1"} + + fib_series = {0: 0, 1: 1} + for i in range(2, n): + fib_series[i] = fib_series[i - 1] + fib_series[i - 2] + + return {i: fib_series[i] for i in range(n)} + + +#uuid +def generate_uuid_from_number(number: int) -> str: + """ + Genera un UUID basado en un número dado. + + :param number: Un número entero. + :return: Un UUID generado a partir del número. + """ + # Convertir el número a una cadena + number_str = str(number) + + # Generar un UUID a partir del espacio de nombres y la cadena del número + generated_uuid = uuid.uuid5(uuid.NAMESPACE_DNS, number_str) + + return str(generated_uuid) + + +@app.get("/uuid/{number}") +async def get_uuid_from_number(number: int) -> dict: + """ + Endpoint que recibe un número y devuelve un UUID generado a partir de ese número. + + :param number: Un número entero. + :return: Un diccionario con el UUID generado. + """ + generated_uuid = generate_uuid_from_number(number) + return {"uuid": generated_uuid, "valid_period": "24 hours"} + + app.include_router(api_router) diff --git a/part-02-path-parameters/app/main.py b/part-02-path-parameters/app/main.py index 293a2f8..d6b148b 100644 --- a/part-02-path-parameters/app/main.py +++ b/part-02-path-parameters/app/main.py @@ -1,5 +1,6 @@ from fastapi import FastAPI, APIRouter +from typing import Optional RECIPES = [ { @@ -20,6 +21,25 @@ "source": "Serious Eats", "url": "http://www.seriouseats.com/recipes/2011/02/cauliflower-and-tofu-curry-recipe.html", }, + { + "id": 4, + "label": "Masas para sopaipillas", + "source": "Recetas Gratis", + "url": "https://www.recetasgratis.net/receta-de-masas-para-sopaipillas-77646.html", + }, + { + "id": 5, + "label": "Causa limeña", + "source": "Recetas Gratis", + "url": "https://www.recetasgratis.net/receta-de-causa-limena-31268.html", + }, + { + "id": 6, + "label": "Torta de manzana sin harina", + "source": "Recetas Gratis", + "url": "https://www.recetasgratis.net/receta-de-torta-de-manzana-sin-harina-77668.html", + }, + ] @@ -45,8 +65,33 @@ def fetch_recipe(*, recipe_id: int) -> dict: """ result = [recipe for recipe in RECIPES if recipe["id"] == recipe_id] - if result: + # if result: + # return result[0] + try: return result[0] + except IndexError: + return {"status": 404, "error": "Recipe not found"} + + + +# New addition, query parameter +# https://fastapi.tiangolo.com/tutorial/query-params/ +@api_router.get("/search/", status_code=200) +def search_recipes( + keyword: Optional[str] = None, max_results: Optional[int] = 10 +) -> dict: + """ + Search for recipes based on label keyword + """ + if not keyword: + # we use Python list slicing to limit results + # based on the max_results query parameter + return {"results": RECIPES[:max_results]} + + results = filter(lambda recipe: keyword.lower() in recipe["label"].lower(), RECIPES) + return {"results": list(results)[:max_results]} + + app.include_router(api_router) diff --git a/part-04-pydantic-schemas/app/main.py b/part-04-pydantic-schemas/app/main.py index a76a0ff..bf17c83 100644 --- a/part-04-pydantic-schemas/app/main.py +++ b/part-04-pydantic-schemas/app/main.py @@ -1,4 +1,4 @@ -from fastapi import FastAPI, APIRouter, Query +from fastapi import FastAPI, APIRouter, Query, HTTPException from typing import Optional @@ -28,8 +28,12 @@ def fetch_recipe(*, recipe_id: int) -> dict: """ result = [recipe for recipe in RECIPES if recipe["id"] == recipe_id] - if result: - return result[0] + # Si no se encuentra, lanzamos una excepción con código 404 + # if not result: + # raise HTTPException(status_code=404, detail=f"Recipe with ID {recipe_id} not found") + + # Si se encuentra, devolvemos la receta (el primer elemento de la lista) + return result[0] # Updated using the FastAPI parameter validation `Query` class @@ -59,7 +63,13 @@ def search_recipes( results = filter(lambda recipe: keyword.lower() in recipe["label"].lower(), RECIPES) return {"results": list(results)[:max_results]} + # results = list(filter(lambda recipe: keyword.lower() in recipe["label"].lower(), RECIPES)) + # if not results: + # raise HTTPException(status_code=404, detail=f"Recipe with LABEL {keyword} not found") + + #return {"results": results[:max_results]} + # New addition, using Pydantic model `RecipeCreate` to define # the POST request body diff --git a/part-04-pydantic-schemas/app/recipe_data.py b/part-04-pydantic-schemas/app/recipe_data.py index d5434fe..7ee499a 100644 --- a/part-04-pydantic-schemas/app/recipe_data.py +++ b/part-04-pydantic-schemas/app/recipe_data.py @@ -17,4 +17,22 @@ "source": "Serious Eats", "url": "http://www.seriouseats.com/recipes/2011/02/cauliflower-and-tofu-curry-recipe.html", }, + { + "id": 4, + "label": "Masas para sopaipillas", + "source": "Recetas Gratis", + "url": "https://www.recetasgratis.net/receta-de-masas-para-sopaipillas-77646.html", + }, + { + "id": 5, + "label": "Causa limeña", + "source": "Recetas Gratis", + "url": "https://www.recetasgratis.net/receta-de-causa-limena-31268.html", + }, + { + "id": 6, + "label": "Torta de manzana sin harina", + "source": "Recetas Gratis", + "url": "https://www.recetasgratis.net/receta-de-torta-de-manzana-sin-harina-77668.html", + }, ] diff --git a/part-04-pydantic-schemas/app/schemas.py b/part-04-pydantic-schemas/app/schemas.py index e1bb430..59aba57 100644 --- a/part-04-pydantic-schemas/app/schemas.py +++ b/part-04-pydantic-schemas/app/schemas.py @@ -18,4 +18,4 @@ class RecipeCreate(BaseModel): label: str source: str url: HttpUrl - submitter_id: int + #submitter_id: int diff --git a/part-05-basic-error-handling/app/templates/index.html b/part-05-basic-error-handling/app/templates/index.html new file mode 100644 index 0000000..d0a195c --- /dev/null +++ b/part-05-basic-error-handling/app/templates/index.html @@ -0,0 +1,50 @@ + + +
+ + + ++ Latest recipes...
++ + + {{recipe.source}} +
+