about things
1# typing
2
3notes on type hints as actually used in projects like pdsx and fastmcp.
4
5## unions
6
7use `|` for unions, not `Optional`:
8
9```python
10RecordValue = str | int | float | bool | None | dict[str, Any] | list[Any]
11```
12
13from [pdsx/_internal/types.py](https://github.com/zzstoatzz/pdsx/blob/main/src/pdsx/_internal/types.py)
14
15## TypedDict
16
17for structured dictionaries where you know the shape:
18
19```python
20from typing import TypedDict
21
22class RecordResponse(TypedDict):
23 """a record returned from list or get operations."""
24 uri: str
25 cid: str | None
26 value: dict
27
28class CredentialsContext(TypedDict):
29 """credentials extracted from context or headers."""
30 handle: str | None
31 password: str | None
32 pds_url: str | None
33 repo: str | None
34```
35
36better than `dict[str, Any]` because the structure is documented and checked.
37
38from [pdsx/mcp/_types.py](https://github.com/zzstoatzz/pdsx/blob/main/src/pdsx/mcp/_types.py)
39
40## Annotated
41
42attach metadata to types. useful for documentation and schema generation:
43
44```python
45from typing import Annotated
46from pydantic import Field
47
48FilterParam = Annotated[
49 str | None,
50 Field(
51 description=(
52 "jmespath expression to filter/project the result. "
53 "examples: '[*].{uri: uri, text: value.text}' (select fields), "
54 "'[?value.text != null]' (filter items), "
55 "'[*].uri' (extract values)"
56 ),
57 ),
58]
59```
60
61the metadata travels with the type. MCP tools use this for parameter descriptions.
62
63from [pdsx/mcp/filterable.py](https://github.com/zzstoatzz/pdsx/blob/main/src/pdsx/mcp/filterable.py)
64
65## Protocol
66
67define what methods something needs, not what class it is:
68
69```python
70from typing import Protocol
71
72class ContextSamplingFallbackProtocol(Protocol):
73 async def __call__(
74 self,
75 messages: str | list[str | SamplingMessage],
76 system_prompt: str | None = None,
77 temperature: float | None = None,
78 max_tokens: int | None = None,
79 ) -> ContentBlock: ...
80```
81
82any callable matching this signature satisfies the protocol. no inheritance required.
83
84from [fastmcp/utilities/types.py](https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py)
85
86## generics
87
88TypeVar for generic functions and classes:
89
90```python
91from typing import ParamSpec, TypeVar
92
93P = ParamSpec("P")
94R = TypeVar("R")
95
96def filterable(
97 fn: Callable[P, R] | Callable[P, Awaitable[R]],
98) -> Callable[P, Any] | Callable[P, Awaitable[Any]]:
99 ...
100```
101
102`ParamSpec` captures the full signature (args and kwargs) for decorator typing.
103
104from [pdsx/mcp/filterable.py](https://github.com/zzstoatzz/pdsx/blob/main/src/pdsx/mcp/filterable.py)
105
106## ParamSpec
107
108for decorators that preserve function signatures:
109
110```python
111@wraps(fn)
112async def async_wrapper(
113 *args: P.args, _filter: str | None = None, **kwargs: P.kwargs
114) -> Any:
115 result = await fn(*args, **kwargs)
116 return apply_filter(result, _filter)
117```
118
119`P.args` and `P.kwargs` carry the original function's parameter types into the wrapper. type checkers see the wrapper with the same signature as the wrapped function (plus any new parameters).
120
121from [pdsx/mcp/filterable.py](https://github.com/zzstoatzz/pdsx/blob/main/src/pdsx/mcp/filterable.py)
122
123## overload
124
125when return type depends on input type:
126
127```python
128from typing import overload
129
130@overload
131def filterable(
132 fn: Callable[P, Awaitable[R]],
133) -> Callable[P, Awaitable[Any]]: ...
134
135@overload
136def filterable(
137 fn: Callable[P, R],
138) -> Callable[P, Any]: ...
139
140def filterable(
141 fn: Callable[P, R] | Callable[P, Awaitable[R]],
142) -> Callable[P, Any] | Callable[P, Awaitable[Any]]:
143 ...
144```
145
146type checkers know async functions get async wrappers, sync functions get sync wrappers.
147
148from [pdsx/mcp/filterable.py](https://github.com/zzstoatzz/pdsx/blob/main/src/pdsx/mcp/filterable.py)
149
150## TYPE_CHECKING
151
152avoid runtime import costs for types only needed for hints:
153
154```python
155from typing import TYPE_CHECKING
156
157if TYPE_CHECKING:
158 from docket import Docket
159 from docket.execution import Execution
160 from fastmcp.tools.tool_transform import ArgTransform, TransformedTool
161```
162
163the import doesn't happen at runtime, only when type checkers analyze the code. with `from __future__ import annotations`, you don't need string quotes.
164
165from [fastmcp/tools/tool.py](https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py)
166
167## import organization
168
169```python
170"""module docstring."""
171
172from __future__ import annotations
173
174import stdlib_module
175from typing import TYPE_CHECKING
176
177from third_party import thing
178
179from local_package import helper
180
181if TYPE_CHECKING:
182 from expensive import Type
183```
184
185sources:
186- [pdsx/src/pdsx/_internal/types.py](https://github.com/zzstoatzz/pdsx/blob/main/src/pdsx/_internal/types.py)
187- [fastmcp/src/fastmcp/tools/tool.py](https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py)