# 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](../ecosystem/pydantic/README.md). ## 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: ```python 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`: ```python 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 `@classmethod`s named `from_*`: ```python @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: ```python 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: ```python 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: ```python 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: ```python 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](../ecosystem/pydantic/settings.md). ## sources - [pdsx](https://github.com/zzstoatzz/pdsx) - [plyr-python-client](https://github.com/zzstoatzz/plyr-python-client) - [docket](https://github.com/chrisguidry/docket)