1# patterns 2 3class design, decorators, error handling, and other structural patterns. 4 5## private internals 6 7keep implementation details in `_internal/`: 8 9``` 10src/mypackage/ 11├── __init__.py # public API 12├── cli.py # entry point 13└── _internal/ # implementation details 14 ├── config.py 15 ├── operations.py 16 └── types.py 17``` 18 19`__init__.py` re-exports the public interface. users import from the package, not from internals. 20 21## dataclasses for DTOs 22 23simple data containers that don't need validation: 24 25```python 26from dataclasses import dataclass 27 28@dataclass 29class BatchResult: 30 successful: list[str] 31 failed: list[tuple[str, Exception]] 32 33 @property 34 def total(self) -> int: 35 return len(self.successful) + len(self.failed) 36``` 37 38lighter than pydantic when you control the data source. 39 40from [pdsx/_internal/batch.py](https://github.com/zzstoatzz/pdsx/blob/main/src/pdsx/_internal/batch.py) 41 42## base classes with parallel implementations 43 44when you need both sync and async: 45 46```python 47class _BaseClient: 48 def __init__(self, *, token: str | None = None): 49 self._token = token or get_settings().token 50 51class Client(_BaseClient): 52 def get(self, url: str) -> dict: 53 return httpx.get(url, headers=self._headers).json() 54 55class AsyncClient(_BaseClient): 56 async def get(self, url: str) -> dict: 57 async with httpx.AsyncClient() as client: 58 return (await client.get(url, headers=self._headers)).json() 59``` 60 61shared logic in base, divergent implementation in subclasses. 62 63## fluent interfaces 64 65chainable methods that return `self`: 66 67```python 68class Track: 69 def __init__(self, source: str): 70 self._source = source 71 self._effects: list[str] = [] 72 73 def volume(self, level: float) -> "Track": 74 self._effects.append(f"volume={level}") 75 return self 76 77 def lowpass(self, freq: float) -> "Track": 78 self._effects.append(f"lowpass=f={freq}") 79 return self 80 81# usage 82track.volume(0.8).lowpass(600).fade_in(0.5) 83``` 84 85## factory classmethods 86 87alternative constructors: 88 89```python 90@dataclass 91class URIParts: 92 """parsed components of an AT-URI.""" 93 repo: str 94 collection: str 95 rkey: str 96 97 @classmethod 98 def from_uri(cls, uri: str, client_did: str | None = None) -> URIParts: 99 """parse an AT-URI into its components.""" 100 uri_without_prefix = uri.replace("at://", "") 101 parts = uri_without_prefix.split("/") 102 103 # shorthand format: collection/rkey 104 if len(parts) == 2: 105 if not client_did: 106 raise ValueError("shorthand URI requires authentication") 107 return cls(repo=client_did, collection=parts[0], rkey=parts[1]) 108 109 # full format: did/collection/rkey 110 if len(parts) == 3: 111 return cls(repo=parts[0], collection=parts[1], rkey=parts[2]) 112 113 raise ValueError(f"invalid URI format: {uri}") 114``` 115 116from [pdsx/_internal/resolution.py](https://github.com/zzstoatzz/pdsx/blob/main/src/pdsx/_internal/resolution.py) 117 118## keyword-only arguments 119 120force callers to name arguments for clarity: 121 122```python 123def batch_create( 124 client: Client, 125 collection: str, 126 records: list[dict], 127 *, # everything after is keyword-only 128 concurrency: int = 10, 129 fail_fast: bool = False, 130) -> BatchResult: 131 ... 132 133# must use: batch_create(client, "posts", items, concurrency=5) 134# not: batch_create(client, "posts", items, 5) 135``` 136 137## custom exceptions 138 139with helpful context: 140 141```python 142class AuthenticationRequired(Exception): 143 def __init__(self, operation: str = "this operation"): 144 super().__init__( 145 f"{operation} requires authentication.\n\n" 146 "Set ATPROTO_HANDLE and ATPROTO_PASSWORD environment variables." 147 ) 148``` 149 150the message tells you what to do, not just what went wrong. 151 152from [pdsx/mcp/client.py](https://github.com/zzstoatzz/pdsx/blob/main/src/pdsx/mcp/client.py) 153 154## exception hierarchies 155 156for structured error handling: 157 158```python 159class AppError(Exception): 160 """base for all app errors.""" 161 162class ValidationError(AppError): 163 """input validation failed.""" 164 165class ResourceError(AppError): 166 """resource operation failed.""" 167 168# callers can catch AppError for all, or specific types 169``` 170 171## argparse for CLIs 172 173argparse is stdlib. subparsers with aliases give you unix-style commands: 174 175```python 176parser = argparse.ArgumentParser( 177 description="my tool", 178 formatter_class=argparse.RawDescriptionHelpFormatter, 179) 180parser.add_argument("-r", "--repo", help="target repo") 181 182subparsers = parser.add_subparsers(dest="command") 183 184list_parser = subparsers.add_parser("list", aliases=["ls"]) 185list_parser.add_argument("collection") 186list_parser.add_argument("--limit", type=int, default=50) 187``` 188 189dispatch with tuple membership to handle aliases: 190 191```python 192args = parser.parse_args() 193 194if not args.command: 195 parser.print_help() 196 return 1 197 198if args.command in ("list", "ls"): 199 return await cmd_list(args.collection, args.limit) 200``` 201 202async entry point pattern: 203 204```python 205async def async_main() -> int: 206 # argparse and dispatch here 207 return 0 208 209def main() -> NoReturn: 210 sys.exit(asyncio.run(async_main())) 211``` 212 213source: [pdsx/src/pdsx/cli.py](https://github.com/zzstoatzz/pdsx/blob/main/src/pdsx/cli.py) 214 215## module-level singletons 216 217instantiate once, import everywhere: 218 219```python 220# config.py 221from functools import lru_cache 222 223@lru_cache 224def get_settings() -> Settings: 225 return Settings() 226 227settings = get_settings() 228 229# console.py 230from rich.console import Console 231 232console = Console() 233``` 234 235sources: 236- [pdsx](https://github.com/zzstoatzz/pdsx) 237- [plyr-python-client](https://github.com/zzstoatzz/plyr-python-client) 238- [docket](https://github.com/chrisguidry/docket)