New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Decompose Field components into Annotated #2129
Comments
I mentioned some similar ideas here. |
Working prototype up at https://github.com/JacobHayes/pydantic-annotated |
High level: This is something I've been meaning to think about for sometime, very happy you've done most of the thinking for me! I think basic support for In more detail:
Really we need PEP-637 when presumably we'll be able to do
Given the following options
Which is really clearest or quickest to type. I would say 1. or 2. With regard to changes in V2 - I doubt we can really make significant changes to I think it would be better to have sensible positional arguments to constrained types, e.g. like #2104, and add more arguments to In summary, let's provide support for |
Sounds good, happy to take a stab at that. Any preference for "take right", "merge right", or "error" for the (edge) cases of: ShortInt = Annotated[int, Field(..., lt=256)]
class Model(BaseModel):
x1: Annotated[int, Field(..., lt=256)] = Field(..., gt=0)
x2: Annotated[int, Field(..., lt=256), Field(..., gt=0)]
x3: Annotated[ShortInt, Field(..., gt=0)] # more subtle, but exact same as x2 "error" seems like a fine first pass that would allow us to open up over time (but "merge right" would be very cool 😁). Ah, glad to see PEP-637 was revived! Not pertinent now, but with kwargs, we may still need to identify
but that might fall inline with separate custom |
Great, regarding errors: class Model(BaseModel):
x1: Annotated[int, Field(..., lt=256)] = Field(..., gt=0) # error surely?
x2: Annotated[int, Field(..., lt=256), Field(..., gt=0)] # don't really mind, probably error, but we shouldn't document this
x3: Annotated[ShortInt, Field(..., gt=0)] # same as now with "x3: ShortInt = Field(..., gt=0)" |
I think the above should be implemented in the PR now (pending some iteration on error messages and docs). For further/separate discussion (motivated by being very new to
Knowing I'm missing a lot of context (and risking orthogonal topics), I wonder if
Regarding:
This makes sense, though I wonder how ... If you made it this far, thanks for the time. I know I'm likely getting quite ahead of myself here (both in designs and general "new to the project" etiquette), but as you can tell, I'm quite excited about the project! I was planning to build (a much worse subset of) this as part of a new data engineering tool before finding the project and seeing how strong a foundation there is. For my uses, I'll be adding dozens/hundreds of new validators and metadata (ex: validators like "reference" and metadata/statistics like "count"), many of which are type independent. That's a large reason I find the "decomposed" pattern so helpful. Anyway, I hope this is well received (even if turned down 😁)! |
Just my 2 cents on this topic. class Pokemon(BaseModel):
name: Annotated[str, Constraints(min_length=1)] = Field('pikachu', alias='Name') With this kind of codefrom dataclasses import dataclass
from enum import Enum
from typing import Annotated, Any, Callable, Optional
from pydantic import BaseModel
NoArgAnyCallable = Callable[[], Any]
class UnsetEnum(Enum):
v = 0
Unset = UnsetEnum.v
@dataclass
class Constraints:
immutable: bool = False
# number
gt: float = None
ge: float = None
lt: float = None
le: float = None
multiple_of: float = None
# str
regex: str = None
# str, list, ... (collections)
min_length: int = None
max_length: int = None
@dataclass
class Field:
default: Any = Unset
default_factory: Optional[NoArgAnyCallable] = None
alias: str = None
title: str = None
description: str = None This way we keep all restrictions, constraints on the type on the left side and all the metadata of the field on the right side. |
Agreed that many classes will be more verbose - particularly on the author side but I think less so on the user side. One downside with a That issue is probably less prominent for |
I've been looking through the issues on constrained types,
I think that's a great idea. Additionally, what if instead of passing class Pokemon(BaseModel):
name: Annotated[str, constr(min_length=1)] = Field('pikachu', alias='Name')
abilities: Annotated[str, conlist(constr(to_lower=True), min_length=4)] or generally Here's why I think that's better than effectively passing jsonschema properties (like
class Box(pydantic.BaseModel):
stuff: List[str] = pydantic.Field(min_length=1) # OOPS, should be min_items
The only downside (that I can see) is that the type is somewhat duplicated and may be out of sync, e.g. |
Not sure exactly where https://github.com/annotated-types/annotated-types is going, but if that ends up being compatible with pydantic, I think that would cover the OP ("decompose field components into Annotated")! |
Gonna close this as it looks like it will be supported with a very similar pattern come v2 using annotated-types! 🎉 |
Feature Request
PEP 593's
typing.Annotated
(ortyping_extensions.Annotated
) provides some scaffolding to do a lot of things that are currently handled withField
. This was recognized in #837, which suggested adding support forvar: Annotated[type, Field(...)]
in addition to the currentvar: type = Field(...)
. I'm proposing we take this one step further and we decomposeField
into a collection of classes (1 for eachField
kwarg) that could be instantiated and used inAnnotated
directly. The benefits would be:Annotated[int, Description("...")]
vsAnnotated[int, Field(description="...")]
pydantic
metadata and could replace**extra
**extra
are explicitly meant forField
, howeverAnnotated
values may not. If this is an issue, perhaps we can define a small interface/FieldAnnotation
base class that could be use to check.Default
to replace the positional argOne thing we may have to deal with is the ability for there to be multiple instances of any of the classes and define some conflict resolution (likely "take last" or "take outer", however this manifests with
Annotated
). Arguably, we'd also have to handle this forAnnotated[type, Field(...), Field(...)
too, but this decomposition ofField
would make it much easier to override only a small piece (or perhaps support multiple, in the case ofAlias
for example).Unless this were a v2 only change (not sure on timeline), we'll of course have to support both this and the existing
var: type = Field(...)
patterns. Separate from this vsAnnotated[type, Field(...)]
, we need to supportAnnotated
metadata in general. I have no familiarity with thepydantic
code base, but would be happy to help out with some guidance.It seems like with a little extra work, we could replace all of the
con*
with these annotations, which would make mypy much happier. We might be able to do that in a backwards compatible-ish way if we make thecon*
calls wrappers returning the appropriateAnnotated
hint. Probably tricky for v1.Example of "my custom code outside pydantic" if implemented:
WDYT?
Checks
The text was updated successfully, but these errors were encountered: