Last active
February 3, 2026 20:53
-
-
Save lmmx/0a7c215598ab39506e539e40239d5ac4 to your computer and use it in GitHub Desktop.
Selecting between paths based on their existence on the filesystem using default validation at runtime
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from pathlib import Path | |
| from typing import Annotated | |
| from pydantic import BaseModel, BeforeValidator, Field, FilePath | |
| PathA = Annotated[FilePath, BeforeValidator(lambda _: Path("a.txt"))] | |
| PathB = Annotated[FilePath, BeforeValidator(lambda _: Path("b.txt"))] | |
| class MyModel(BaseModel): | |
| my_path: PathA | PathB = Field(default="sentinel", validate_default=True) | |
| print(" >> Setup: create only a.txt") | |
| Path("a.txt").touch() | |
| Path("b.txt").unlink(missing_ok=True) | |
| m1 = MyModel() | |
| print(f"Selected: {m1.my_path}") # Should print a.txt | |
| print() | |
| print(" >> Setup: create only b.txt") | |
| Path("a.txt").unlink(missing_ok=True) | |
| Path("b.txt").touch() | |
| m2 = MyModel() | |
| print(f"Selected: {m2.my_path}") # Should print b.txt |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from pathlib import Path | |
| from typing import Annotated | |
| from pydantic import BaseModel, BeforeValidator, Field, FilePath | |
| # Single-expression callable | |
| Candidate = lambda p: Annotated[FilePath, BeforeValidator(lambda _: Path(p))] | |
| class MyModel(BaseModel): | |
| my_path: Candidate("a.txt") | Candidate("b.txt") = Field( | |
| default=None, validate_default=True | |
| ) | |
| print(" >> Setup: create only a.txt") | |
| Path("a.txt").touch() | |
| Path("b.txt").unlink(missing_ok=True) | |
| m1 = MyModel() | |
| print(f"Selected: {m1.my_path}") # a.txt | |
| print() | |
| print(" >> Setup: create only b.txt") | |
| Path("a.txt").unlink(missing_ok=True) | |
| Path("b.txt").touch() | |
| m2 = MyModel() | |
| print(f"Selected: {m2.my_path}") # b.txt |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from pathlib import Path | |
| from typing import Annotated | |
| from pydantic import BaseModel, BeforeValidator, Field, FilePath | |
| class Candidate: | |
| """FilePath that ignores input and resolves to a fixed path. | |
| Union of Candidates acts as a fallback chain (first existing wins).""" | |
| def __class_getitem__(cls, p: str | Path): | |
| return Annotated[FilePath, BeforeValidator(lambda _: Path(p))] | |
| class MyModel(BaseModel): | |
| my_path: Candidate["a.txt"] | Candidate["b.txt"] = Field( | |
| default=None, validate_default=True | |
| ) | |
| print(" >> Setup: create only a.txt") | |
| Path("a.txt").touch() | |
| Path("b.txt").unlink(missing_ok=True) | |
| m1 = MyModel() | |
| print(f"Selected: {m1.my_path}") # a.txt | |
| print() | |
| print(" >> Setup: create only b.txt") | |
| Path("a.txt").unlink(missing_ok=True) | |
| Path("b.txt").touch() | |
| m2 = MyModel() | |
| print(f"Selected: {m2.my_path}") # b.txt |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from pathlib import Path | |
| from typing import Annotated, Union | |
| from pydantic import BaseModel, BeforeValidator, Field, FilePath | |
| def FirstOf(*paths: str | Path): | |
| # Generator expression: must capture p with p=p | |
| return Union[tuple( | |
| Annotated[FilePath, BeforeValidator(lambda _, p=p: Path(p))] | |
| for p in paths | |
| )] | |
| class MyModel(BaseModel): | |
| my_path: FirstOf("a.txt", "b.txt") = Field(default=None, validate_default=True) | |
| print(" >> Setup: create only a.txt") | |
| Path("a.txt").touch() | |
| Path("b.txt").unlink(missing_ok=True) | |
| m1 = MyModel() | |
| print(f"Selected: {m1.my_path}") # a.txt | |
| print() | |
| print(" >> Setup: create only b.txt") | |
| Path("a.txt").unlink(missing_ok=True) | |
| Path("b.txt").touch() | |
| m2 = MyModel() | |
| print(f"Selected: {m2.my_path}") # b.txt |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from pathlib import Path | |
| from typing import Annotated | |
| from pydantic import BaseModel, BeforeValidator, Field, FilePath | |
| def Candidate(p: str | Path): | |
| # Safe without p=p; closure is unique per call | |
| return Annotated[FilePath, BeforeValidator(lambda _: Path(p))] | |
| class MyModel(BaseModel): | |
| my_path: Candidate("a.txt") | Candidate("b.txt") = Field( | |
| default=None, validate_default=True | |
| ) | |
| print(" >> Setup: create only a.txt") | |
| Path("a.txt").touch() | |
| Path("b.txt").unlink(missing_ok=True) | |
| m1 = MyModel() | |
| print(f"Selected: {m1.my_path}") # a.txt | |
| print() | |
| print(" >> Setup: create only b.txt") | |
| Path("a.txt").unlink(missing_ok=True) | |
| Path("b.txt").touch() | |
| m2 = MyModel() | |
| print(f"Selected: {m2.my_path}") # b.txt |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Alt. forms, including a rare
__class_getitem__, which'd let you do aLiteral-like square bracketed hint here