CreationalPythonverifiedVerified
Builder Pattern in Python
Separates the construction of a complex object from its representation, allowing the same construction process to produce different results.
How to Implement the Builder Pattern in Python
1Step 1: Define the product dataclass
from dataclasses import dataclass, field
@dataclass
class House:
walls: int = 0
doors: int = 0
windows: int = 0
has_garage: bool = False
has_pool: bool = False
def describe(self) -> str:
features = [
f"{self.walls} walls",
f"{self.doors} doors",
f"{self.windows} windows",
*(["garage"] if self.has_garage else []),
*(["pool"] if self.has_pool else []),
]
return f"House with {', '.join(features)}"2Step 2: Create the Builder with a fluent API
class HouseBuilder:
def __init__(self) -> None:
self._house = House()
def reset(self) -> "HouseBuilder":
self._house = House()
return self
def set_walls(self, n: int) -> "HouseBuilder":
self._house.walls = n
return self
def set_doors(self, n: int) -> "HouseBuilder":
self._house.doors = n
return self
def set_windows(self, n: int) -> "HouseBuilder":
self._house.windows = n
return self
def set_garage(self, v: bool) -> "HouseBuilder":
self._house.has_garage = v
return self
def set_pool(self, v: bool) -> "HouseBuilder":
self._house.has_pool = v
return self
def build(self) -> House:
result = self._house
self.reset()
return result3Step 3: Add a Director for preset configurations
class Director:
def build_minimal(self, b: HouseBuilder) -> House:
return b.reset().set_walls(4).set_doors(1).set_windows(2).build()
def build_luxury(self, b: HouseBuilder) -> House:
return (
b.reset()
.set_walls(8).set_doors(4).set_windows(12)
.set_garage(True).set_pool(True)
.build()
)4Step 4: Build houses with and without a director
builder = HouseBuilder()
director = Director()
print(director.build_minimal(builder).describe())
print(director.build_luxury(builder).describe())
custom = builder.set_walls(6).set_doors(2).set_windows(8).set_pool(True).build()
print(custom.describe())"""HTTP Request Builder with retry policy and immutable config."""
import asyncio
import logging
from dataclasses import dataclass, field
from typing import Any, Literal
logger = logging.getLogger(__name__)
HttpMethod = Literal["GET", "POST", "PUT", "PATCH", "DELETE"]
# [step] Define immutable request and retry policy types
@dataclass(frozen=True)
class RetryPolicy:
max_retries: int = 0
base_delay_ms: int = 1000
retry_on: tuple[int, ...] = (429, 500, 502, 503)
@dataclass(frozen=True)
class RequestConfig:
method: HttpMethod
url: str
headers: dict[str, str]
body: Any
timeout_ms: int
retry: RetryPolicy
# [step] Implement the fluent RequestBuilder
class RequestBuilder:
def __init__(self) -> None:
self._method: HttpMethod = "GET"
self._url: str = ""
self._headers: dict[str, str] = {}
self._body: Any = None
self._timeout_ms: int = 30_000
self._retry = RetryPolicy()
@classmethod
def create(cls) -> "RequestBuilder":
return cls()
def method(self, m: HttpMethod) -> "RequestBuilder":
self._method = m
return self
def url(self, u: str) -> "RequestBuilder":
self._url = u
return self
def header(self, key: str, value: str) -> "RequestBuilder":
self._headers[key] = value
return self
def bearer_token(self, token: str) -> "RequestBuilder":
self._headers["Authorization"] = f"Bearer {token}"
return self
def json(self, data: Any) -> "RequestBuilder":
self._body = data
self._headers["Content-Type"] = "application/json"
return self
def timeout(self, ms: int) -> "RequestBuilder":
if ms <= 0:
raise ValueError("Timeout must be positive")
self._timeout_ms = ms
return self
def retries(self, max_retries: int, base_delay_ms: int = 1000) -> "RequestBuilder":
self._retry = RetryPolicy(
max_retries=max_retries, base_delay_ms=base_delay_ms,
retry_on=self._retry.retry_on,
)
return self
def build(self) -> RequestConfig:
if not self._url:
raise ValueError("URL is required")
if self._body and self._method in ("GET", "DELETE"):
raise ValueError(f"{self._method} requests should not have a body")
return RequestConfig(
method=self._method,
url=self._url,
headers=dict(self._headers),
body=self._body,
timeout_ms=self._timeout_ms,
retry=self._retry,
)
# [step] Implement the request executor with retry logic
async def execute_request(config: RequestConfig) -> dict[str, Any]:
import json as json_mod
last_error: Exception | None = None
max_attempts = config.retry.max_retries + 1
for attempt in range(1, max_attempts + 1):
try:
# Simulated HTTP call (replace with httpx/aiohttp)
logger.info("[%s] %s (attempt %d)", config.method, config.url, attempt)
return {"status": 200, "data": {}, "attempts": attempt}
except Exception as exc:
last_error = exc
if attempt < max_attempts:
delay = config.retry.base_delay_ms * (2 ** (attempt - 1)) / 1000
await asyncio.sleep(delay)
raise last_error or RuntimeError("Request failed")Builder Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Builder Pattern in the Real World
“Consider ordering a custom sandwich at a deli. You tell the sandwich artist (builder) each step — bread type, protein, toppings, sauce — and they assemble it in the right order. You don’t need to know how to layer ingredients properly; you just specify what you want, and the builder hands you a finished sandwich.”