Loading repository data…
Loading repository data…
100nm / repository
An opinionated Web API template with Python, built around Domain-Driven Design (DDD), CQRS, and Clean Architecture.
A transparent discovery signal based on current public GitHub metadata.
This score does not audit code, security, maintainers, documentation quality, or suitability. Verify the repository and its current documentation before adoption.
An opinionated Web API template with Python, built around Domain-Driven Design (DDD), CQRS, and Clean Architecture.
| Category | Package | Role |
|---|---|---|
| Package Manager | uv | Dependency management |
| Web Framework | FastAPI | REST API |
| CLI | Typer | Command line interface |
| ORM | SQLAlchemy | Database access |
| Migration | Alembic | Schema migrations |
| Validation | Pydantic | Validation and serialization |
| DI | python-injection | Dependency injection |
| CQRS | python-cq | Command/Query Responsibility Segregation |
src/
├── core/ # Domain + Application layers (business logic)
│ └── {context}/
│ ├── domain/ # Domain layer (entities, value objects, aggregates)
│ └── ... # Application layer (commands, queries, ports, events)
├── services/ # Shared technical services (cross-cutting concerns)
└── infra/ # Infrastructure layer (concrete implementations)
src/core/{context}/domain/)The Domain layer contains pure business models: entities, value objects, and aggregates. It has no dependencies on external frameworks.
src/core/{context}/domain/
├── {aggregate}.py # Aggregates (entity that groups related objects and ensures they are always in a valid state together)
├── {entity}.py # Entities (objects with unique identity)
└── {value_object}.py # Value Objects (immutable, no identity)
| Package | Role | Justification |
|---|---|---|
pydantic | Domain models | Native validation, immutability with frozen=True |
| Type | Path Pattern | Description |
|---|---|---|
| Aggregate | src/core/{context}/domain/{aggregate}.py | Entity that groups related objects and ensures they are always in a valid state together |
| Entity | src/core/{context}/domain/{entity}.py | Object with unique identity |
| Value Object | src/core/{context}/domain/{value_object}.py | Immutable object without identity |
UserSessionfrom datetime import datetime
from uuid import UUID
from pydantic import BaseModel, SecretStr, field_serializer
class UserSession(BaseModel):
id: UUID
user_id: UUID
created_at: datetime
last_use_at: datetime
secret: SecretStr
src/core/{context}/)The Application layer contains use cases and orchestration logic: commands, queries, events, and ports. Everything
in the bounded context folder except domain/.
src/core/{context}/
├── domain/ # Domain layer (see above)
├── commands/ # Commands and their Handlers (write operations)
├── queries/ # Queries and their Views (read operation definitions)
├── events/ # Domain Events
├── ports/ # Interfaces (Protocols) for dependency inversion
│ └── repo/ # Repository interfaces
└── shared/ # Shared code within the bounded context
| Package | Role | Justification |
|---|---|---|
python-cq | CQRS | Command/Query separation, handler decoupling |
pydantic | DTOs | Command/Event/Query/View validation |
| Type | Path Pattern | Description |
|---|---|---|
| Command | src/core/{context}/commands/{action}.py | Action that modifies state |
| Query | src/core/{context}/queries/{query_name}.py | Data reading (Query + View) |
| Event | src/core/{context}/events/{event}.py | Domain event |
| Port (Repository) | src/core/{context}/ports/repo/{aggregate}.py | Persistence interface |
| Port (Service) | src/core/{context}/ports/{service}.py | External service interface |
from typing import NamedTuple
from uuid import UUID
from cq import command_handler
from pydantic import BaseModel, SecretStr, field_serializer
from src.core.auth.domain.session import UserSession
from src.core.auth.ports.repo.user_permission import UserPermissionRepository
from src.core.auth.ports.repo.user_session import UserSessionRepository
from src.core.auth.ports.token_generator import TokenGenerator
from src.core.auth.shared.access_token import encode_access_token
from src.core.auth.shared.session_token import encode_session_token
from src.services.datetime.abc import DateTimeService
from src.services.hasher.abc import Hasher
from src.services.jwt.abc import JWTService
from src.services.uuid.abc import UUIDGenerator
class OpenUserSessionCommand(BaseModel):
user_id: UUID
class UserTokens(BaseModel):
access_token: SecretStr
session_token: SecretStr
@field_serializer("access_token", "session_token", when_used="json")
def _dump_secret(self, value: SecretStr) -> str:
return value.get_secret_value()
@command_handler
class OpenUserSessionHandler(NamedTuple):
datetime: DateTimeService
hasher: Hasher
jwt: JWTService
repo: UserSessionRepository
token_generator: TokenGenerator
user_permission_repo: UserPermissionRepository
uuid: UUIDGenerator
async def handle(self, command: OpenUserSessionCommand) -> UserTokens:
user_id = command.user_id
session_secret = self.token_generator.generate(128)
session = self.new_session(user_id, session_secret)
await self.repo.save(session)
permissions = await self.user_permission_repo.get(user_id)
access_token = encode_access_token(self.jwt, user_id, permissions)
session_token = encode_session_token(session.id, session_secret)
return UserTokens(
access_token=SecretStr(access_token),
session_token=SecretStr(session_token),
)
def new_session(self, user_id: UUID, session_secret: str) -> UserSession:
now = self.datetime.utcnow()
return UserSession(
id=self.uuid.next(),
user_id=user_id,
created_at=now,
last_use_at=now,
secret=SecretStr(self.hasher.hash(session_secret)),
)
from abc import abstractmethod
from typing import Protocol
from uuid import UUID
from src.core.auth.domain.session import UserSession
class UserSessionRepository(Protocol):
@abstractmethod
async def delete(self, session_id: UUID) -> None:
raise NotImplementedError
@abstractmethod
async def get(self, session_id: UUID) -> UserSession | None:
raise NotImplementedError
@abstractmethod
async def save(self, session: UserSession) -> None:
raise NotImplementedError
from datetime import datetime
from uuid import UUID
from pydantic import BaseModel
class GetPrivateUserProfileQuery(BaseModel):
user_id: UUID
class PrivateUserProfileView(BaseModel):
id: UUID
created_at: datetime
first_name: str
last_name: str
src/services/)The Services layer defines abstract interfaces for common technical services used across the entire application. These are cross-cutting concerns that can be used by any layer.
src/services/{service_name}/
├── abc.py # Abstract interface (Protocol)
└── {impl}.py # Implementation
| Type | Path Pattern | Description |
|---|---|---|
| Service Interface | src/services/{service}/abc.py | Abstract Protocol |
| Implementation | src/services/{service}/{impl}.py | Concrete implementation |
Hasherfrom abc import abstractmethod
from typing import Protocol
class Hasher(Protocol):
@abstractmethod
def hash(self, value: str) -> str:
raise NotImplementedError
@abstractmethod
def verify(self, value: str, hashed_value: str) -> bool:
raise NotImplementedError
def needs_rehash(self, hashed_value: str) -> bool:
return False
Argon2Hasherfrom argon2 import PasswordHasher
from argon2.exceptions import InvalidHashError, VerificationError
from injection import injectable
from src.services.hasher.abc import Hasher
@injectable(on=Hasher)
class Argon2Hasher(Hasher):
def __init__(self) -> None:
self.__internal = PasswordHasher()
def hash(self, value: str) -> str:
return self.__internal.hash(value)
def verify(self, value: str, hashed_value: str) -> bool:
try:
return self.__internal.verify(hashed_value, value)
except (InvalidHashError, VerificationError):
return False
def needs_rehash(self, hashed_value: str) -> bool:
return self.__internal.check_needs_rehash(hashed_value)
src/infra/)The Infrastructure layer contains all concrete implementations: API, database, external integrations, etc.
src/infra/
├── adapters/ # Port implementations (repositories, services)
│ └── {context}/
│ └── repo/ # SQLAlchemy repositories
├── api/
│ ├── builder.py # FastAPI configuration
│ ├── dependencies.py # FastAPI dependencies (auth, locale, etc.)
│ └── routes/ # Endpoints by domain
├── cli/
│ ├── builder.py # Typer configuration
│ └── apps/ # CLI commands
├── db/ # Database
│ ├── tables.py # SQLAlchemy table definitions
│ └── migrations/ # Alembic migrations
├── integrations/ # Third-party integrations (Stripe, etc.)
│ └── {provider}/
│ └── commands/ # Integration-specific commands
└── query_handlers/ # Query handlers (DB read operations)
| Package | Role | Justification | |----------------------------------|---------