Change of API in v2 to declare required and optional fields #2353
Replies: 24 comments 28 replies
-
I was initially thrown off by the union operator, thinking "That's not valid!" but, after looking at it further, I must say, it is highly intriguing. It does look like it would solve the "What is Optional" issue that many have had. Would we even need/use If we added |
Beta Was this translation helpful? Give feedback.
-
What would it mean for an optional field to not have a default value? Would the attribute be omitted from model instances and how would type checkers be made aware? The ellipsis is also only valid as a placeholder in functions: def foo(
# this type checks
bar: str = ...,
) -> None: ...
class Foo:
# this doesn't: 'Incompatible types in assignment (expression has type "ellipsis", variable has type "str")'
bar: str = ... |
Beta Was this translation helpful? Give feedback.
-
I see potential for encountering a lot of AFAIK, you can't tell mypy that specific attr might be undefined on some instances. So we'll be forced to check What are the benefits of having such fields? |
Beta Was this translation helpful? Give feedback.
-
Hmm that's already the case now when we force required fields with
And indeed it would need some extra work on the My main focus is making models more explicit and the DX. |
Beta Was this translation helpful? Give feedback.
-
How's that? So by saying «optional field with no default value», you mean that if field was not in data, attr will still be present on model and value would be |
Beta Was this translation helpful? Give feedback.
-
Sorry @MrMrRobat maybe I was not clear. And yes I just mean the default value used behind the scene is always |
Beta Was this translation helpful? Give feedback.
-
'Forcing' required fields with the ellipsis is redundant which is why it hasn't been an issue so far, I guess.
Well, there are other type checkers out there, some of which do not support plug-ins. Pyright in particular has been gaining in popularity owing to its integration in VS Code. |
Beta Was this translation helpful? Give feedback.
-
You're absolutely right. I only use from pydantic import BaseModel, Unset
class Model(BaseModel):
a: int | None
b: int | None | Unset # or `Undefined` or something else...
c: int | None | Unset with |
Beta Was this translation helpful? Give feedback.
-
That seems reasonable from a typing standpoint, but I'm not sure I fully understand the use case, if you could explain how this would be useful. |
Beta Was this translation helpful? Give feedback.
-
Sorry if I'm not clear :(
My main point is to be able to declare a field that may be omitted, but may not be null if the field is supplied |
Beta Was this translation helpful? Give feedback.
-
I see, so this would be for fields that (a) can be omitted but (b) should also not have a default value in a JSON schema. The discussion of As an aside, if #990 would mean reverting to normal typing semantics and |
Beta Was this translation helpful? Give feedback.
-
from pydantic import BaseModel, Unset
class Model(BaseModel):
a: int | None
b: int | None | Unset # or `Undefined` or something else...
c: int | None | Unset I don't think this is a great idea to attempt to capture this at the type level by using https://www.python.org/dev/peps/pep-0593/ from typing import Annotated, TypeVar, Optional
from pydantic import BaseModel
class _Unset:
pass
UNSET = _Unset()
class M1:
a: Optional[int]
b: Annotated[Optional[int], UNSET] Or T = TypeVar('T')
Unset = Annotated[T, UNSET]
class M2:
a: Optional[int]
b: Unset[Optional[int]] Also, note that proper NoneType and EllipsisType types have been added to 3.10 and should be supported by mypy in a future release. |
Beta Was this translation helpful? Give feedback.
-
What would the value of |
Beta Was this translation helpful? Give feedback.
-
Yes exactly! I also would like this for TypedDict (which I compare with TS interfaces). Thanks for sharing this thread! |
Beta Was this translation helpful? Give feedback.
-
Looks like the PEP the first version of the PEP has been posted the typing-sig mailing list. https://mail.python.org/archives/list/typing-sig@python.org/thread/3NJZS7VCHSF54MD465SO7AF3AXGBGEDO/ It looks like the current version of the spec,
|
Beta Was this translation helpful? Give feedback.
-
Thanks for the update! I hope this is something that we'll be able to leverage. |
Beta Was this translation helpful? Give feedback.
-
Pipe operator for unions sounds great, I'm keen to support it, including backporting if it's reasonably easy. Here's what I would propose for the rest of this (I'm using the current class Model(BaseModel):
a: str # value required, None not allowed
b: Optional[str] # value required, None are allowed
c: str = None # value not required, but if it is provided it may not be None
c2: OptionalDisallowNone[str] = None # same as c but could satisfy mypy and friends (see discussion below)
d: Optional[str] = None # value not required, if it is provided it may be None
e: Optional[str] = Field(None, omit_default=True) # optional field with no default value #1270 Regarding #1270 and the desire for fields that can disappear. I think it's a case of "the tail wagging the dog", I don't think this is something you'd ever really want, it's only come up because of trying to match JSONSchema. I think we can achieve this with a kwarg to In particular I think reversing the meaning of Again, I tend towards respecting the silent majority who want to keep it simple, pythonic and intuitive rather than the vocal minority who want to support specific behaviour. I think the above (except For |
Beta Was this translation helpful? Give feedback.
-
Is there a reason for additional Can we use just e: Optional[str] = Field(omit_default=True) # optional field with no default value #1270 |
Beta Was this translation helpful? Give feedback.
-
Hi. Since we are into this typing stuff, I think it may be interesting to take a look at Clojure’s spec/select. The idea is explained in depth in Rich Hickey’s “Maybe Not” talk. Essentially the concept is to define the model field types and the optionality of the fields separately. In almost any FastAPI app there are many models for the same data: MyDataIn, MyDataOut, MyDataCreate, MyDataSearch. Usually each model has a subset of fields from the set of fields for MyData domain entity. This is a proliferation of types. What we might want to do instead is to define MyData type without specifying optionality for the fields in the model. The optionality then would be specified separately because it is context dependent. When we create MyData in the system we probably want to specify more fields than when we search MyData by MyData.ID. Same model but different field optionality depending on the context. |
Beta Was this translation helpful? Give feedback.
-
I agree. But I can't think of a way to tell a type checker whether field optional or not, depending on context. This might be solved with a plugin, but I'm sure it will still require multiple models. |
Beta Was this translation helpful? Give feedback.
-
I write this comment as a user of pydantic and an author of the pydantic PyCharm plugin.
I respect the policy. Someday, someone will implement a plugin for VSCode or a language server. It may be in Pyright. |
Beta Was this translation helpful? Give feedback.
-
So in summary we'd go with the "All fields without a default value would be required and all fields with a default value would be optional" rule (except if the default value is |
Beta Was this translation helpful? Give feedback.
-
Is there a way to specify an
|
Beta Was this translation helpful? Give feedback.
-
I don't understand how this works,
with error message:
How is it possible to have this UUID attribute that can be both None and UUID? |
Beta Was this translation helpful? Give feedback.
-
Hi everyone!
I'm playing a lot with the new builtin generic types in 3.9 and type union operator introduced in 3.10 (I even wrote a backport for 3.6+ since I love them so much 😆).
Playing with them and pydantic, I really feel like the API can be challenged for v2. The idea is:
All fields without a default value would be required and all fields with a default value would be optional.
For example
It would imply that
Optional[str]
does not mean "optional" but more "nullable" (which is what it is 😄)To define an optional field without actually setting a default value, I suggest we use
Ellipsis
. I like the fact that we don't need to import aUndefined
sentinel to say "no value at all".It would be a bit confusing when switching from v1 to v2 since
...
is currently used to define a required field but IMHO it makes more sense to write a model this way with a homogeneous API.For
Field
it would just meanField(description='')
is required whenField(..., description='')
is optional.For
create_model
it's the same@samuelcolvin @StephenBrown2 @MrMrRobat others WDYT?
Partially related issue #990
Related issue #1223
EDIT: I tried for the first time to convert an issue into a discussion and it works well 🎉 Love this new feature! 😄
I also add directly up here the proposal for TypedDict (thanks @mpkocher!) with a new syntax
Required[...]
.If they go with it, it can be interesting to have the same interface as
TypedDict
forBaseModel
and coBeta Was this translation helpful? Give feedback.
All reactions