about things notes.zzstoatzz.io
notes
notes languages python language patterns.md
3.3 kB

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.

sources #