Aún no tienes acceso a esta clase

Crea una cuenta y continúa viendo este curso

Tipos de datos especiales

17/20
Recursos

Aportes 48

Preguntas 5

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad? Crea una cuenta o inicia sesión.

Reto

Decidí hacer un endpoint que simula la compra de un producto en una tienda online. Además incluí mi propio tipo de dato “exótico”.

Código

"""
Program: Reto 1; uso de tipos de datos exóticos.

Description: Simulación de endpoint encargado de registrar una compra en una tienda online.

Author: Jose Noriega <[email protected]>

Last Update: 2021-10-26 

"""

import re
from datetime import date
from typing import Dict
from typing import Any

# Pydantic
from pydantic import BaseModel
from pydantic import Field
from pydantic import EmailStr
from pydantic import PaymentCardNumber
from pydantic.validators import str_validator
from pydantic.types import PaymentCardBrand

# Fast API
from fastapi import FastAPI
from fastapi import Body


app = FastAPI()

PHONE_REGEXP = re.compile(r'^\+?[0-9]{1,3}?[0-9]{6,14}$')

# Custom Types


class PhoneNumber(str):
    """Phone number type"""

    @classmethod
    def __get_validators__(cls) -> Dict[str, Any]:
        yield str_validator
        yield cls.validate

    @classmethod
    def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None:
        field_schema.update(
            pattern=r'^\+?[0-9]+$',
            example=['+541112345678'],
            format='phone-number',
        )

    @classmethod
    def validate(cls, value: str) -> str:
        if not isinstance(value, str):
            raise TypeError('Phone number must be a string')

        match = PHONE_REGEXP.search(value)

        if not match:
            raise ValueError('Phone number must be a valid phone number')

        return value

    def __repr__(self) -> str:
        return f'PhoneNumber({super().__repr__()})'


# Models


class Person(BaseModel):
    name: str = Field(...,
                      min_length=2,
                      max_length=50,
                      title='Name',
                      description='The name of the person that will receive the package.',
                      example='John Doe')

    email: EmailStr = Field(...,
                            title='Email',
                            description='The email of the person that will receive the package.')

    phone: PhoneNumber = Field(...,
                               title='Phone',
                               description='The phone number of the person that will receive the package.')


class Product(BaseModel):
    """Product model"""

    name: str = Field(...,
                      min_length=2,
                      max_length=50,
                      title='Name',
                      description='The name of the product.',
                      example='Laptop')


class PaymentMethod(BaseModel):
    """Payment method model"""

    card_number: PaymentCardNumber = Field(...,
                                      title='Number',
                                      description='The number of the payment card.',
                                      example='1234567890123456')

    expiration_month: int = Field(...,
                                  title='Expiration month',
                                  description='The expiration month of the payment card.',
                                  ge=1,
                                  le=12,
                                  example=12)

    expiration_year: int = Field(...,
                                 title='Expiration year',
                                 description='The expiration year of the payment card.')

    @property
    def brand(self) -> PaymentCardBrand:
        """Returns the brand of the payment card"""
        return self.card_number.brand

    @property
    def expired(self) -> bool:
        """Returns if the payment card is expired"""

        today = date.today()
        expiration_date = date(year=self.expiration_year,
                               month=self.expiration_month,
                               day=1)

        return today > expiration_date


class Address(BaseModel):
    """Address model"""

    street: str = Field(...,
                        min_length=2,
                        max_length=50,
                        title='Street',
                        description='The street of the address.')

    city: str = Field(...,
                      min_length=2,
                      max_length=50,
                      title='City',
                      description='The city of the address.')

    country: str = Field(...,
                         min_length=2,
                         max_length=50,
                         title='Country',
                         description='The country of the address.')

# Endpoints

@app.post('/order')
def add_order(
    person: Person = Body(...,),
    product: Product = Body(...,),
    address: Address = Body(...,),
    payment_method: PaymentMethod = Body(...,),
):
    """Registers a new order"""

    return {
        'person': person,
        'product': product,
        'address': address,
        'payment_method': {
            'brand': payment_method.brand,
            'last4': payment_method.card_number.last4,
            'mask': payment_method.card_number.masked,
            'expired': payment_method.expired,
        }
    }

Request Body

{
    "person": {
        "name": "John Doe",
        "email": "[email protected]",
        "phone": "+52638107905"
    },
    "address": {
        "street": "Street",
        "city": "City",
        "country": "Country"
    },
    "product": {
        "name": "Macbook Pro"
    },
    "payment_method": {
        "card_number":"4242424242424242",
        "expiration_month": 12,
        "expiration_year": 2050
    }
 
}

Response Body

{
    "person": {
        "name": "John Doe",
        "email": "[email protected]",
        "phone": "+52638107905"
    },
    "product": {
        "name": "Macbook Pro"
    },
    "address": {
        "street": "Street",
        "city": "City",
        "country": "Country"
    },
    "payment_method": {
        "brand": "Visa",
        "last4": "4242",
        "mask": "424242******4242",
        "expired": false
    }
}

Si nos les funciona el EmailStr tienen que instalarlo con:

pip install pydantic[email]

Creo que deberían crear otra sección además de aportes y preguntas, donde se tuvieran que poner las soluciones a los retos y que no se vieran los de los demás hasta que pongamos la nuestra. Y así evitar ver sin querer la solución en los aportes y que pierda la gracia

RETO # 1

class Location(BaseModel):
  city: str = Field(
    ...,
    min_length = 0
  );
  lat: Optional[float] = Field(default = None); # latitud
  lon: Optional[float] = Field(default = None); # longitud
  country: Optional[str] = Field(default = None);

RETO # 2

class Person(BaseModel): # herencia de clases el basemodel
  first_name: str = Field(
    ...,
    min_length = 1,
    max_length = 50
  );
  last_name: str = Field(
    ...,
    min_length = 1,
    max_length = 50
  );
  age: int = Field(
    ...,
    gt = 0,
    le = 120
  );
  email: EmailStr = Field(...);
  addressIp: Optional[IPvAnyAddress] = Field(...);
  website_url: Optional[HttpUrl] = Field(...);
  hair_color: Optional[HairColor] = Field(default = None);
  is_married: Optional[bool] = Field(default = False);
  abilities: Optional[Dict[str, Any]] = Field(default = None);

Instalación de plugin para validación email.

Si usarán EmailStr tendrán que instalar una extensión de pydantic, lo cual se hace con el siguiente comando:

Comando

(venv) $ pip install pydantic[email] 

Error en terminal de MacOS

Si están en MacOS y usan la terminal por defecto, es posible que les salga el error zsh: no matches found: pydantic[email] como a mi.

Para solucionarlo solo tienen que poner el nombre de la extensión entre comillas… de la siguiente forma:

(venv) $ pip install "pydantic[email]"

Todos estos tipos de datos corresponden a Pydantic, se pueden importar al igual que Field.

Tipos de datos clásicos:

  • str → Cadena de texto
  • int → Número entero
  • float → Número flotante (decimal)
  • bool → Booleano

