Skip to content
Back to skills

2553 Python Clean Architecture 9bcff3a0

ASecurity

This reference covers Python-specific patterns for implementing Clean Architecture, Hexagonal Architecture, and Domain-Driven Design.

  • 9 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added May 31, 2026
documentationpythongosqltesting

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned May 31, 2026

npx -y skills add tools-only/X-Skills --skill 2553-python-clean-architecture_9bcff3a0 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 2553 Python Clean Architecture 9bcff3a0?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 2553 Python Clean Architecture 9bcff3a0
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-2553-python-clean-architecture-9bcff3a0/badge)](https://www.skillsdirectory.com/skills/tools-only-2553-python-clean-architecture-9bcff3a0)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
# Python Clean Architecture Patterns

This reference covers Python-specific patterns for implementing Clean Architecture, Hexagonal Architecture, and Domain-Driven Design.

## Python 3.11+ Features for Clean Architecture

### Dataclasses with Slots (Python 3.10+)

```python
from dataclasses import dataclass


@dataclass(frozen=True, slots=True)
class Money:
    """Value object with slots for memory efficiency."""
    amount: int  # cents
    currency: str

    def add(self, other: "Money") -> "Money":
        if self.currency != other.currency:
            raise ValueError("Currency mismatch")
        return Money(self.amount + other.amount, self.currency)
```

### Pattern Matching for Domain Logic (Python 3.10+)

```python
from dataclasses import dataclass
from typing import Union


@dataclass
class CreditCardPayment:
    card_number: str
    expiry: str
    cvv: str


@dataclass
class PayPalPayment:
    email: str


@dataclass
class BankTransferPayment:
    iban: str
    bic: str


PaymentMethod = Union[CreditCardPayment, PayPalPayment, BankTransferPayment]


class PaymentProcessor:
    async def process(self, amount: Money, method: PaymentMethod) -> PaymentResult:
        match method:
            case CreditCardPayment(card_number, expiry, cvv):
                return await self._process_card(amount, card_number, expiry, cvv)
            case PayPalPayment(email):
                return await self._process_paypal(amount, email)
            case BankTransferPayment(iban, bic):
                return await self._process_transfer(amount, iban, bic)
            case _:
                raise ValueError("Unknown payment method")
```

### Self Type for Fluent Interfaces (Python 3.11+)

```python
from typing import Self


class Order:
    def __init__(self):
        self._items: list[OrderItem] = []
        self._status = OrderStatus.DRAFT

    def add_item(self, item: OrderItem) -> Self:
        self._items.append(item)
        return self

    def apply_discount(self, percentage: float) -> Self:
        for item in self._items:
            item.apply_discount(percentage)
        return self

    def finalize(self) -> Self:
        self._status = OrderStatus.FINALIZED
        return self


# Usage: fluent interface
order = (
    Order()
    .add_item(OrderItem("Product A", 100))
    .add_item(OrderItem("Product B", 200))
    .apply_discount(0.1)
    .finalize()
)
```

## Repository Pattern Implementations

### Generic Repository Base

```python
from abc import ABC, abstractmethod
from typing import TypeVar, Generic, List, Optional
from uuid import UUID

T = TypeVar('T')


class IRepository(ABC, Generic[T]):
    """Generic repository interface."""

    @abstractmethod
    async def find_by_id(self, entity_id: UUID) -> Optional[T]:
        pass

    @abstractmethod
    async def find_all(self) -> List[T]:
        pass

    @abstractmethod
    async def save(self, entity: T) -> T:
        pass

    @abstractmethod
    async def delete(self, entity_id: UUID) -> bool:
        pass


class IUnitOfWork(ABC):
    """Unit of Work pattern for transaction management."""

    @abstractmethod
    async def commit(self) -> None:
        pass

    @abstractmethod
    async def rollback(self) -> None:
        pass

    async def __aenter__(self):
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        if exc_type:
            await self.rollback()
        else:
            await self.commit()
```

### SQLAlchemy 2.0 Repository

```python
from typing import Type, TypeVar, List, Optional
from uuid import UUID

from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, update, delete
from sqlalchemy.orm import DeclarativeBase

T = TypeVar('T', bound=DeclarativeBase)
E = TypeVar('E')  # Entity type


class SQLAlchemyRepository(IRepository[E], Generic[T, E]):
    """Generic SQLAlchemy repository implementation."""

    def __init__(self, session: AsyncSession, model_class: Type[T]):
        self._session = session
        self._model_class = model_class

    async def find_by_id(self, entity_id: UUID) -> Optional[E]:
        result = await self._session.execute(
            select(self._model_class).where(self._model_class.id == entity_id)
        )
        model = result.scalar_one_or_none()
        return self._to_entity(model) if model else None

    async def find_all(self) -> List[E]:
        result = await self._session.execute(select(self._model_class))
        models = result.scalars().all()
        return [self._to_entity(m) for m in models]

    async def save(self, entity: E) -> E:
        model = self._to_model(entity)
        self._session.add(model)
        await self._session.flush()
        return entity

    async def delete(self, entity_id: UUID) -> bool:
        result = await self._session.execute(
            delete(self._model_class).where(self._model_class.id == entity_id)
        )
        return result.rowcount > 0

    @abstractmethod
    def _to_entity(self, model: T) -> E:
        """Convert model to domain entity."""
        pass

    @abstractmethod
    def _to_model(self, entity: E) -> T:
        """Convert domain entity to model."""
        pass
```

### In-Memory Repository for Testing

```python
from typing import Dict, List, Optional
from uuid import UUID


class InMemoryRepository(IRepository[E], Generic[E]):
    """In-memory repository for testing."""

    def __init__(self):
        self._storage: Dict[UUID, E] = {}

    async def find_by_id(self, entity_id: UUID) -> Optional[E]:
        return self._storage.get(entity_id)

    async def find_all(self) -> List[E]:
        return list(self._storage.values())

    async def save(self, entity: E) -> E:
        # Assuming entity has an 'id' attribute
        self._storage[entity.id] = entity
        return entity

    async def delete(self, entity_id: UUID) -> bool:
        if entity_id in self._storage:
            del self._storage[entity_id]
            return True
        return False

    def clear(self) -> None:
        self._storage.clear()
```

## Domain Events

### Event Bus Implementation

```python
from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import datetime
from typing import List, Callable, Type, Dict, Any
from collections import defaultdict
import asyncio


@dataclass
class DomainEvent:
    """Base class for domain events."""
    occurred_at: datetime = field(default_factory=datetime.utcnow)


class IEventBus(ABC):
    """Event bus interface for publishing and subscribing to events."""

    @abstractmethod
    async def publish(self, event: DomainEvent) -> None:
        pass

    @abstractmethod
    def subscribe(self, event_type: Type[DomainEvent], handler: Callable) -> None:
        pass


class InMemoryEventBus(IEventBus):
    """In-memory event bus implementation."""

    def __init__(self):
        self._handlers: Dict[Type[DomainEvent], List[Callable]] = defaultdict(list)

    def subscribe(self, event_type: Type[DomainEvent], handler: Callable) -> None:
        self._handlers[event_type].append(handler)

    async def publish(self, event: DomainEvent) -> None:
        handlers = self._handlers.get(type(event), [])
        await asyncio.gather(
            *[handler(event) for handler in handlers],
            return_exceptions=True
        )


class OutboxPattern(IEventBus):
    """Outbox pattern for reliable event publishing."""

    def __init__(self, event_bus: IEventBus, outbox_repository):
        self._event_bus = event_bus
        self._outbox = outbox_repository

    async def publish(self, event: DomainEvent) -> None:
        # Store in outbox first
        await self._outbox.store(event)

    async def process_outbox(self) -> None:
        """Process pending events - call this after transaction commit."""
        events = await self._outbox.get_pending()
        for event in events:
            try:
                await self._event_bus.publish(event)
                await self._outbox.mark_as_processed(event)
            except Exception:
                await self._outbox.mark_as_failed(event)
                raise
```

## Specification Pattern

```python
from abc import ABC, abstractmethod
from typing import List, TypeVar

T = TypeVar('T')


class Specification(ABC, Generic[T]):
    """Specification pattern for business rules."""

    @abstractmethod
    def is_satisfied_by(self, candidate: T) -> bool:
        pass

    def __and__(self, other: "Specification[T]") -> "AndSpecification[T]":
        return AndSpecification(self, other)

    def __or__(self, other: "Specification[T]") -> "OrSpecification[T]":
        return OrSpecification(self, other)

    def __invert__(self) -> "NotSpecification[T]":
        return NotSpecification(self)


class AndSpecification(Specification[T]):
    def __init__(self, left: Specification[T], right: Specification[T]):
        self._left = left
        self._right = right

    def is_satisfied_by(self, candidate: T) -> bool:
        return self._left.is_satisfied_by(candidate) and self._right.is_satisfied_by(candidate)


class OrSpecification(Specification[T]):
    def __init__(self, left: Specification[T], right: Specification[T]):
        self._left = left
        self._right = right

    def is_satisfied_by(self, candidate: T) -> bool:
        return self._left.is_satisfied_by(candidate) or self._right.is_satisfied_by(candidate)


class NotSpecification(Specification[T]):
    def __init__(self, spec: Specification[T]):
        self._spec = spec

    def is_satisfied_by(self, candidate: T) -> bool:
        return not self._spec.is_satisfied_by(candidate)


# Example usage
class PremiumCustomerSpecification(Specification[Customer]):
    def is_satisfied_by(self, customer: Customer) -> bool:
        return customer.total_orders > 100 and customer.average_order_value > 500


class ActiveCustomerSpecification(Specification[Customer]):
    def is_satisfied_by(self, customer: Customer) -> bool:
        return customer.last_order_date > datetime.now() - timedelta(days=90)


# Combine specifications
eligible_for_vip = PremiumCustomerSpecification() & ActiveCustomerSpecification()
customers = [c for c in all_customers if eligible_for_vip.is_satisfied_by(c)]
```

## Result Pattern for Error Handling

```python
from dataclasses import dataclass
from typing import Generic, TypeVar, Union

T = TypeVar('T')
E = TypeVar('E')


@dataclass(frozen=True)
class Ok(Generic[T]):
    value: T


@dataclass(frozen=True)
class Err(Generic[E]):
    error: E


Result = Union[Ok[T], Err[E]]


class DomainError:
    """Base class for domain errors."""
    pass


@dataclass(frozen=True)
class ValidationError(DomainError):
    field: str
    message: str


@dataclass(frozen=True)
class NotFoundError(DomainError):
    resource: str
    identifier: str


# Usage in use cases
class CreateOrderUseCase:
    async def execute(self, request: CreateOrderRequest) -> Result[Order, DomainError]:
        customer = await self._customer_repo.find_by_id(request.customer_id)
        if not customer:
            return Err(NotFoundError("Customer", str(request.customer_id)))

        if not customer.can_place_orders():
            return Err(ValidationError("customer", "Customer cannot place orders"))

        order = Order.create(customer, request.items)
        await self._order_repo.save(order)
        return Ok(order)
```

## Manual Dependency Injection

```python
from typing import TypeVar, Callable, Dict, Type, Any

T = TypeVar('T')


class Container:
    """Simple manual dependency injection container."""

    def __init__(self):
        self._registrations: Dict[Type, Callable] = {}
        self._singletons: Dict[Type, Any] = {}

    def register(self, interface: Type[T], factory: Callable[[], T]) -> None:
        """Register a factory for an interface."""
        self._registrations[interface] = factory

    def register_instance(self, interface: Type[T], instance: T) -> None:
        """Register a singleton instance."""
        self._singletons[interface] = instance

    def resolve(self, interface: Type[T]) -> T:
        """Resolve an interface to its implementation."""
        if interface in self._singletons:
            return self._singletons[interface]

        if interface not in self._registrations:
            raise KeyError(f"No registration for {interface}")

        return self._registrations[interface]()


# Usage
container = Container()

# Register repositories
container.register(
    IUserRepository,
    lambda: SQLAlchemyUserRepository(get_db_session())
)

# Register use cases
container.register(
    CreateUserUseCase,
    lambda: CreateUserUseCase(container.resolve(IUserRepository))
)

# Resolve
use_case = container.resolve(CreateUserUseCase)
```

## Async Context Managers for Resources

```python
from contextlib import asynccontextmanager
from typing import AsyncGenerator


@asynccontextmanager
async def unit_of_work(session: AsyncSession) -> AsyncGenerator[UnitOfWork, None]:
    """Context manager for transaction handling."""
    uow = UnitOfWork(session)
    try:
        yield uow
        await uow.commit()
    except Exception:
        await uow.rollback()
        raise


# Usage
async def create_user_handler(request: CreateUserRequest):
    async with unit_of_work(session) as uow:
        use_case = CreateUserUseCase(uow.user_repository)
        result = await use_case.execute(request)
        # Automatically commits or rolls back
```

## Type Hints Best Practices

```python
from typing import NewType, Protocol
from uuid import UUID

# NewType for type safety
UserId = NewType('UserId', UUID)
OrderId = NewType('OrderId', UUID)


def get_user(user_id: UserId) -> User: ...
def get_order(order_id: OrderId) -> Order: ...

# This will be caught by type checker:
# get_user(order_id)  # Error: Expected UserId, got OrderId


# Protocol for structural typing
class Logger(Protocol):
    def debug(self, msg: str) -> None: ...
    def info(self, msg: str) -> None: ...
    def error(self, msg: str) -> None: ...


class UseCase:
    def __init__(self, logger: Logger) -> None:
        self._logger = logger


# Any object with debug/info/error methods works
class ConsoleLogger:
    def debug(self, msg: str) -> None: print(f"DEBUG: {msg}")
    def info(self, msg: str) -> None: print(f"INFO: {msg}")
    def error(self, msg: str) -> None: print(f"ERROR: {msg}")


use_case = UseCase(ConsoleLogger())  # Works!
```

Files in this skill

  • README.md1.1 KB
  • skill.md14.1 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.