add annotated types pattern for reusable validation

bind validation to types with Annotated + AfterValidator
instead of repeating @field_validator on each schema

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Changed files
+31
languages
python
ecosystem
+31
languages/python/ecosystem/pydantic.md
··· 53 53 54 54 from [pdsx/_internal/config.py](https://github.com/zzstoatzz/pdsx/blob/main/src/pdsx/_internal/config.py) 55 55 56 + ## annotated types for reusable validation 57 + 58 + when multiple schemas share the same validation logic, bind it to the type itself instead of repeating `@field_validator` on each schema: 59 + 60 + ```python 61 + from datetime import timedelta 62 + from typing import Annotated 63 + from pydantic import AfterValidator, BaseModel 64 + 65 + def _validate_non_negative_timedelta(v: timedelta) -> timedelta: 66 + if v < timedelta(seconds=0): 67 + raise ValueError("timedelta must be non-negative") 68 + return v 69 + 70 + NonNegativeTimedelta = Annotated[ 71 + timedelta, 72 + AfterValidator(_validate_non_negative_timedelta) 73 + ] 74 + 75 + class RunDeployment(BaseModel): 76 + schedule_after: NonNegativeTimedelta 77 + ``` 78 + 79 + benefits: 80 + - write validation once 81 + - field types become swappable interfaces 82 + - types are self-documenting 83 + 84 + from [coping with python's type system](https://blog.zzstoatzz.io/coping-with-python-type-system/) 85 + 56 86 ## when to use what 57 87 58 88 pydantic models are heavier than they look - they do a lot of work on instantiation. for internal data you control, python's `dataclasses` are simpler: ··· 77 107 78 108 sources: 79 109 - [how to use pydantic-settings](https://blog.zzstoatzz.io/how-to-use-pydantic-settings/) 110 + - [coping with python's type system](https://blog.zzstoatzz.io/coping-with-python-type-system/)