Tipos de datos exóticos:

  • Enum → Enumerar caracteres
  • HttpUrl → Revisa si una URL es valida (***https://myapp.com***, *www.google.com*)
  • FilePath → Valida si la ruta que envía el cliente es un archivo (*c:/windows/system32/432.dll*)
  • DirectoryPath → Valida si la ruta que envía el cliente es un directorio (***/mnt/c/someFolder***)
  • EmailStr → Valida si el cliente ingresa un email (*[email protected]*)
  • PaymentCardNumber → Valida si el cliente ingresa un número de tarjeta
  • IPvAnyAdress → Valida si el cliente ingresa una dirección IP
  • NegativeFloat → Valida si el cliente ingresa un número negativo de tipo flotante
  • PositiveFloat → Valida si el cliente ingresa un número positivo de tipo flotante
  • NegativeInt → Valida si el cliente ingresa un número entero negativo
  • PositiveInt → Valida si el cliente ingresa un número entero positivo

Para más tipos de datos se puede revisar la documentación de Pydantic.

La nueva version de FastAPI o mejor dicho de pydantic requiere email-validator para poder usar EmailStr

pip install email-validator

reto cumplido
A la clase person le agregué el email, su página web, y el color favorito para su tema.

class Person(BaseModel):
    first_name: str = Field(...)
    last_name: str = Field(...)
    age: int = Field(...)
    hair_color: Optional[str] = Field(None)
    is_married: Optional[bool] = Field(None)
    email: EmailStr
    website: HttpUrl
    color: Color


class Location(BaseModel):
    city: str = Field(..., max_length=120)
    state: str = Field(..., max_length=120)
    country: str = Field(..., max_length=120)

El profe tiene un efecto de glitch en su miniatura a propósito?

Agregue un enum de países para el reto y el control de colores

from pydantic.color import Color
...
class Country(Enum):
    argentina = "Argentina"
    chile = "Chile"
    uruguay = "Uruguay"
    paraguay = "Paraguay"
    colombia = "Colombia"
    venezuela = "Venezuela"
...
class Person(BaseModel):
    first_name: str = Field(
        ...,
        min_length=1,
        max_length=50
    )
    last_name: str = Field(
        ...,
        min_length=1,
        max_length=50
    )
    age: int = Field(
        ...,
        gt=0,
        le=115
    )
    hair_color: Optional[HairColor] = Field(default=None)
    is_married: Optional[bool] = Field(default=None)
    profile_background: Color = Field(...)


class Location(BaseModel):
    city: str = Field(
        ...,
        min_length=1,
        max_length=50
    )
    state: str = Field(
        ...,
        min_length=1,
        max_length=50
    )
    country: Country = Field(...)


Agregue los atributos de email, número de tarjeta y peso

from pydantic import EmailStr, PaymentCardNumber, PositiveFloat

weight: Optional[PositiveFloat] = Field(default=None)
	email: EmailStr = Field(
		...,
		title="Person Email")
	card: PaymentCardNumber = Field(
		...,
		title="Payment Card")

Aqui mi aporte usando HttpUrl y EmailStr

from pydantic import BaseModel,HttpUrl,EmailStr
from pydantic import Field

app = FastAPI()

# Models:
class HairColor(Enum):
    white = "white"
    brown = "brown"
    black = "black"
    blonde = "blonde"
    red = "red"

class CountryCenterAmerica(Enum):
    guatemala = "Guatemala"
    salvador = "El Salvador"
    honduras =  "Honduras"
    nicaragua = "Nicaragua"
    costa_rica = "Costa Rica"

class Location(BaseModel):
    city: str =  Field(
        ...,
        min_length = 1,
        max_length = 50
    )
    state: str = Field(
        ...,
        min_length = 1,
        max_length = 50
    )
    country: CountryCenterAmerica


class Person(BaseModel):
    first_name: str = Field(
        ..., 
        min_length = 1,
        max_length = 50
        )
    last_name: str = Field(
        ..., 
        min_length = 1,
        max_length = 50
        )
    age: int = Field(
        ...,
        gt=0,
        le=115
    )
    email: EmailStr
    website: HttpUrl
    hair_color: Optional[HairColor] = Field(defult=None)
    is_married: Optional[bool] = Field(defult=None)

Antes tuve que hacer esto pip install email-validator, en este caso solo agregue el email en la class person

RETO: Ejemplo de como validaremos el Paypal que Person tendra guardado, para ello utilizaremos un nuevo package de pydantic llamado email-validator, en Linux se instala con pip install pydantic[emai], recordemos, ademas, que esto debemos instalarlo en entorno virtual. Alli guardaremos el email que la Persona ingrese al request body.
Crearemos la Clase EmailPaypal que tendrá como parámetro a la clase EmailStr de pydantic. En esta clase crearemos la variable Paypal que tendrá como valor simplemente un str cualquiera como “email”
En el Model Person, crearemos el atributo paypal, que será Optional y que será de tipo EmailPaypal y será un Field con default None

 #Models

class EmailPaypal(EmailStr):
    Paypal = "email"

#Los otros models (...)

#Person Model
class Person(BaseModel):
    first_name: str = Field(
        ...,
        min_length=1,
        max_length=50
    )
    last_name: str = Field(
        ...,
        min_length=1,
        max_length=50
    )
    age: int = Field(
        ...,
        gt=0,
        Le=115
    )
    hair_color: Optional[HairColor] = Field(default=None)
    is_married: Optional[bool] = Field(default=None)
    paypal: Optional[EmailPaypal] = Field(default=None)

Ejecucion (Insomnia):

Reto-02:

class Person(BaseModel):
    first_name: str = Field(
        ...,
        title="First Name",
        description=None,
        min_length=1,
        max_length=50
    )
    last_name: str = Field(
        ...,
        title="Last Name",
        description=None,
        min_length=1,
        max_length=50
    )
    age: int = Field(
        ...,
        title="Age",
        description=None,
        gt=0,
        le=115
    )
    hair_color: Optional[HairColor] = Field(title="Hair Color", description=None)
    is_married: Optional[bool] = Field(title="Is Married", description=None)
    website: Optional[HttpUrl] = Field(title="Website", description=None)
    email: Optional[EmailStr] = Field(title="Email", description=None)
    password: Optional[SecretStr] = Field(title="Password", description=None, min_length=8) 

CODIGO:

Reto-01:

class Location(BaseModel):
    city: str = Field(
        ...,
        title="Last Name",
        description=None,
        min_length=1,
        max_length=50
    )
    state: str = Field(
        ...,
        title="Last Name",
        description=None,
        min_length=1,
        max_length=50
    )
    country: str = Field(
        ...,
        title="Last Name",
        description=None,
        min_length=1,
        max_length=50
    ) 

Update del Reto validando número de tarjeta de crédito e email, para validar donaciones a ONG’s

class Payment(BaseModel):
    card_num: PaymentCardNumber = Field(...)

class Person(BaseModel):
    first_name: str = Field(
        ...,
        min_length=2,
        max_length=50,
    )
    last_name: str = Field(
        ...,
        min_length=2,
        max_length=60,
    )
    msg: str = Field(..., min_length=2,max_length=300)
    email: EmailStr = Field(...)
    birthday: date_type = Field(...)
    country: str = Field(...)

app = FastAPI()

@app.get('/')
def home():
    return {'Hello': 'World'}

@app.post('/dotanions/')
def donations(
    ong_id:Optional[int] = Query(
        default=None,
        gt=0,
        title='ONG ID',
        description='This is the ONG ID',
    ),
    person: Optional[Person]= Body(default=None),
    payment: Optional[Payment]= Body(default=None),
):
    results = (dict(person))
    results.update(dict(payment))
    return {ong_id: results}

Un método put para hacer donaciones a una ONG

# Donation's API

# Challenge: Create an API to receive donations
# Getting: Person data, Payment Data, email, and date

# Python

from DateTime import date as date_type
from enum import Enum
from typing import Optional

#Pydanyic

from pedantic import BaseModel, EmailStr, Field

# FastAPI

from fastapi import Body, FastAPI, Path, Query

# MODELS

class Person(BaseModel):
    first_name: str = Field(
        ...,
        min_length=2,
        max_length=50,
    )
    last_name: str = Field(
        ...,
        min_length=2,
        max_length=60,
    )
    msg: str = Field(..., min_length=2,max_length=300)
    email: EmailStr = Field(...)
    birthday: date_type = Field(...)
    country: str = Field(...)

app = FastAPI()

@app.get('/')
def home():
    return {'Hello': 'World'}

@app.post('/dotanions/')
def donations(
    ong_id:Optional[int] = Query(
        default=None,
        gt=0,
        title='ONG ID',
        description='This is the ONG ID',
    ),
    person: Optional[Person]=Body(...)
):
    results = (dict(person))
    return {ong_id: results}
# Models

class HairColor(Enum):
    white = "white"
    brown = "brown"
    black = "black"
    blonde = "blonde"
    red = "red"

class Country(Enum):
    mexico = "Mexico"
    colombia = "Colombia"
    peru = "Peru"
    chile = "Chile"
    argentina = "Argentina"
    brazil = "Brasil"

class Location(BaseModel):
    city:str = Field(
        ...,
        min_length=1,
        max_length=50
        )
    state:str = Field(
        ...,
        min_length=1,
        max_length=50
        )
    country:Optional[Country] = Field(default=None)
    

class User(BaseModel):
    first_name: str = Field(
        ...,
        min_length=1,
        max_length=50
        )
    last_name: str= Field(
        ...,
        min_length=1,
        max_length=50
        )
    age: int = Field(
        ...,
        gt=0,
        le=115
        )
    email: EmailStr = Field()
    credit_card: PaymentCardNumber = Field()
    birthday: PastDate = Field()
    hair_color: Optional[HairColor] = Field(default=None)
    is_married: Optional[bool] = Field(default=None)

Mi solucion al reto

Para instalar el validador de email: pip install email-validator

#Python
from typing import Optional
from enum import Enum

#Pydantic
from pydantic import BaseModel, PaymentCardNumber
from pydantic import Field, EmailStr, PositiveInt, HttpUrl

#FastAPI
from fastapi import FastAPI
from fastapi import Body, Query, Path


app = FastAPI()


#Models
class HairColor(Enum):
    white = 'white'
    brown = 'brown'
    black = 'black'
    blonde = 'blonde'
    red = 'red'

class Location(BaseModel):
    city: str = Field(
        ...,
        min_length=1,
        max_length=30
    )
    state: str = Field(
        ...,
        min_length=1,
        max_length=30
    )
    country: str = Field(
        ...,
        min_length=1,
        max_length=30
    )

class Person(BaseModel):
    first_name: str = Field(
        ..., 
        min_length=1,
        max_length=50
        )
    last_name: str = Field(
        ..., 
        min_length=1,
        max_length=50
        )
    age: int = Field(
        ...,
        gt=0,
        lt=115
    )
    email: EmailStr = Field(
        ...,
        exclusiveMinimum=1,
        exclusiveMaximum=30
    )
    payment_card: Optional[PaymentCardNumber] = Field(default=None)
    personal_page: Optional[HttpUrl] = Field(default=None)
    hair_color: Optional[HairColor] = Field(default=None)
    is_married: Optional[bool] = Field(default=None)


@app.get('/')
def home():
    return {'Hello': 'World'}


# Request and Response body
@app.post('/person/new')
def create_person(person: Person = Body(...)):
    return person

# Validaciones: Query parameters
@app.get('/person/detail')
def show_person(
    name: Optional[str] = Query(
        None,
        min_length=1,
        max_length=50,
        title='Person Name',
        description='This is the person name. It\'s between 1 and 50 characters'
        ),
    age: str = Query(
        ...,
        title='Person Age',
        description='This is the person age. It\'s required'
        )
):
    return {name: age}


# Validaciones: Path parameters
@app.get('/person/detail/{person_id}')
def show_person(
    person_id: int = Path(
        ..., 
        gt=0,
        title='Person Age',
        description='This is the peron age. It\'s required'
        )
):
    return {person_id: 'It exists!'}


# Validaciones: Request Body
@app.put('/person/{person_id}')
def update_person(
    person_id: int = Path(
        ...,
        title='Person ID',
        description='This is the person ID',
        gt=0
    ),
    person: Person = Body(...),
    location: Location = Body(...)
):
    results = person.dict()
    results.update(location.dict())
    return results

Reto:

#Python - Python está por encima de cualquier librería, así que todo lo que sea de python va encima
from typing import Optional
from enum import Enum


#Pydantic - Esta librería esta por encima de FastAPI, es decir que FastAPI lo utiliza, por ello se pone por encima (jerarquía)
from pydantic import BaseModel, EmailStr, PaymentCardNumber, PositiveInt, FutureDate
from pydantic import Field#Validar modelos

#FastAPI
from fastapi import FastAPI
from fastapi import Body,Query,Path#Me permite decir explicitamente que un parametro es de tipo Body

app = FastAPI()

#Models
class HairColor(Enum):
    white = 'white'
    brown = 'brown'
    black = 'black'
    blonde = 'blonde'
    red = 'red'

class Person(BaseModel):
    #Dependiendo del negocio hay ciertos atributos que puede ser ocpcionales
    first_name: str = Field(
                            default=...,
                            min_length=1,
                            max_length=50
                            )
    last_name: str = Field(
                            default=...,
                            min_length=1,
                            max_length=50
                            )
    age: int = Field(
                    default=...,
                    gt=0,
                    le=115
                    )
    #Atributos opcionales
    hair_color: Optional[HairColor] = Field(
                                    default=None
                                    )
    is_married: Optional[bool] = Field(
                                        default=None
                                        
                                        )

class Payment(BaseModel):
    email:Optional[EmailStr] = Field(
                                    ...,
                                    )

    credit_card:Optional[PaymentCardNumber] = Field(
                                                    ...,
                                                    )
    valid_date:Optional[FutureDate] = Field(
                                        ...
                                        )
    phone_number:Optional[PositiveInt] = Field(
                                            defaiult=None
                                            ) 


class Location(BaseModel):
    country:str = Field(
                        default=...,
                        min_length=3,
                        max_length=100
                    )
    state:str = Field(
                        ...,
                        min_length=3,
                        max_length=100
                    )
    city:str = Field(
                        default=None,
                        min_length=3,
                        max_length=100
                    )
@app.get("/")
def home():
    return  {'Hello': 'World '}

#Request and Response body
#Como vamos a enviar datos al servidor, utilizamos post
@app.post('/person/new')
def create_person(person: Person = Body(
        ...
        )
    ):#El parametro Body() indica que es de tipo body request y ... significa que el parámetro es obligatorio, en este caso que el request body es obliatorio
    return person

#Validaciones: Query Parameters
#'/person/detail?name=Miguel&age=25'
@app.get('/person/detail')
def show_person(
    name: Optional[str] = Query(
                            default=None, 
                            min_length=1, 
                            max_length=50
                            ),
    #Los tres puntos hace que que sea obligatorio, no es la mejor opción, ya que quitan quitando el query param (Query(...):) y Optional. Para que sean obligatorio está la opción de create_person
    age:Optional[int] = Query(
                            ...
                            )
):
    return {'name':name, 'age':age}

#Validaciones: explorando más parameters
@app.get('/person/detailv2')
def show_person(
    name: Optional[str] = Query(
                                default=None, 
                                min_length=1, 
                                max_length=50, 
                                title='Name',#En swagger UI no se muestra, porque no es soportado, pero si se muestra en redoc 
                                description='Person name. It`s between 1 and 50 character'
                                ),
    #Los tres puntos hace que que sea obligatorio, no es la mejor opción, ya que quitan quitando el query param (Query(...):) y Optional. Para que sean obligatorio está la opción de create_person
    age:Optional[int] = Query(
                                ..., 
                                title='Age',#En swagger UI no se muestra, porque no es soportado, pero si se muestra en redoc 
                                description='Person Age. It`s a required parameter'
                                )
):
    return {'name':name, 'age':age}

#Validaciones: Path Parameters, este path operation es igual al anterior, y de esta forma lo que va a hacer python es sobreescribir el de arriba que ya no va servir, porque este lo sobreescribe
@app.get('person/detail/{person_id}')
def show_persona(
    person_id:int = Path(
                        ..., 
                        gt=0, 
                        lt=130, 
                        title='Id',#En swagger UI no se muestra, porque no es soportado, pero si se muestra en redoc
                        description="Person Id. It`s a required parameter and the number could be between 1 and 129"
                        )
):
    return {'person_id': person_id}

#Validaciones: Request Body
@app.put('/person/{person_id}')#Sirve para actualizar algun contenido en nuestra aplicación
def update_persona(
    person_id:int = Path(
                        ..., 
                        title='Id', 
                        description='Person identification',
                        gt=0
                        ),
    person: Person = Body(
                        ...,
                        ),
    location: Location = Body(
                            ...
                            ),
    payment: Payment = Body(
                            ...
                            )
):
    #Si queremos combinar dos json, esto fastapi no lo hace y por eso debemos realizarlo nosotros
    #Como?
    results = person.dict()
    results.update(location.dict())
    #Agregamos más datos a la persona
    results.update(payment.dict())
    #Habría otra forma de hacer que sería person.dict() & location.dict(), pero fastapi todavía no lo soporta
    return results

Código completo con reto incluido:

#Python
from typing import Optional
from enum import Enum

#Pydantic
from pydantic import BaseModel, EmailStr, HttpUrl
from pydantic import Field

#FastAPI
from fastapi import FastAPI
from fastapi import Body, Query, Path


app = FastAPI()

#Models



class HairColor(Enum):
    white = "white"
    brown = "brown"
    black = "black"
    blonde = "blonde"
    red = "red"

class Location(BaseModel):
    city: str = Field(
        min_length=1,
        max_length=50,
        example="Ilo"
    )
    state: str = Field(
        min_length=1,
        max_length=50,
        example="Moquegua"
    )
    country: str = Field(
        min_length=1,
        max_length=50,
        example="Peru"
    )

class Person(BaseModel):
    first_name: str = Field(
        ..., 
        min_length=1,
        max_length=50,
        example="Jordan"
        )
    
    last_name: str = Field(
        ..., 
        min_length=1,
        max_length=50,
        example="Salas"
        )
    age: int = Field(
        ...,
        gt=0,
        le=115,
        example=29
    )
    hair_color: Optional[HairColor] = Field(
        default=None,
        example="black"
        )
    is_married: Optional[bool] = Field(
        default=None,
        example=False
        )
    email: Optional[EmailStr] = Field(
        default=None, 
        example="[email protected]"
    )
    
    website: Optional[HttpUrl] = Field(
        default=None,
        example="www.noldiacc.com"
    )
    
    # class Config:
    #     schema_extra = {
    #         "example": {
    #             "first_name": "Noldia",
    #             "last_name": "Chavez Cavero",
    #             "age": 28,
    #             "hair_color": "black",
    #             "is_married": False
    #         }
    #     }

@app.get("/")
def home():
    return {"Hello": "World"}

#request and response body

@app.post("/person/new")
def create_person(person: Person = Body(...)):
    return person

#validaciones query parameters
#validations Querys: max_length,min_length, regex, ge ,le ,gt,lt

@app.get("/person/detail")
def show_person(
    name: Optional[str] = Query(
        None,
        min_length=1,
        max_length=50,
        title="Person Name",
        description="This is the person name. It between 1 and 50 characters",
        example="Andrea"
        ),
    age: int = Query(
        ...,
        title="Person Age",
        description="This is the person age. It's requiered",
        example=20
        )
):
    return {name: age}

#validaciones path parameters

@app.get("/person/detail/{person_id}")
def show_person(
    person_id: int = Path(
        ..., 
        gt=0,
        title="Person ID",
        description="This is a person ID. It's requiered",
        example=222
        )
):
    return {person_id: "It exist!"}

#Validaciones: Request body

@app.put("/person/{person_id}")
def update_person(
    person_id: int = Path(
        ...,
        title="Person ID",
        description="This is the person ID",
        gt=0,
        example=111
    ),
    person: Person = Body(...),
    location: Location =Body(...)
):
    results = person.dict()
    results.update(location.dict())
    return results
    #return person

Para la validación del mail en mac, use

pip install email-validator

reto 1

class Location(BaseModel):
    city: str = Field(
        ...,
        min_length=2,
        max_length=50,
        default=None,
    )
    state: str = Field(
        ...,
        min_length=2,
        max_length=50,
        default=None,
    )
    country: str= Field(
        ...,
        min_length=2,
        max_length=50,
        default=None,
    )

reto 2

from pydantic import PastDate, HttpUrl, PaymentCardNumber

    # exotics types
    birthday: PastDate = Field(..., alias="birthday")   
    personal_cite: HttpUrl = Field(..., alias="personal_cite")
    credit_card: PaymentCardNumber = Field(..., alias="credit_card")

Challenge:
Email, Color, PaymentCard

# Python
from typing import Optional, List
from enum import Enum
from datetime import date

# Pydantic
from pydantic import BaseModel
from pydantic import Field, EmailStr
from pydantic.color import Color
from pydantic.types import PaymentCardBrand, PaymentCardNumber, constr

# FastAPI
from fastapi import FastAPI
from fastapi import Body, Query, Path # specified the parameter have a body

# this var contain all the app
app = FastAPI()

# Models

class HairColor(Enum):
    white: Color= "#FFFFFF"
    brown: Color= "#964B00"
    black: Color= "#282c34"
    blonde: Color= "#f5e265"
    red: Color= "#c11b1b"


class Location(BaseModel):
    city: str = Field(
        ..., 
        min_length=1,
        max_length=50,
    )
    state: str = Field(
        ..., 
        min_length=1,
        max_length=50,
    )
    country: str = Field(
        ..., 
        min_length=1,
        max_length=50,
    )


class Person(BaseModel):
    first_name: str = Field(
        ..., 
        min_length=1,
        max_length=50,
    )
    last_name: str = Field(
        ..., 
        min_length=1,
        max_length=50,
    )
    age: int = Field(
        ..., 
        gt=0,
        le=115
    )
    email: List[EmailStr]
    # if the user dont send nothing the value is NULL
    hair_color: Optional[HairColor] = Field(default=None)
    is_married: Optional[bool] = Field(default=None)


class Card(BaseModel):
    name: constr(strip_whitespace=True, min_length=1)
    number: PaymentCardNumber = Field(
        ..., 
        min_length=1,
        max_length=16,
    )
    exp: date = Field(
        ..., 
        title="Expired date",
        description="This is the expired date for the card",
    )


    @property
    def brand(self) -> PaymentCardBrand:
        return self.number.brand

    @property
    def expired(self) -> bool:
        return self.exp < date.today() 




# Fast operations
@app.get("/") # path operation decorator
def home(): # path operation function
    return {"Hello": "World"}

# Request and response body
@app.post("/person/new")
def create_person(person: Person = Body(...)):
    return person

# Validations: Query params
@app.get("/person/detail")
def show_person(
    name: Optional[str] = Query(
        None, 
        min_length=1, 
        max_length=50,
        title="Person name",
        description="This is the person name. It's between 1 and 50 characters",
    ),
    age: str = Query(
        ...,
        title="Person age",
        description="This is the person age. It's required",
    ),
):
    return {name: age}

# Validations: Path params
@app.get("/person/detail/{person_id}")
def show_person(
    person_id: int = Path(
        ..., 
        gt=0,
        title="Person id",
        description="This is the person id. It's required",
    )
):
    return {person_id: "It exists!"}

# Validations: Request body
@app.put("/person/{person_id}")
def update_person(
    person_id: int = Path(
        ...,
        title= "Person ID",
        description= "This is the person id.",
        gt= 0,
    ),
    person: Person = Body(...),
    location: Location = Body(...),
    card: Card = Body(...),
): 
    results = person.dict()
    results.update(location.dict()) # append the dic
    results.update(card.dict())
    return results

Challenge 2:

I added these 3 new attributes:

born_date: PastDate = Field(
    ...
)
personal_web: Optional[AnyHttpUrl] = Field(
    default=None
)
password: SecretStr = Field(...)

Challenge 1:

Here is how you can validate Location Model:

city: str = Field(
    ...,
    min_length=2,
    max_length=100
)
state: str = Field(
    ...,
    min_length=2,
    max_length=100
)
country: str = Field(
    ...,
    min_length=2,
    max_length=100
)

Para el reto agregué dos datos exóticos de tarjeta de credito:

from pydantic.types import PaymentCardBrand, PaymentCardNumber
class Person(BaseModel):
	firts_name: str = Field(
		...,
		min_length = 1,
		max_length = 50
		)
	last_name: str = Field(
		...,
		min_length = 1,
		max_length = 50
		)
	age: int = Field(
		...,
		gt=0,
		le=150
		)
	hair_color: Optional[HairColor] = Field(default=None)
	is_married: Optional[bool] = Field(default=None)
	credit_card_company: PaymentCardBrand = Field(...)
	credit_card_number: PaymentCardNumber = Field(...)

class Location(BaseModel):
	city: str = Field(
		...,
		min_length = 2,
		max_length = 100,
		)
	state: str  = Field(
		...,
		min_length = 2,
		max_length = 100
		)
	country: str = Field(
		...,
		min_length = 2,
		max_length = 100
		)

Reto

#Python
from typing import Optional
from enum import Enum

#Pydantic
from pydantic import BaseModel, Field, EmailStr, SecretStr, HttpUrl

#FastAPI
from fastapi import FastAPI
from fastapi import Body, Query, Path

#  Instancia de la clase
app = FastAPI()

#Models 
# Modelo de especificación de colores de cabello
class HairColor(Enum):
    white = 'white'
    black = 'black'
    brown = 'brown'
    blonde = 'blonde'
    
class Location(BaseModel):
   city: str = Field(
      ...,
      min_length = 0,
      max_length=120
      )
   state: str = Field(
      ...,
      min_length = 0,
      max_length=120
      )
   country: Optional[str] = Field(
      default = None
      )


class Person(BaseModel):
    first_name: str = Field(
        ...,
        min_length=1,
        max_length=50
      )
    last_name: str = Field(
        ...,
        min_length=1,
        max_length=50
      )
    age: int = Field(
        ...,
        gt=8,
        le=110
      )
    hair_color: Optional[HairColor] = Field(default=None)
    is_married: Optional[bool] = Field(default=None)
    email: EmailStr = Field(...)
    password: SecretStr = Field(
        ...,
        min_length=8
    )
    web_site : Optional[HttpUrl] = Field(default = None)

#  Path Operator Decoration
@app.get("/")
def home():
   #  Return JSON.
   return {"Hello": "World"}

#Request and Response Body

@app.post("/person/new")
def create_person(person: Person = Body(...)):
   return {"status": "Ok", "response": person}


# Validaciones: Query Parameters

@app.get("/person/detail")
def show_person(
   name: Optional[str] = Query(
      None, 
      min_length=1, 
      max_length=50,
      regex='^[A-Za-z]*$', #Espresion regular. Admite caracteres alfabéticos y sin espacios.
      title="Person Name",
      description="This is the person name. It's between 3 and 35 characters."
      ),
   age: str = Query(
      ...,
      title="Person Age",
      description="This is the person age. It's required."
      )
):
   return {name: age}


# Validaciones: Path Parameters

@app.get("/person/detail/{person_id}")
def show_person(
   person_id: int = Path(
      ..., 
      gt=0,
      title="Person Id",
      description="This is the person id. It's required and it's more than 0."
      )
):
   return {person_id: "it exists!"}


# Validaciones: Request Body 

@app.put('/person/{person_id}')
def update_person(
    person_id: int = Path(
        ...,
        ge=1,
        title='Person id',
        description='Id of the person you want to update'
    ),
    person: Person = Body(...),
    location: Location = Body(...)
):
    result = dict(person)
    result.update(dict(location))

    return result

Esta es mi solución al reto

class Person(BaseModel):
    first_name: str = Field(
        ...,
        min_length=1,
        max_length=50
    )
    last_name: str = Field(
        ...,
        min_length=1,
        max_length=50
    )
    email: EmailStr = Field(
        ...,
    )
    age: int = Field(
        ...,
        ge=18,
        le=100,
    )
    website: Optional[HttpUrl] = Field(
        default=None
    )
    birth_date: Optional[date] = Field(
        default=None
    )
    hair_color: Optional[HairColor] = Field(
        default=None,
    )
    is_married: Optional[bool] = Field(
        default=None,
    )


class Location(BaseModel):
    city: str = Field(
        ...,
        min_length=1,
        max_length=50
    )
    state: str = Field(
        ...,
        min_length=1,
        max_length=50
    )
    country: str = Field(
        ...,
        min_length=1,
        max_length=50
    )

Dentro de la clase Person, yo solo puse un “example” para que la edad predeterminada fuera 18 y no 115 (pensé que estaría bien asumir que nuestro usuario es mayor de edad, al menos acá en México esa es la edad para ser considerado adulto).

age: int = Field(
        ...,
        gt=0,
        le=115,
        example=18
    )

Reto de la clase

class Person(BaseModel):
    first_name: str = Field(..., min_length=1, max_length=50)
    last_name: str = Field(..., min_length=1, max_length=50)
    email: EmailStr = Field(..., min_length=1, max_length=50)
    age: int = Field(..., gt=0, le=115)
    web_page: HttpUrl = Field(...)
    height: PositiveFloat = Field(...)
    hair_color: Optional[HairColor] = Field(default=None)
    is_married: Optional[bool] = Field(default=None)


class Location(BaseModel):
    city: str = Field(..., min_length=1, max_length=50)
    state: str = Field(..., min_length=1, max_length=50)
    country: str = Field(..., min_length=1, max_length=50)

He agregado un email validator, y un password validator a la clase person, hay una clase de pydantic que se llama SecretStr que permite ponerle formato de password al json al hacer la validación para que no se pueda fugar esa información:

#Python
from typing import Optional
from fastapi.param_functions import Query
from enum import Enum

#Pydantic
from pydantic import BaseModel, SecretStr
from pydantic import Field

#FastAPI
from fastapi import FastAPI
from fastapi import Body, Query, Path

#Email Validator
from email_validator import validate_email, EmailNotValidError
from pydantic.networks import EmailStr

app = FastAPI()

#Models

class Location(BaseModel):
    city: str = Field(
        ...,
        min_length=2,
        max_length=50
    )
    state: Optional[str] = Field(default=None)
    country: str = Field(
        ...,
        min_length=2,
        max_length=50
    )

class HairColor(Enum):
    white="white"
    brown="brown"
    black="black"
    blonde="blonde"
    red="red"


class Person(BaseModel):
    first_name: str = Field(
        ..., 
        min_length=1,
        max_length=50
        )
    last_name: str = Field(
        ..., 
        min_length=1,
        max_length=50
        )
    age: int = Field(
        ...,
        gt=0,
        le=115
        )
    hair_color: Optional[HairColor] = Field(default=None)
    is_married: Optional[bool] = Field(default=None)
    email: EmailStr = Field(...)
    password: SecretStr = Field(
        ...,
        min_length=8
    )

@app.get("/") #Path operation decorator
def home():   #Path Operation Function
    return {"Hello": "World"} #JSON

#Request and Response Body


@app.post("/person/new")
def create_person(person: Person = Body(...)):
    return person

#Validaciones: Query Parameters

@app.get("/person/detail")
def show_person(
    name: Optional[str] = Query(
        None, 
        min_length=1, 
        max_length=50,
        title="Person's name",
        description="This is the person's name. Must be between 1-50 characters"
        ),
    age: int = Query (
        ..., 
        gt=0, 
        title="Person's age", 
        description="This is the person's age. Must be greater than 0 (doesn't exist negative age values)"
        )
):
    return {name: age}

#Validations: Path Parameters

@app.get("/person/detail/{person_id}")
def show_person(
    person_id: int = Path(..., gt=0)
):
    return {person_id: "It exists!"}

#Validations: Request Body

@app.put("/person/{person_id}")
def update_person(
    person_id: int = Path(
        ...,
        title="Person ID",
        description="This is the person's ID",
        gt=0
    ),
    person: Person = Body(...),
    location: Location = Body(...)
):
    results= person.dict() #This convert JSON to DICT
    results.update(location.dict()) #This update the DICT with the other JSON converted in DICT

    return results

Así quedaron mis modelos
Para EmailStr instalar en el entonrno virtual:

pip install email-validator

Para PostiveInt importar:

from pydantic import PositiveInt
class Location(BaseModel):
    city: str = Field(..., example="New York")
    state: str = Field(..., example="NY")
    country: str = Field(..., example="USA")


class Person(BaseModel):
    first_name: str = Field(
        ...,
        min_length=2,
        max_length=50,
    )
    last_name: str = Field(
        ...,
        min_length=2,
        max_length=50,
    )
    age: PositiveInt = Field(...)
    hair_color: Optional[HairColor] = Field(default=None, example=HairColor.BLACK)
    is_married: Optional[bool] = Field(default=None, example=False)
    email: EmailStr = Field(default=None)

Cumplido ambos retos.
Reto 1:

class Location(BaseModel):
    city: str = Field(
        ...,
        min_length=4,
        max_length=24
        )
    state: str = Field(
        ...,
        min_length=4,
        max_length=24
        )
    country: str = Field(
        ...,
        min_length=4,
        max_length=24
        )
    latam: bool

Reto 2:

class Person(BaseModel): # Person parameters
    first_name: str = Field(
        ...,
        min_length=3,
        max_length=35
        )
    last_name: str = Field(
        ...,
        min_length=3,
        max_length=35
        )
    age_person: int = Field(
        ...,
        ge=18,
        le=115
        )
    color_hair: Optional[HairColor] = Field(default=None)
    married: Optional[bool] = Field(default=None)
    email_usr : EmailStr
    web_usr : HttpUrl
    pay_card_usr: PaymentCardNumber

Por curiosidad quise usar el tipo de dato ‘PaymentCardNumber’. Entonces usé un número ‘4545454545454545’ pero obtuve en la interfaz un error diciendo que este número no es de tipo ‘Luhn’. Me pareció muy extraño y busqué un poco por internet y llegué a que esto se basa en el Algoritmo de Luhn
Esta es la base para las funciones hash 🤯 y me pareció muy interesante.
Bueno, al final usé un número de tarjeta válido y funcionó. Aquí mi código:

#Python
from typing import Optional
from enum import Enum #Podemos crear enumaraciones de Strings

#Pydantic
from pydantic import BaseModel
from pydantic import Field # Es lo mismo que Body, Query, Path
from pydantic import EmailStr
from pydantic import PaymentCardNumber
from pydantic.color import Color

#FastAPI
from fastapi import FastAPI
from fastapi import Body, Query, Path


app = FastAPI() # Todo nuestro programa se carga en la variable


# Models

class HairColor(Enum):
    RED = "red"
    BLONDE = "blonde"
    BROWN = "brown"
    BLACK = "black"
    WHITE = "white"
    GRAY = "gray"


class Location(BaseModel):
    city: str = Field(
        ...,
        min_length=1,
        max_length=75
    )
    state: str = Field(
        ...,
        min_length=1,
        max_length=75
    )
    country: str = Field(
        ...,
        min_length=1,
        max_length=75
    )


class Person(BaseModel):
    first_name: str = Field(
        ...,
        min_length=1,
        max_length=50
    )
    last_name: str= Field(
        ...,
        min_length=1,
        max_length=50
    )
    age: int = Field(
        ...,
        gt=0,
        le=115
    )
    hair_color: Optional[HairColor] = Field(default=None)
    is_married: Optional[bool] = Field(default=None)
    email: EmailStr = Field(
        ...,
        title="Email",
        description="Email of the person. Must be valid.",
    )
    payment_card_number: PaymentCardNumber = Field(
        ...,
        title="Payment card number",
        description="Payment card number of the person to pay our services. Must be valid.",
    )
    favorite_color: Optional[Color] = Field(default=None)



@app.get("/") #path operation decorator
def home(): #path operation function
    return {"message": "Hello World, I'm using Python and FastAPI 🐍"}


# Request and Response Body

@app.post("/person/new")
def create_person(person: Person = Body(...)): # Los '...' indican que es obligatorio
    return person


# Validaciones: Query Parameters

@app.get("/person/detail")
def show_person(
    name: Optional[str] = Query(
        None,
        min_length=1,
        max_length=50,
        title="Person name.",
        description="This is the person name. It's between 1 and 50 characters.",
        ),
    age: int = Query(
        ...,
        title="Person age",
        description="This is the person age. It's required",
        )
):
    return {"name": name, "age": age}


# Validaciones: Path Parameters

@app.get("/person/detail/{person_id}")
def show_person(
    person_id: int = Path(
        ...,
        gt=0,
        title="Person ID.",
        description="This is the person ID. It's required and must be greater than 0.",
        )
):
    return {"person_id": person_id}


# Validations: Request Body

@app.put("/person/{person_id}")
def update_person(
    person_id: int = Path(
        ...,
        title="Person ID.",
        description="This is the person ID.",
        gt=0
    ),
    person: Person = Body(...),
    location: Location = Body(...),
):
    return {
        "person_id": person_id,
        "person:": person,
        "location": location,
    }

#ORDEN DE IMPORTACIÓN
#Python
from typing import Optional
from enum import Enum
#from fastapi.param_functions import Path
#Pydantic
from pydantic import BaseModel
from pydantic import Field # Validar los atributos de un modelo
#FastAPI
from fastapi import FastAPI
from fastapi import Body, Query, Path
from pydantic.networks import EmailStr   # BD Reconoce a un parameto de llegada para que sea de tipo Body
from pydantic.color import Color

app = FastAPI() #Definir una variable colocandole una instancia de FastAPI

# ... MODELS ...
class Vacuna_(Enum):
    Pfizer = "Pfizer"
    Moderna = "Moderna"
    Sputnik = "Sputnik"
    Novavax = "Novavax"
    Sinopharm = "Sinopharm"
    
class Person(BaseModel):
    first_name: str = Field(
        ..., 
        min_length=1, 
        max_length=50
        )
    last_name: str = Field(
        ..., 
        min_length=1, 
        max_length=50
        )
    age: int = Field(
        ..., 
        gt=0, 
        le=100
        )
    email: EmailStr = Field(...)
    hair_color: Optional[Color] = Field(default=None)
    vacuna: Optional[Vacuna_] = Field(default=None)
    is_married: Optional[bool] = Field(default=None)
    
class Location(BaseModel):
    city: str = Field(
        ..., 
        min_length=1,
        max_length=50
        )
    state: str = Field(
        ..., 
        min_length=1,
        max_length=50
        )
    country: str = Field(
        ..., 
        min_length=1,
        max_length=50
        )  


@app.get("/") # Path operation decorator
def home():   # Es el primer lugar en el que un usuario de nuestra API va aparecer cuand la use
    return {"Hello": "World", "Nombre":"Junior"}

# Request and Response Body

@app.post("/person/new")
def create_person(person: Person = Body(...)):  # BD usando Body
    return person

# Validation Query Parameters

@app.get("/person/detail")
def show_person(
    name: Optional[str] = Query(
        None, 
        min_length=1, 
        max_length=50,
        title="Person Name",
        description="This is the person name, It's between 1 and 50 character"
        ),
    age: int = Query(
        ...,
        title="Person Age",
        description="This is the person age. It's required"
        ), 
    email: EmailStr = Query(...)
):
    return {name : age}

# Validation Path Parameters

@app.get("/person/detail/{person_id}")
def show_person(
    person_id: int = Path(
        ..., 
        title="Person ID",
        description="This is the person id. It's greater than 0",
        gt=0)    
):
    return {person_id : "It exists!"}

# Validation Request Body (02 Request Body en el ejemplo)

@app.put("/person/{person_id}")
def update_person(
    person_id: int = Path(
        ...,
        title = "Person_ID",
        description="This is the person ID",
        gt=0
    ),
    person: Person = Body(...),
    location: Location = Body(...)
):
    # Unir diccionarios con el update
    results = person.dict()
    results.update(location.dict())
    return results

Hola este es mi código cumpliendo el reto propuesto por Facundo

#Python
from typing import Optional
from enum import Enum

#Pydantic
from pydantic import BaseModel
from pydantic import Field
from pydantic.networks import EmailStr, HttpUrl
from pydantic.types import PaymentCardNumber

#FastAPI
from fastapi import FastAPI
from fastapi import Body, Query, Path, Body
app = FastAPI()

#Models

class CustomerSeller(Enum):
    customer = "customer"
    seller = "seller"

class HairColor(Enum):
    white = "white"
    black = "black"
    brown = "brown"
    blonde = "blonde"
    red = "red"

class location(BaseModel):
    city : str = Field(
        ...,
        min_length = 1,
        max_length = 150,
    )
    state: str = Field(
        ...,
        min_length = 1,
        max_length = 150,
    )
    country: str = Field(
        ...,
        min_length = 1,
        max_length = 150,
    )

class Person(BaseModel):
    first_name : str = Field(
        ..., 
        min_length = 1,
        max_length = 50,
    )
    last_name : str = Field(
        ..., 
        min_length = 1,
        max_length = 50,
    )
    age: int = Field(
        ...,
        gt = 0,
        le = 115
    )
    email : EmailStr = Field(...)
    web_site : Optional[HttpUrl] = Field(default = None)
    customer_seller : Optional[CustomerSeller] = Field(default = "customer")
    hair_color : Optional[HairColor] = Field(default = None)
    is_married : Optional[bool] = Field(defauld = None)

@app.get("/")
def home():
    return {"Hello": "World"}

# Request and Pesponse

@app.post("/person/new")
                                #Obligatorio el (...)
def create_person(person: Person = Body(...)):
    return person

#Validaciones: Query Parameters
@app.get("/person/detail")
def show_person(
    name: Optional[str] = Query(
        None, 
        min_length = 1, 
        max_length=50,
        title = "Person Name",
        description = "This is the person name. It´s between 1 and 50 characters"
        ),
    age : str = Query(
        ...,
        title = "Person Age",
        description = "This is the person age. It´s required"
        )
):
    return {name : age}

#Validaciones: Path Parameters

@app.get("/person/detail/{person_id}")
def show_person(
    person_id : int = Path(
        ..., 
        gt=0,
        title = "Person Id",
        description = "This is the Person Id."
        )
):  
    return {person_id: "It exists"}

#Validaciones: Request Body

@app.put("/person/{person_id}")
def update_person(
    person_id: int = Path(
        ...,
        title = "Person ID",
        description = "This is the person ID",
        gt = 0
    ),
    person : Person = Body(...),
    location: location = Body(...)
):
    results = person.dict()
    results.update(location.dict())
    return results

@app.post("/person/{person_id}/buy")
def BuyWebSide(
    person_id: int = Path(
        ...,
        title = "Person ID",
        description = "This is the person ID",
        gt = 0
    ),
    URLtoBuy : HttpUrl = Path(...),
    value : float = Path(
        ..., 
        gt=0,
        title = "Value Website",
        description = "This is the value of the web site you have registered"
    ),
    PayCard : PaymentCardNumber = Path(...),
    person : Person = Body(...)
):
    results = person.dict()
    results.update(person_id.dict())
    results.update(URLtoBuy.dict())
    results.update(value.dict())
    results.update(PayCard.dict())
    return results

Lo que pasa es que cuando lo pruebo me lanza esto:

los valores que le di para esa prueba fueron estos :

Reto

Reto 1

Hice que los campos fueran obligatorios y les puse un min y un max length


Reto 2

Le agregue un email, un website y un nombre y numero de tarjeta a la clase Person.

Para validar el email tuve que instalar email-validator de la siguente forma:
pip install email-validator

💚 Se acepta feeback, gracias 💚

validar a partir de los modelos
y se ven otros tipos de datos para poder dejar pasar la informacion entre cliente y servidor

#Python
from typing import Optional
from enum import Enum

#Pydantic
from pydantic import BaseModel
from pydantic import Field
from pydantic.networks import EmailStr

#FastAPI
from fastapi import FastAPI
from fastapi import Body, Query, Path

app = FastAPI()

Models

class HairColor(Enum):
white = "white"
brown = "brown"
black = "black"
blonde = "blonde"
red = “red”

class Location(BaseModel):
city: str = Field(
…,
min_length=1,
max_length=50
)
state: str = Field(
…,
min_length=1,
max_length=50
)
country: str = Field(
…,
min_length=1,
max_length=50
)

class Person(BaseModel):
first_name: str = Field(
…,
min_length=1,
max_length=50
)
last_name: str = Field(
…,
min_length=1,
max_length=50
)
age: int = Field(
…,
gt=0,
le=115
)
hair_color: Optional[HairColor] = Field(
default=None
)
is_married: Optional[bool] = Field(
default=None
)
email:str = EmailStr(
…
)

@app.get("/")
def home():
return {“Hello”: “World”}

Request and Response Body

@app.post("/person/new")
def create_person(person: Person = Body(…)):
return person

Validations: Query Parameters

@app.get("/person/detail")
def show_person(
name: Optional[str] = Query(
None,
min_Length=1,
max_Length=50,
title=“Person Name”,
description=“This is the person name. It’s between 1 and 50 characters”
),
age: str = Query(
…,
title=“Person Age”,
description=“This is the person age. It’s required”
)
):
return {name: age}

Validations : Path Parameters

@app.get("/person/detail/{person_id}")
def show_person(
person_id: int = Path (
…,
gt=0,
title=“Person Id”,
description=“This is the person Id. It’s required and greater than 0”
)
):
return {person_id: “It exists!”}

Validations: Request Body

@app.put("/person/{person_id}")
def update_person(
person_id: int = Path(
…,
title=“Person ID”,
description=“This is the personID”,
gt=0
),
person: Person = Body(…),
Location: Location = Body(…)
):
results = person.dict()
results.update(Location.dict())
return results

Reto 1

class Location(BaseModel):
    city: str = Field(
        ...,
        min_length=1,
        max_length=50
    )
    state: str = Field(
        ...,
        min_length=1,
        max_length=50
    )
    country: str = Field(
        ...,
        min_length=1,
        max_length=50
    )

Reto 2

class Person(BaseModel):
    first_name: str = Field(
        ...,
        min_length=1,
        max_length=50)
    last_name: str = Field(
        ...,
        min_length=1,
        max_length=50)
    age: PositiveInt = Field(
        ...)
    email: EmailStr = Field(...)
    card_number: Optional[PaymentCardNumber]
    hai_color: Optional[HairColor] = Field(default=None)
    is_married: Optional[bool] = Field(default=None) 
class Person(BaseModel):
    first_name: str = Field(
        ...,
    min_length=1,
    max_length=50
    )
    last_name: str =  Field(
        ...,
    min_length=1,
    max_length=50
    )
    age: int = Field(
        ...,
        gt=0,
        le=150)
    hair_color: Optional[HairColor] = Field(default=None)
    is_married: Optional[bool] = Field(default=None)
    email: EmailStr = Field(
        ...,
        validate=lambda email: validate_email(email)
    )
from typing import Optional
from enum import Enum
from pydantic import BaseModel, Field
from fastapi import FastAPI
from fastapi import Body, Query, Path
from pydantic.networks import EmailStr, HttpUrl
from pydantic.types import PaymentCardNumber

class Hair_Color(Enum):
    black = "black"
    blonde = "blonde"
    brown = "brown"
    white = "white"
    halfRed = "halfred"

class Person(BaseModel):
    first_name: str = Field(
        ...,
        min_length=3,
        max_length=50 
    )

    last_name:str =  Field(
        ...,
        min_length=3,
        max_length=50 
    )
    age:int = Field(
        ...,
        ge=18
    )

    email:EmailStr = Field(...)
    linkedIn: HttpUrl = Field(...)
    credit_card:Optional[PaymentCardNumber] = Field(default="no have credit card")
    hair_color: Optional[Hair_Color] = Field(default=None)
    is_married: Optional[bool] = Field(default=False)


class Location(BaseModel):
    city: str = Field(
        ...,
        title="city of the person",
        description="show the city of the person, between 3 and 150 characters",
        min_length=3,
        max_length=150    
    )
    state:str = Field(
        ...,
        title="state of the person",
        description="show the state of the person, between 3 and 150 characters",
        min_length=3,
        max_length=150   
    )
    country:str = Field(
        ...,
        title="country of the person",
        description="show the country of the person, between 3 and 150 characters",
        min_length=3,
        max_length=150   
    )
    

app = FastAPI()


#GET
@app.get("/items") #PATH operation decorator
def holaMundo(): #PATH operation function
    return {
        "Hello": "World"
    }

#POST
@app.post("/post")
def People(person: Person = Body(...)):
    return person


#Query Parameter
@app.get("/persons")
def show_people(
    name: Optional[str] = Query(None,
    min_lenght=1,
    max_length=50,
    title="show the people names between 1 and 50 characters",
    description="show the people names between 1 and 50 characters"

      ),
    age: str = Query(
    ...,
    title="person age",
    description="show the person age, it's required"
    )
):
    return {name : age}


#Path Parameter
@app.get("/persons/detail/{person_id}")
def show_people(
    person_id: int = Path(
        ...,
        gt=0,
        title="person ID",
        description="show the person ID, it's required"
        )
):
    return {person_id: "Exists"}


#PUT
@app.put("/persons/{person_id}")
def update_people(
    person_id:int = Path(
        ...,
        gt=0,
        title="person ID",
        description = "update the person ID, it must be greater than 0"
    ),

    person: Person = Body(...),

    location: Location = Body(...)
):
    result = person.dict()
    result.update(location.dict())

    return result

Aca mi aporte: se aceptan criticas, ya que no estoy seguro de que esté correcto!

![](

Validaciones en Location

class Location(BaseModel):
    city: str = Field(
        ...,
        min_length=1,
        max_length=50,
    )
    state: str = Field(
        ...,
        min_length=1,
        max_length=50,
    )
    country: str = Field(
        ...,
        min_length=1,
        max_length=50,
    )

Un curso muy recomandao, el de expresiones regulares que les servirá a todos para validar esos datos exóticos que hablan en esta clase: https://platzi.com/clases/expresiones-regulares/