Practical Patterns¶
Real-world dataclass patterns that solve common problems: configuration objects, API models, and domain entities.
Configuration Objects¶
from dataclasses import dataclass, field
from typing import Dict, List
@dataclass
class DatabaseConfig:
host: str
port: int = 5432
username: str = "admin"
password: str = field(repr=False) # Don't show in repr
options: Dict[str, str] = field(default_factory=dict)
@property
def connection_string(self) -> str:
return f"postgres://{self.username}@{self.host}:{self.port}"
config = DatabaseConfig("localhost", password="secret")
print(f"Connecting to {config.connection_string}")
API Request/Response Models¶
from dataclasses import dataclass, asdict
from typing import Optional
from datetime import datetime
@dataclass
class User:
id: int
name: str
email: str
created_at: datetime = None
def __post_init__(self):
if self.created_at is None:
self.created_at = datetime.now()
def to_dict(self):
return asdict(self)
user = User(1, "Alice", "alice@example.com")
print(user.to_dict())
Domain Model with Validation¶
from dataclasses import dataclass
from enum import Enum
class OrderStatus(Enum):
PENDING = "pending"
SHIPPED = "shipped"
DELIVERED = "delivered"
@dataclass
class Order:
order_id: str
customer: str
amount: float
status: OrderStatus = OrderStatus.PENDING
def __post_init__(self):
if self.amount <= 0:
raise ValueError("Amount must be positive")
if not self.order_id:
raise ValueError("Order ID required")
def ship(self):
if self.status != OrderStatus.PENDING:
raise ValueError("Can only ship pending orders")
self.status = OrderStatus.SHIPPED
def deliver(self):
if self.status != OrderStatus.SHIPPED:
raise ValueError("Can only deliver shipped orders")
self.status = OrderStatus.DELIVERED
order = Order("ORD-001", "Alice", 99.99)
order.ship()
order.deliver()
print(f"Order {order.order_id}: {order.status.value}")
Nested Dataclasses¶
from dataclasses import dataclass
from typing import List
@dataclass
class Address:
street: str
city: str
country: str
@dataclass
class Contact:
email: str
phone: str
@dataclass
class Company:
name: str
address: Address
contact: Contact
employees: int = 0
company = Company(
name="TechCorp",
address=Address("123 Main St", "San Francisco", "USA"),
contact=Contact("info@techcorp.com", "555-1234"),
employees=50
)
print(f"{company.name} in {company.address.city}")
Builder Pattern with Dataclasses¶
from dataclasses import dataclass, field
@dataclass
class QueryBuilder:
table: str
where_conditions: list = field(default_factory=list)
select_fields: list = field(default_factory=lambda: ["*"])
limit_value: int = None
def where(self, condition: str):
self.where_conditions.append(condition)
return self
def select(self, *fields):
self.select_fields = list(fields)
return self
def limit(self, count: int):
self.limit_value = count
return self
def build(self) -> str:
query = f"SELECT {', '.join(self.select_fields)} FROM {self.table}"
if self.where_conditions:
query += " WHERE " + " AND ".join(self.where_conditions)
if self.limit_value:
query += f" LIMIT {self.limit_value}"
return query
query = (QueryBuilder("users")
.select("id", "name", "email")
.where("age > 18")
.where("country = 'USA'")
.limit(10)
.build())
print(query)
Data Validation with post_init¶
from dataclasses import dataclass, field
from typing import List
@dataclass
class EmailList:
emails: List[str] = field(default_factory=list)
def __post_init__(self):
# Validate and normalize emails
validated = []
for email in self.emails:
if '@' in email:
validated.append(email.lower())
self.emails = validated
def add_email(self, email: str):
if '@' in email:
self.emails.append(email.lower())
email_list = EmailList(["Alice@Example.com", "Bob@Test.com"])
print(email_list.emails) # ['alice@example.com', 'bob@test.com']