patterns #
class design, decorators, error handling, and other structural patterns.
private internals #
keep implementation details in _internal/; __init__.py re-exports the public API:
src/mypackage/
├── __init__.py # public API
├── cli.py # entry point
└── _internal/ # implementation details
users import from the package, not from internals.
dataclasses for DTOs #
for the pydantic-vs-dataclass boundary (validate at edges, plain dataclasses inside), see pydantic.
base classes with parallel implementations #
when you need both sync and async, put shared logic in a base class and diverge only in the subclasses:
class _BaseClient:
def __init__(self, *, token: str | None = None):
self._token = token or get_settings().token
class Client(_BaseClient):
def get(self, url: str) -> dict: ...
class AsyncClient(_BaseClient):
async def get(self, url: str) -> dict: ...
fluent interfaces #
chainable methods mutate state and return self:
def volume(self, level: float) -> "Track":
self._effects.append(f"volume={level}")
return self
track.volume(0.8).lowpass(600).fade_in(0.5)
factory classmethods #
alternative constructors as @classmethods named from_*:
@classmethod
def from_uri(cls, uri: str) -> URIParts:
repo, collection, rkey = uri.removeprefix("at://").split("/")
return cls(repo=repo, collection=collection, rkey=rkey)
keeps parsing/derivation logic out of __init__, which stays a plain field assignment.
keyword-only arguments #
a bare * forces callers to name everything after it:
def batch_create(client, collection, records, *, concurrency: int = 10, fail_fast: bool = False): ...
call sites stay legible and reordering defaults is not a breaking change.
custom exceptions #
put remediation in the message, not just the diagnosis:
class AuthenticationRequired(Exception):
def __init__(self, operation: str = "this operation"):
super().__init__(
f"{operation} requires authentication.\n\n"
"Set ATPROTO_HANDLE and ATPROTO_PASSWORD environment variables."
)
give an app a base exception class so callers can catch broadly or narrowly:
class AppError(Exception): ...
class ValidationError(AppError): ...
class ResourceError(AppError): ...
argparse for CLIs #
argparse is stdlib. subparsers with aliases give unix-style commands; dispatch with tuple membership to cover aliases:
subparsers = parser.add_subparsers(dest="command")
list_parser = subparsers.add_parser("list", aliases=["ls"])
...
if args.command in ("list", "ls"):
return await cmd_list(args.collection, args.limit)
async entry point: def main() -> NoReturn: sys.exit(asyncio.run(async_main())).
module-level singletons #
instantiate once at module level, import everywhere (console = Console()). for settings, use the cached-factory form — see pydantic settings.