diff --git a/python-pydantic/README.md b/python-pydantic/README.md new file mode 100644 index 0000000000..3beab79a0f --- /dev/null +++ b/python-pydantic/README.md @@ -0,0 +1,15 @@ +# Pydantic: Simplifying Data Validation in Python + +Supporting code for the Real Python tutorial [Pydantic: Simplifying Data Validation in Python](https://realpython.com/pydantic-simplifying-data-validation-in-python/). + +To run the code in this tutorial, install Pydantic with its `email` dependency: + +```console +(venv) $ python -m pip install "pydantic[email]" +``` + +Pydantic has a separate package for [settings management](https://docs.pydantic.dev/latest/concepts/pydantic_settings/), which you'll also cover in this tutorial. To install this, run the following command: + +```console +(venv) $ python -m pip install pydantic-settings +``` diff --git a/python-pydantic/pydantic_models.py b/python-pydantic/pydantic_models.py new file mode 100644 index 0000000000..870d5230e7 --- /dev/null +++ b/python-pydantic/pydantic_models.py @@ -0,0 +1,50 @@ +from typing import Self +from datetime import date +from uuid import UUID, uuid4 +from enum import Enum +from pydantic import ( + BaseModel, + EmailStr, + Field, + field_validator, + model_validator, +) + + +class Department(Enum): + HR = "HR" + SALES = "SALES" + IT = "IT" + ENGINEERING = "ENGINEERING" + + +class Employee(BaseModel): + employee_id: UUID = Field(default_factory=lambda: uuid4(), frozen=True) + name: str = Field(min_length=1, frozen=True) + email: EmailStr = Field(pattern=r".+@example\.com$") + date_of_birth: date = Field(alias="birth_date", repr=False, frozen=True) + salary: float = Field(alias="compensation", gt=0, repr=False) + department: Department + elected_benefits: bool + + @field_validator("date_of_birth") + @classmethod + def check_valid_age(cls, date_of_birth: date) -> date: + date_delta = date.today() - date_of_birth + age = date_delta.days / 365 + + if age < 18: + raise ValueError("Employees must be at least 18 years old.") + + return date_of_birth + + @model_validator(mode="after") + def check_it_benefits(self) -> Self: + department = self.department + elected_benefits = self.elected_benefits + + if department == Department.IT and elected_benefits: + raise ValueError( + "IT employees are contractors and don't qualify for benefits." + ) + return self diff --git a/python-pydantic/settings_management.py b/python-pydantic/settings_management.py new file mode 100644 index 0000000000..7c622e619e --- /dev/null +++ b/python-pydantic/settings_management.py @@ -0,0 +1,16 @@ +from pydantic import HttpUrl, Field +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class AppConfig(BaseSettings): + model_config = SettingsConfigDict( + env_file=".env", + env_file_encoding="utf-8", + case_sensitive=True, + extra="forbid", + ) + + database_host: HttpUrl + database_user: str = Field(min_length=5) + database_password: str = Field(min_length=10) + api_key: str = Field(min_length=20) diff --git a/python-pydantic/validate_functions.py b/python-pydantic/validate_functions.py new file mode 100644 index 0000000000..c4c67a1750 --- /dev/null +++ b/python-pydantic/validate_functions.py @@ -0,0 +1,23 @@ +import time +from typing import Annotated +from pydantic import PositiveFloat, validate_call, Field, EmailStr + + +@validate_call +def send_invoice( + client_name: Annotated[str, Field(min_length=1)], + client_email: EmailStr, + items_purchased: list[str], + amount_owed: PositiveFloat, +) -> str: + email_str = f""" + Dear {client_name}, \n + Thank you for choosing xyz inc! You + owe ${amount_owed:,.2f} for the following items: \n + {items_purchased} + """ + + print(f"Sending email to {client_email}...") + time.sleep(2) + + return email_str