about things
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)