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()) 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/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/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) 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()) 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. 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. 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())