From 484a43956e8dbb0809bf1bffbd578e2ce58da5cf Mon Sep 17 00:00:00 2001 From: Tejas Dixit Date: Tue, 7 Jul 2026 17:01:49 +0530 Subject: [PATCH 01/10] Understand how to use pydantic extra, allow, and forbid --- .../exercises/pydantic-extra.py | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 python_fundamentals/exercises/pydantic-extra.py diff --git a/python_fundamentals/exercises/pydantic-extra.py b/python_fundamentals/exercises/pydantic-extra.py new file mode 100644 index 0000000..95d5eba --- /dev/null +++ b/python_fundamentals/exercises/pydantic-extra.py @@ -0,0 +1,87 @@ +from typing import ClassVar + +from pydantic import BaseModel, ConfigDict, Field, ValidationError + + +class WeatherAPI(BaseModel): + model_config: ClassVar[ConfigDict] = ConfigDict(extra="ignore") + air_quality: int + city: str + + +class BankAccount(BaseModel): + model_config: ClassVar[ConfigDict] = ConfigDict(extra="forbid") + account_number: str = Field( + min_length=12, + max_length=12, + ) + account_type: str + + +class EventLog(BaseModel): + model_config: ClassVar[ConfigDict] = ConfigDict(extra="allow") + event_id: int + event_type: str + + +""" +BaseModel defines `model_config` as a class-level configuration. + +Annotating it with `ClassVar[ConfigDict]` +tells static type checkers that this attribute +belongs to the class rather than individual instances. +""" + + +if __name__ == "__main__": + print("=" * 60) + print("EventLog (extra='allow')") + print("=" * 60) + + try: + event = EventLog( + event_id=0, + event_type="Error", + event_description="ValidationError", + ) + + print(event.model_dump()) + + except ValidationError as exc: + print(exc) + + print() + + print("=" * 60) + print("WeatherAPI (extra='ignore')") + print("=" * 60) + + try: + pune_weather = WeatherAPI( + city="Pune", + air_quality=10, + humidity="low", # type: ignore + ) + + print(pune_weather.model_dump()) + + except ValidationError as exc: + print(exc) + + print() + + print("=" * 60) + print("BankAccount (extra='forbid')") + print("=" * 60) + + try: + bank_account = BankAccount( + account_number="123456789012", + account_type="Savings", + is_admin=True, # type: ignore + ) + + print(bank_account.model_dump()) + + except ValidationError as exc: + print(exc) From dae3a6900f1a7c44afde2cb1c2d75593b776a16a Mon Sep 17 00:00:00 2001 From: Tejas Dixit Date: Sat, 18 Jul 2026 02:15:05 +0530 Subject: [PATCH 02/10] feat: Understand Why is used in buisness fields --- .../exercises/day18-field-alias.py | 85 +++++++ .../exercises/day_23_str_enum/employee.py | 129 ++++++++++ .../exercises/nested_models/nested_models.py | 106 ++++++++ python_fundamentals/notes/day23-str-enum.md | 239 ++++++++++++++++++ 4 files changed, 559 insertions(+) create mode 100644 python_fundamentals/exercises/day18-field-alias.py create mode 100644 python_fundamentals/exercises/day_23_str_enum/employee.py create mode 100644 python_fundamentals/exercises/nested_models/nested_models.py create mode 100644 python_fundamentals/notes/day23-str-enum.md diff --git a/python_fundamentals/exercises/day18-field-alias.py b/python_fundamentals/exercises/day18-field-alias.py new file mode 100644 index 0000000..a65b401 --- /dev/null +++ b/python_fundamentals/exercises/day18-field-alias.py @@ -0,0 +1,85 @@ +from typing import ClassVar + +from pydantic import ( + BaseModel, + ConfigDict, + Field, + ValidationError, + computed_field, + field_validator, +) + + +class Employee(BaseModel): + model_config: ClassVar[ConfigDict] = ConfigDict( + populate_by_name=True, + ) + + """ + Allow model initialization using both the Python field name (`snake_case`) + and its alias (for example, `camelCase`). + + This keeps the internal codebase consistent with Python's naming conventions + while remaining compatible with external APIs that use different field names. + Without `populate_by_name=True`, Pydantic accepts only the alias for aliased + fields during validation, which can result in a `ValidationError` when the + Python field name is provided. + """ + + """This Model represents an employee""" + + first_name: str = Field( + min_length=3, + max_length=20, + alias="firstName", + ) + last_name: str = Field(min_length=2, max_length=20, alias="lastName") + monthly_salary: float = Field(gt=0, alias="monthlySalary") + + @field_validator("first_name") + @classmethod + def transform_first_name(cls, first_name: str) -> str: + first_name = first_name.strip().title() + return first_name + + @field_validator("last_name") + @classmethod + def transform_last_name(cls, last_name: str) -> str: + last_name = last_name.strip().title() + return last_name + + @computed_field + def full_name(self) -> str: + """This will return full name of an employee""" + return f"{self.first_name} {self.last_name}" + + @computed_field + def annual_salary(self) -> float: + """This will return annual salary of an employee""" + return self.monthly_salary * 12 + + def change_first_name(self, first_nm: str) -> None: + self.first_name = first_nm.strip().title() + return None + + +try: + emp1: Employee = Employee( + firstName="tejas", + lastName="dixit", + monthlySalary=25000, + ) + + print(emp1.model_dump()) +except ValidationError as exc: + print(exc) + +try: + emp2: Employee = Employee( + first_name="swaroop", + last_name="dixit", + monthly_salary=20000, + ) + print(emp2.model_dump()) +except ValidationError as exc: + print(exc) diff --git a/python_fundamentals/exercises/day_23_str_enum/employee.py b/python_fundamentals/exercises/day_23_str_enum/employee.py new file mode 100644 index 0000000..191675d --- /dev/null +++ b/python_fundamentals/exercises/day_23_str_enum/employee.py @@ -0,0 +1,129 @@ +from enum import StrEnum + +from pydantic import BaseModel, EmailStr, Field, field_validator + +""" + +Understand why StrEnum is used ? + + +Problem: + + - department : str = " HR" + - department : str = "hr" + - department : str = "Human Resource" + +We know the Fixed Department and so we can resolve the same issue with StrEnum which +follow the consistancy for redundant but fixed values. + +now StrEnum will gives JSON friendly ouput for department +but after serialization using `.model_dump_json()` + +""" + + +class Department(StrEnum): + ADMIN = "ADMIN" + TECH = "TECH" + DATA = "DATA" + CONTENT = "CONTENT" + + +class Address(BaseModel): + city: str | None = Field( + default=None, + min_length=3, + max_length=10, + ) + pincode: str | None = Field(default=None, min_length=6, max_length=6) + + +class Employee(BaseModel): + """ + This class represent an Employee of a Company + """ + + emp_name: str = Field(min_length=3, max_length=60) + emp_id: str = Field(min_length=7, max_length=20) + email: EmailStr | None = None + department: Department + address: Address + + @field_validator("emp_name") + @classmethod + def transform_employee_name(cls, emp_name: str) -> str: + """ + This method transforms emp_name and validates it + removes whitespace and save employee name in title case + """ + emp_name = emp_name.strip().title() + + if emp_name.isalpha(): + raise ValueError( + f"Employee name has digits in it. \ + Employee name must be pure string and not contains digit. \ + you have entered {emp_name}" + ) + return emp_name + + @field_validator("emp_id") + @classmethod + def transform_and_validate_employee_id(cls, emp_id: str) -> str: + """ + This method helps to transforms and validate the employee id + - Removes the whitespaces from emp_id + - Capitalize the emp_id + - Checks wether it strats with EMP- + """ + emp_id = emp_id.strip().upper() + + if not emp_id.startswith("EMP-"): + raise ValueError("Employee id must starts with EMP- ") + + if emp_id.isalnum(): + raise ValueError("Employee id must contain digits") + + return emp_id + + +class Company(BaseModel): + """ + This Class represents company + """ + + company_name: str = Field(min_length=3, max_length=50) + employees: list[Employee] + + @field_validator("company_name") + @classmethod + def transform_company_name(cls, company_name: str) -> str: + company_name = company_name.strip().title() + return company_name + + +tejas_dixit: Employee = Employee( + emp_name=" tejas dixit ", + emp_id=" emp-108 ", + email="tejasdixit17@gmail.com", + address=Address(city="Pune", pincode="411028"), + department=Department.TECH, +) + +aniket_jagadale: Employee = Employee( + emp_name=" aniket Jagadale", + emp_id="EmP-107 ", + department=Department.CONTENT, + address=Address(), +) + + +media_vidya: Company = Company( + company_name=" media VIdya pvt ltd", employees=[tejas_dixit, aniket_jagadale] +) + + +print(media_vidya.model_dump()) # prints normal model with python object for Department +print(media_vidya.model_dump_json()) # heare we can see department values pydantic +# serialize the output using model_dump_json() +# its API friendly and help when working with +# other languages. diff --git a/python_fundamentals/exercises/nested_models/nested_models.py b/python_fundamentals/exercises/nested_models/nested_models.py new file mode 100644 index 0000000..68599da --- /dev/null +++ b/python_fundamentals/exercises/nested_models/nested_models.py @@ -0,0 +1,106 @@ +from enum import StrEnum + +from pydantic import BaseModel, EmailStr, Field + +# Company has Company Name and company has address and employees. + + +class Address(BaseModel): + """This model represnts the Address""" + + city: str + pincode: str = Field(min_length=6, max_length=6) + state: str + + +class Employee(BaseModel): + """This Model Represents the an Employee""" + + employee_id: str + name: str = Field(max_length=20, min_length=0) + email: EmailStr + address: Address + + +class Company(BaseModel): + comany_name: str = Field(min_length=3, max_length=50) + address: Address + employees: list[Employee] + + +emp_1: Employee = Employee( + employee_id="EMP-101", + name="Tejas Dixit", + email="tejasdixit17@gmail.com", + address=Address(city="Pune", pincode="411028", state="Maharashtra"), +) +emp_2: Employee = Employee( + employee_id="EMP-102", + name="Swaroop Dixit", + email="swaroopdixit17@gmail.com", + address=Address(city="Pune", pincode="411028", state="Maharashtra"), +) + +media_vidya: Company = Company( + comany_name="Media Vidya", + address=Address(city="Pune", pincode="411028", state="Maharashtra"), + employees=[emp_1, emp_2], +) + + +class Department(StrEnum): + HR = "HR" + ADMIN = "ADMIN" + MARKETING = "MARKETING" + OPERATIONS = "OPERATIONS" + + +class TestEmployee(BaseModel): + """ + This class represents Test Employee + """ + + employee_id: str = Field(min_length=7, max_length=7) + first_name: str = Field(min_length=3, max_length=10) + middle_name: str | None = None + last_name: str = Field(min_length=3, max_length=10) + email: EmailStr + secondary_email: EmailStr | None = None + phone_number: str | None = None + linkedin_url: str | None = None + department: Department + + +try: + test_employee: TestEmployee = TestEmployee( + employee_id="EMP-108", + first_name="Tejas", + middle_name="Vinaykant", + last_name="Dixit", + email="tejasdixit17@gmail.com", + department=Department.ADMIN, + ) + + print(test_employee.model_dump()) + print(test_employee.department) + print(type(test_employee.department)) + print(test_employee.department == "ADMIN") + print(test_employee.model_dump_json()) + +except ValueError as exc: + print(exc) + + +try: + test_employee_2: TestEmployee = TestEmployee( + employee_id="EMP-102", + first_name="Vignesh", + last_name="Gawali", + email="vbg3008@gmail.com", + department="SALES", + ) + + print(test_employee_2.model_dump()) + +except ValueError as exc: + print(exc) diff --git a/python_fundamentals/notes/day23-str-enum.md b/python_fundamentals/notes/day23-str-enum.md new file mode 100644 index 0000000..91b4d4d --- /dev/null +++ b/python_fundamentals/notes/day23-str-enum.md @@ -0,0 +1,239 @@ +# Day 23 — StrEnum + +## Goal + +Understand why `StrEnum` is used for business fields with a fixed set of values and why it is preferred for JSON APIs. + +--- + +## Business Problem + +Using `str` allows inconsistent values. + +```python +department = "HR" +department = "hr" +department = "Human Resource" +department = "Human Resources" +``` + +Although they represent the same department, they produce inconsistent data. + +--- + +## Solution + +Use `StrEnum` when a business field has a predefined set of valid values. + +```python +from enum import StrEnum + +class Department(StrEnum): + ADMIN = "ADMIN" + TECH = "TECH" + DATA = "DATA" + CONTENT = "CONTENT" +``` + +Now only the declared values are accepted. + +--- + +## Syntax + +```python +from enum import StrEnum + +class Department(StrEnum): + ADMIN = "ADMIN" + TECH = "TECH" +``` + +Use inside a model: + +```python +department: Department +``` + +--- + +## Business Use Cases + +- Department +- User Role +- Order Status +- Payment Status +- Task Status +- Account Type + +--- + +## Rules + +- Use `StrEnum` only for fixed business values. +- Do not use `str` when only predefined values are allowed. +- Prefer `StrEnum` over `Enum` for API models. +- `StrEnum` improves consistency and prevents invalid values. + +--- + +## Validation + +Valid: + +```python +department = Department.TECH +``` + +or + +```python +department = "TECH" +``` + +Invalid: + +```python +department = "SALES" +``` + +Result: + +``` +ValidationError +``` + +--- + +## Output + +Python representation: + +```python +employee.model_dump() +``` + +Output: + +```python +{ + "department": +} +``` + +JSON representation: + +```python +employee.model_dump_json() +``` + +Output: + +```json +{ + "department": "TECH" +} +``` + +--- + +## Common Mistakes + +### Wrong + +```python +department: str +``` + +### Correct + +```python +department: Department +``` + +--- + +### Wrong + +```python +Department.SALES +``` + +Raises: + +``` +AttributeError +``` + +Reason: + +`SALES` is not a member of the enum. + +--- + +### Wrong + +```python +department="SALES" +``` + +Raises: + +``` +ValidationError +``` + +Reason: + +Pydantic validates the input against the allowed enum values. + +--- + +## Key Takeaways + +- `StrEnum` represents a fixed set of business values. +- It prevents inconsistent string values. +- It serializes cleanly to JSON. +- It is preferred for FastAPI request and response models. + +--- + +## Hansei + +### Today I Learned + +- Why business-controlled fields should use `StrEnum`. +- Difference between `Enum` and `StrEnum`. +- Difference between `AttributeError` and `ValidationError`. + +### Mistakes I Made + +- Expected `Department.SALES` to raise a `ValidationError`. + +### Why It Happened + +- Python evaluates enum members before Pydantic validation. + +### Improvement + +- Distinguish between Python runtime errors and Pydantic validation errors. + +--- + +## Interview Questions + +1. Why should `Department` be modeled as a `StrEnum` instead of a `str`? +2. What is the difference between `Enum` and `StrEnum`? +3. Why does `Department.SALES` raise an `AttributeError`? +4. When does Pydantic raise a `ValidationError` for an enum? +5. Why is `StrEnum` preferred in FastAPI applications? + +--- + +## Summary + +- Fixed business values → `StrEnum` +- Free text → `str` +- Python validates enum members. +- Pydantic validates input values. +- `model_dump_json()` produces API-friendly JSON. From 0f7087fc14c9c7973311ec98d52bb9ac634f3b52 Mon Sep 17 00:00:00 2001 From: "MR. TEJAS DIXIT" Date: Sat, 18 Jul 2026 02:19:41 +0530 Subject: [PATCH 03/10] fix: Add SALES to Department enum and use valid enum member --- python_fundamentals/exercises/nested_models/nested_models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python_fundamentals/exercises/nested_models/nested_models.py b/python_fundamentals/exercises/nested_models/nested_models.py index 68599da..bfd1265 100644 --- a/python_fundamentals/exercises/nested_models/nested_models.py +++ b/python_fundamentals/exercises/nested_models/nested_models.py @@ -53,6 +53,7 @@ class Department(StrEnum): ADMIN = "ADMIN" MARKETING = "MARKETING" OPERATIONS = "OPERATIONS" + SALES = "SALES" class TestEmployee(BaseModel): @@ -97,7 +98,7 @@ class TestEmployee(BaseModel): first_name="Vignesh", last_name="Gawali", email="vbg3008@gmail.com", - department="SALES", + department=Department.SALES, ) print(test_employee_2.model_dump()) From d89f14eda992004a4aca8fe675decb1386ed8eb8 Mon Sep 17 00:00:00 2001 From: "MR. TEJAS DIXIT" Date: Sat, 18 Jul 2026 02:20:10 +0530 Subject: [PATCH 04/10] fix: Add type: ignore comments for snake_case field names with populate_by_name --- python_fundamentals/exercises/day18-field-alias.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python_fundamentals/exercises/day18-field-alias.py b/python_fundamentals/exercises/day18-field-alias.py index a65b401..10e7657 100644 --- a/python_fundamentals/exercises/day18-field-alias.py +++ b/python_fundamentals/exercises/day18-field-alias.py @@ -76,9 +76,9 @@ def change_first_name(self, first_nm: str) -> None: try: emp2: Employee = Employee( - first_name="swaroop", - last_name="dixit", - monthly_salary=20000, + first_name="swaroop", # type: ignore + last_name="dixit", # type: ignore + monthly_salary=20000, # type: ignore ) print(emp2.model_dump()) except ValidationError as exc: From a5eec3fc61bb0d2d4a596b125d74ece9a91acb65 Mon Sep 17 00:00:00 2001 From: Tejas Dixit Date: Wed, 22 Jul 2026 01:24:53 +0530 Subject: [PATCH 05/10] day_24_decimal/product_invoice.py --- .../day_24_decimal/product_invoice.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 python_fundamentals/exercises/day_24_decimal/product_invoice.py diff --git a/python_fundamentals/exercises/day_24_decimal/product_invoice.py b/python_fundamentals/exercises/day_24_decimal/product_invoice.py new file mode 100644 index 0000000..9b69c88 --- /dev/null +++ b/python_fundamentals/exercises/day_24_decimal/product_invoice.py @@ -0,0 +1,46 @@ +from decimal import Decimal + +from pydantic import BaseModel, Field, computed_field + + +class Product(BaseModel): + name: str = Field(min_length=3, max_length=20) + price: Decimal + quantity: int + + @computed_field + def subtotal(self) -> Decimal: + return self.price * self.quantity + + +class Invoice(BaseModel): + invoice_id: str + customer_name: str = Field(min_length=3, max_length=20) + products: list[Product] + gst_percentage: Decimal + + @computed_field + def subtotal_total(self) -> Decimal: + total: Decimal = Decimal("0") + for product in self.products: + total += product.subtotal + return total + + @computed_field + def grand_total(self) -> Decimal: + subtotal: Decimal = self.subtotal_total + gst_amount = subtotal * (self.gst_percentage / 100) + return Decimal(subtotal + gst_amount) + + +p1: Product = Product(name="Sugar", price=Decimal("20.00"), quantity=1) +p2: Product = Product(name="Oil", price=Decimal("120.00"), quantity=2) + +invoice: Invoice = Invoice( + invoice_id="123", + customer_name="Tejas Dixit", + products=[p1, p2], + gst_percentage=Decimal("18"), +) + +print(invoice.model_dump()) From 8b0f83622ca7fddd2e8874d627a1297a6f3838ca Mon Sep 17 00:00:00 2001 From: Tejas Dixit Date: Wed, 22 Jul 2026 02:42:43 +0530 Subject: [PATCH 06/10] feat(pydantic): add date and datetime models --- .../exercises/day_25_date_datetime/README.md | 74 ++++++ .../day_25_date_datetime/employee.py | 35 +++ .../notes/day25-date-datetime.md | 220 ++++++++++++++++++ 3 files changed, 329 insertions(+) create mode 100644 python_fundamentals/exercises/day_25_date_datetime/README.md create mode 100755 python_fundamentals/exercises/day_25_date_datetime/employee.py create mode 100644 python_fundamentals/notes/day25-date-datetime.md diff --git a/python_fundamentals/exercises/day_25_date_datetime/README.md b/python_fundamentals/exercises/day_25_date_datetime/README.md new file mode 100644 index 0000000..8211e2f --- /dev/null +++ b/python_fundamentals/exercises/day_25_date_datetime/README.md @@ -0,0 +1,74 @@ +# Day 25 — Date & DateTime + +## Objective + +Understand when to use `date` and `datetime` in Pydantic models and how Pydantic parses ISO formatted strings into Python objects. + +--- + +## Concepts Covered + +- `date` +- `datetime` +- Pydantic parsing +- `ValidationError` +- `model_dump()` +- `model_dump_json()` + +--- + +## Business Use Cases + +### `date` + +- Employee joining date +- Date of birth +- Manufacturing date + +### `datetime` + +- User login +- Payment timestamp +- Audit logs +- Record creation time + +--- + +## Key Learning + +- Use `date` when only the calendar date is required. +- Use `datetime` when both date and time are required. +- Python validates `date()` and `datetime()` constructors. +- Pydantic parses strings into `date` and `datetime` objects. +- Invalid parsed values raise `ValidationError`. + +--- + +## Run + +```bash +uv run python employee.py +``` + +--- + +## Expected Output + +- Successful creation of a valid `Employee`. +- `model_dump()` returns Python objects. +- `model_dump_json()` returns JSON. +- Invalid input raises `ValidationError`. + +--- + +## Files + +``` +employee.py +``` + +--- + +## Status + +✅ Completed diff --git a/python_fundamentals/exercises/day_25_date_datetime/employee.py b/python_fundamentals/exercises/day_25_date_datetime/employee.py new file mode 100755 index 0000000..1ee8d0f --- /dev/null +++ b/python_fundamentals/exercises/day_25_date_datetime/employee.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3.12 +from datetime import date, datetime + +from pydantic import BaseModel, Field, ValidationError + + +class Employee(BaseModel): + emp_name: str = Field(min_length=3, max_length=20) + joining_date: date + created_at: datetime + + +try: + tejas_dixit: Employee = Employee( + emp_name="Tejas Dixit", + joining_date=date(year=2024, month=1, day=29), + created_at=datetime(year=2024, month=1, day=29, hour=13, minute=00, second=20), + ) + + print(f"Python Representaion of Employee Object: {tejas_dixit.model_dump()}") + print(f"Pydantic Representaion of Employee Object: {tejas_dixit.model_dump_json()}") + + vignesh_gawali: Employee = Employee( + emp_name="Vignesh Gawali", + joining_date="2024-12-12", # Intentional: testing Pydantic parsing + created_at="0000-12-12 12:12", # Intentional: testing Pydantic parsing + ) + + print(f"Python Representaion of Employee Object: {vignesh_gawali.model_dump()}") + print( + f"Pydantic Representaion of Employee Object: {vignesh_gawali.model_dump_json()}" + ) + +except (ValueError, ValidationError) as exc: + print(exc) diff --git a/python_fundamentals/notes/day25-date-datetime.md b/python_fundamentals/notes/day25-date-datetime.md new file mode 100644 index 0000000..a6a3727 --- /dev/null +++ b/python_fundamentals/notes/day25-date-datetime.md @@ -0,0 +1,220 @@ +# Day 25 — Date & DateTime + +## Goal + +Understand when to use `date` and `datetime` in Pydantic models and how Pydantic validates and parses them. + +--- + +## Business Problem + +Using `str` for dates allows inconsistent formats. + +```python +"21/07/2026" +"21-07-2026" +"July 21, 2026" +"2026-07-21" +``` + +Business applications require a consistent and validated date format. + +--- + +## Solution + +Use Python's `date` and `datetime` types. + +```python +from datetime import date, datetime + +joining_date: date +created_at: datetime +``` + +Pydantic automatically parses valid ISO formatted strings into Python objects. + +--- + +## Syntax + +```python +from datetime import date, datetime + +class Employee(BaseModel): + joining_date: date + created_at: datetime +``` + +--- + +## Business Use Cases + +### Use `date` + +- Employee joining date +- Date of birth +- Manufacturing date +- Invoice due date + +### Use `datetime` + +- User login +- Payment timestamp +- Order creation +- Audit logs +- Record creation time + +--- + +## Validation + +### Python Validation + +```python +date(year=2024, month=13, day=1) +``` + +Result: + +``` +ValueError +``` + +Reason: + +Python validates the `date()` constructor before Pydantic receives the value. + +--- + +### Pydantic Validation + +```python +Employee( + joining_date="2024-13-01", +) +``` + +Result: + +``` +ValidationError +``` + +Reason: + +Pydantic parses the string into a `date`. Invalid values raise a `ValidationError`. + +--- + +## Validation Ownership + +| Layer | Responsibility | +|--------|----------------| +| Python | Validates `date()` and `datetime()` constructors | +| Pydantic | Parses raw input into Python `date` and `datetime` objects | +| Business Validators | Business-specific rules (future lessons) | + +--- + +## Common Mistakes + +### Wrong + +```python +joining_date: str +``` + +### Correct + +```python +joining_date: date +``` + +--- + +### Wrong + +```python +created_at: str +``` + +### Correct + +```python +created_at: datetime +``` + +--- + +### Wrong + +Expecting: + +```python +date(year=2024, month=13, day=1) +``` + +to raise: + +``` +ValidationError +``` + +Actual: + +``` +ValueError +``` + +--- + +## Key Takeaways + +- `date` stores only the calendar date. +- `datetime` stores both date and time. +- Business requirements determine whether to use `date` or `datetime`. +- Python validates constructed `date` and `datetime` objects. +- Pydantic parses raw input and raises `ValidationError` when parsing fails. + +--- + +## Hansei + +### Today I Learned + +- Difference between `date` and `datetime`. +- Difference between Python validation and Pydantic validation. +- Pydantic parses ISO formatted strings automatically. + +### Mistakes I Made + +Initially expected invalid `date()` construction to raise a `ValidationError`. + +### Why It Happened + +I did not distinguish between Python runtime validation and Pydantic model validation. + +### Improvement + +Before debugging, identify which layer owns the validation. + +--- + +## Interview Questions + +1. When should you use `date` instead of `datetime`? +2. Why should dates not be stored as `str`? +3. What is the difference between `ValueError` and `ValidationError`? +4. How does Pydantic handle ISO formatted date strings? +5. Which layer owns the validation of `date()`? + +--- + +## Summary + +- Business requirement decides `date` vs `datetime`. +- Python validates constructors. +- Pydantic parses and validates input. +- Use `date` for calendar dates. +- Use `datetime` when time is required. From 6767002a11763bba2af93f388a3dae226cc7b6dc Mon Sep 17 00:00:00 2001 From: Tejas Dixit Date: Thu, 23 Jul 2026 04:47:55 +0530 Subject: [PATCH 07/10] feat(model_dump) understand how to serialize model using include, exclude --- .../employee.py | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 python_fundamentals/exercises/day_26_model_dump_serialization/employee.py diff --git a/python_fundamentals/exercises/day_26_model_dump_serialization/employee.py b/python_fundamentals/exercises/day_26_model_dump_serialization/employee.py new file mode 100644 index 0000000..284f571 --- /dev/null +++ b/python_fundamentals/exercises/day_26_model_dump_serialization/employee.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3.12 + +# model_dump() serializes a validated Pydantic model into a Python dictionary. +# Prefer model_dump() over __dict__ because it supports Pydantic features +# such as include, exclude, aliases and serializers. + + +from datetime import date, datetime + +from pydantic import BaseModel, Field, ValidationError + + +class Employee(BaseModel): + """This Model Represents and Employee of an Companay""" + + emp_name: str = Field(min_length=3, max_length=20) + joining_date: date + created_at: datetime + + +def format_emp() -> None: + print( + "-" * 120, + end="\n", + ) + + +try: + emp: Employee = Employee( + emp_name="Tejas Dixit", + joining_date=date(year=2024, month=1, day=29), + created_at=datetime(year=2024, month=1, day=25, hour=9, minute=30), + ) + + print( + f"HR View: {emp.model_dump(include={'emp_name', 'joining_date'})}" + ) # exclude `created_at` field. + + print( + f"ADMIN View: {emp.model_dump(exclude={'joining_date'})}" + ) # excludes `joining_date` and include each field. + + format_emp() + + another_emp: Employee = Employee( + emp_name="Tejas Dixit", + joining_date="2024-12-29", + # Intentional: Pydantic will conert it into date format + created_at="2024-12-28 12:12", + # Intentional: Pydantic model will convert it into datetime format + ) + + print( + f"HR View: {another_emp.model_dump(include={'emp_name', 'joining_date'})}" + ) # exclude `created_at` field. + + print(f"ADMIN View: {another_emp.model_dump(exclude={'joining_date'})}") + + +except (ValueError, ValidationError) as exc: + print(exc) From 38671169fbc1f0e9c3981ca6615d4e003b317aaa Mon Sep 17 00:00:00 2001 From: Tejas Dixit Date: Thu, 23 Jul 2026 04:53:15 +0530 Subject: [PATCH 08/10] docs(Add Why model_dump is used and How to use) --- .../day_26_model_dump_serialization/README.md | 66 +++++++ python_fundamentals/notes/day26-model-dump.md | 183 ++++++++++++++++++ 2 files changed, 249 insertions(+) create mode 100644 python_fundamentals/exercises/day_26_model_dump_serialization/README.md create mode 100644 python_fundamentals/notes/day26-model-dump.md diff --git a/python_fundamentals/exercises/day_26_model_dump_serialization/README.md b/python_fundamentals/exercises/day_26_model_dump_serialization/README.md new file mode 100644 index 0000000..e21f7ad --- /dev/null +++ b/python_fundamentals/exercises/day_26_model_dump_serialization/README.md @@ -0,0 +1,66 @@ +# Day 26 — model_dump() + +## Objective + +Understand how `model_dump()` serializes a validated Pydantic model into a Python dictionary and how to control serialized output using `include` and `exclude`. + +--- + +## Concepts Covered + +- `model_dump()` +- Serialization +- `include` +- `exclude` +- Business Views + +--- + +## Business Use Cases + +- HR View +- Admin View +- Public Profile +- API Response +- Logging + +--- + +## Key Learning + +- `model_dump()` serializes a validated Pydantic model. +- It returns a standard Python dictionary. +- Use `include` to serialize only required fields. +- Use `exclude` to omit sensitive or unnecessary fields. +- One model can produce multiple business-specific representations. + +--- + +## Run + +```bash +uv run python employee.py +``` + +--- + +## Expected Output + +- Python dictionary representation of the model. +- HR View containing selected fields. +- Admin View excluding selected fields. +- Automatic parsing of ISO formatted strings into Python objects. + +--- + +## Files + +``` +employee.py +``` + +--- + +## Status + +✅ Completed diff --git a/python_fundamentals/notes/day26-model-dump.md b/python_fundamentals/notes/day26-model-dump.md new file mode 100644 index 0000000..0f025cf --- /dev/null +++ b/python_fundamentals/notes/day26-model-dump.md @@ -0,0 +1,183 @@ +# Day 26 — model_dump() + +## Goal + +Understand how `model_dump()` serializes a validated Pydantic model and why serialization is important for different business views. + +--- + +## Business Problem + +Different consumers require different representations of the same object. + +Example: + +### HR + +- Employee Name +- Joining Date + +### Admin + +- Employee Name +- Created At + +### Public Profile + +- Employee Name + +The business object remains the same, but the serialized output changes based on business requirements. + +--- + +## Solution + +Use `model_dump()` to serialize a validated Pydantic model into a Python dictionary. + +```python +employee.model_dump() +``` + +--- + +## Syntax + +### Serialize Entire Model + +```python +employee.model_dump() +``` + +### Include Fields + +```python +employee.model_dump( + include={"emp_name", "joining_date"} +) +``` + +### Exclude Fields + +```python +employee.model_dump( + exclude={"created_at"} +) +``` + +--- + +## Business Use Cases + +- API responses +- HR dashboard +- Admin dashboard +- Public profile +- Logging +- Exporting reports + +--- + +## Serialization Flow + +```text +Raw Input + ↓ +Pydantic Validation + ↓ +Employee Model + ↓ +model_dump() + ↓ +Python Dictionary +``` + +--- + +## Rules + +- `model_dump()` serializes a validated model. +- Validation happens during model creation, not during serialization. +- One model can have multiple serialized representations. +- Prefer `model_dump()` over `__dict__` for Pydantic models. + +--- + +## Common Mistakes + +### Wrong + +Thinking `model_dump()` validates the model. + +### Correct + +Validation occurs during model creation. + +`model_dump()` only serializes the existing model. + +--- + +### Wrong + +```python +employee.__dict__ +``` + +for application serialization. + +### Correct + +```python +employee.model_dump() +``` + +--- + +## Key Takeaways + +- Serialization converts a model into a dictionary. +- Business requirements determine which fields are serialized. +- `include` selects specific fields. +- `exclude` hides specific fields. +- Serialization and validation are different stages. + +--- + +## Hansei + +### Today I Learned + +- Difference between validation and serialization. +- One model can produce multiple business views. +- `model_dump()` returns a Python dictionary. + +### Mistakes I Made + +Initially thought `model_dump()` performed validation. + +### Why It Happened + +I associated every Pydantic method with validation. + +### Improvement + +Separate model creation from model representation. + +--- + +## Interview Questions + +1. What is serialization? +2. What does `model_dump()` return? +3. What is the difference between validation and serialization? +4. Why is `model_dump()` preferred over `__dict__`? +5. When should `include` and `exclude` be used? + +--- + +## Summary + +- `model_dump()` serializes a validated Pydantic model. +- It returns a standard Python dictionary. +- Use `include` for selected fields. +- Use `exclude` to hide fields. +- The same model can produce multiple business-specific views. From a738cb603f11c365ed8c228cdb95ae4b9790d141 Mon Sep 17 00:00:00 2001 From: Tejas Dixit Date: Sun, 26 Jul 2026 01:59:52 +0530 Subject: [PATCH 09/10] feat(field_serializer) lesson --- .../exercises/employee_field_serializer.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 python_fundamentals/exercises/employee_field_serializer.py diff --git a/python_fundamentals/exercises/employee_field_serializer.py b/python_fundamentals/exercises/employee_field_serializer.py new file mode 100644 index 0000000..1c3ddba --- /dev/null +++ b/python_fundamentals/exercises/employee_field_serializer.py @@ -0,0 +1,27 @@ +from datetime import date, datetime +from decimal import Decimal + +from pydantic import BaseModel, field_serializer + + +class Employee(BaseModel): + name: str + salary: Decimal + joining_date: date + + @field_serializer("salary") + def salary_in_dollars(self, salary: Decimal) -> str: + return f"${salary}" + + @field_serializer("joining_date") + def format_date(self, date: datetime) -> str: + return date.strftime("%d %b %Y") + + +emp1: Employee = Employee( + name="Tejas Dixit", + salary=Decimal(25000), + joining_date=date(year=2024, month=1, day=29), +) + +print(emp1.model_dump()) From 6e50c47e1ac976134fd9f3b097126b2e0573b316 Mon Sep 17 00:00:00 2001 From: Tejas Dixit Date: Tue, 28 Jul 2026 02:53:53 +0530 Subject: [PATCH 10/10] capsote project --- .../__init__.py | 0 .../company.py | 107 ++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 python_fundamentals/projects/capstone_01_employee_management_system/__init__.py create mode 100644 python_fundamentals/projects/capstone_01_employee_management_system/company.py diff --git a/python_fundamentals/projects/capstone_01_employee_management_system/__init__.py b/python_fundamentals/projects/capstone_01_employee_management_system/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python_fundamentals/projects/capstone_01_employee_management_system/company.py b/python_fundamentals/projects/capstone_01_employee_management_system/company.py new file mode 100644 index 0000000..9a6d8dc --- /dev/null +++ b/python_fundamentals/projects/capstone_01_employee_management_system/company.py @@ -0,0 +1,107 @@ +from datetime import date, datetime +from decimal import Decimal +from enum import StrEnum +from pprint import pprint + +from pydantic import ( + BaseModel, + EmailStr, + Field, + computed_field, + field_serializer, + field_validator, +) + + +class AllowedState(StrEnum): + MAHARASHTRA = "MAHARASHTRA" + UP = "UTTARPRADESH" + MP = "MADHYAPRADESH" + KARNATAKA = "KARNATAKA" + + +class Address(BaseModel): + city: str = Field(min_length=3, max_length=20) + pincode: str = Field(min_length=6, max_length=6) + state: AllowedState + + +class AllowedDepartment(StrEnum): + CONTENT = "CONTENT" + ADMIN = "ADMIN" + TECHNICAL = "TECHNICAL" + DATA = "DATA" + + +class Employee(BaseModel): + emp_name: str = Field(min_length=3, max_length=30) + email: EmailStr | None = None + department: AllowedDepartment + salary: Decimal + address: Address + joining_date: date + created_at: datetime + + @field_validator("emp_name") + def _validate_format_emp_name(cls, emp_name: str) -> str: + words = emp_name.strip().split() + emp_name = " ".join(words).title() + for char in emp_name: + if not (char.isalpha() or char.isspace() or char in ("-", "'")): + raise ValueError( + "Employee name must only contain letters, \ + spaces, hyphens, or apostrophes." + ) + return emp_name + + @field_serializer("salary") + def salary_in_dollars(self, salary: Decimal) -> str: + return f"${salary}" + + @computed_field + def annual_salary(self) -> Decimal: + return 12 * self.salary + + @field_serializer("annual_salary") + def _format_annual_salary(self, annual_salary: Decimal) -> str: + return f"${annual_salary}" + + @field_serializer("joining_date") + def format_date_content_dept(self, joining_date: date) -> str: + return str(joining_date.strftime(format="%d %b %Y")) + + +class Company(BaseModel): + address: Address + employees: list[Employee] + + +tejas_dixit: Employee = Employee( + emp_name=" teJaS diXit ", + email="tejasdixit17@zohomail.in", + department=AllowedDepartment.CONTENT, + salary=Decimal("25000"), + joining_date=date(year=2024, month=1, day=29), + created_at=datetime(year=2024, month=1, day=28, hour=20, minute=12), + address=Address(city="Pune", pincode="411028", state=AllowedState.MAHARASHTRA), +) + + +vignesh_gawali: Employee = Employee( + emp_name=" Vignesh Gawali ", + department=AllowedDepartment.TECHNICAL, + salary=Decimal("25000"), + joining_date=date(year=2024, month=1, day=29), + created_at=datetime(year=2024, month=1, day=28, hour=20, minute=30, second=56), + address=Address(city="Pune", pincode="411005", state=AllowedState.MAHARASHTRA), +) + + +company_address: Address = Address( + city="Bengluru", pincode="311098", state=AllowedState.KARNATAKA +) + + +tcs: Company = Company(address=company_address, employees=[tejas_dixit, vignesh_gawali]) + +pprint(tcs.model_dump())