Currently we have the class Schema for when you need to define more stuff on a field than just the type and default.
But this is a confusing name since not all it's arguments refer to the schema. It'll get more confusing if we want to add more arguments to Schema/Field, eg. a more complex alias system #477.
Therefore we should rename it to Field.
This would require:
Field class, perhaps renaming the entire fields.py module.Schema needs to carry on working for a while but with a deprecation warningWhat does everyone thing?
@tiangolo Schema was your work originally, are you ok with this?
Thanks for tagging me. Yep. I think it makes sense.
If we can keep supporting it for a while with a deprecation warning, we should be fine.
And thinking about it, it actually makes more sense for it to be Field.
Slightly related, currently Schema (soon to be Field) is a class. When declaring a Pydantic field with:
a: int = Schema(...)
mypy complains that a should be of type int but is being of type Schema.
One way I'm "hacking" it in FastAPI (for stuff that inherits from Schema) is to create a function with a return type annotation of Any (while it is returning a Schema instance).
Then because the return of the function is annotated as Any, mypy accepts it.
I think this is something that could be interesting to have here too, maybe worth considering during this change.
In favour of this change too.
This seems to align better with dataclass terminology too, https://docs.python.org/3/library/dataclasses.html#dataclasses.field.
For consideration: is there a possibility that the similarity would be a bad thing, rather than good?
I agree with @jasonkuhrt that using a field function would be nice. Being a function that actually returns a class would fix mypy errors when using Schema (or the next, Field), as a function can be typed to return Any. In fact, I'm doing something like that in FastAPI.
Also, by implementing a field function right away we could update the docs and show the new interface to new users, even while Schema is not yet renamed to Field (and the current Field to something else).
I wanna take a shot at this.
I have some naming questions:
field function exposed as the main interface instead of the corresponding class?Field) that simulates the class name, even while it's a function?Field class?On the other side, having just a function field that returns a Schema class would solve the visible naming issue for users, that could be enough, without needing to rename the underlying classes. The drawback is that the internal names would be more difficult for newcomer contributors to the internal code.
I think the function should be called Field, it should have an alias called Schema that also returns a Field but also raises a warning.
The only question is what do we call the class? It would be confusing to also call it Field, how about FieldInfo?
Please take a shot. I want to work on v1 features once v0.32 is released.
Perfect.
So, the current Schema class will be called FieldInfo?
And how should the current Field class be called? FieldAttr, FieldObject, FieldElement, Attr, (something else). Or should we keep it just Field?
In summary, the next parts will be:
Field function that returns a FieldInfo class instance.Schema function that returns a FieldInfo class instance and logs a warning.FieldInfo class that will replace the current Schema class.Field class (or do we just keep this Field class)?Looks good, I think current field becomes Attribute, we should also rename that file.
Correction, Attribute is a dumb name since then you would have model.__fields__: Dict[str, Attribute] which is confusing.
Better to rename what used to be called Field as ModelField?
+1 for ModelField, FieldInfo, and Field function that returns a FieldInfo
This may be controversial, but the library attrs supported typing (before python 3.6) using a type parameter. Field could have two positional arguments (type and default) then the Field function could actually return the correct type (using a Generic).
class Foo(BaseModel):
maximum = Field(int, 100)
# this would require a different code path
class Bar(BaseModel):
maximum: int
# this would be bad but possible
class Foo(BaseModel):
maximum: int = Field(str, "100")
Would having the type as an arg to the Field method provide any additional benefits?
I see a variety of approaches to the possible signature of the Field function; I've included my breakdown below. (I use FieldType to refer to the type of the object actually returned by Field.)
I'm most in favor of one of the first two approaches, and would be fine with the first one (the current proposal).
def Field(default: Any, **etc) -> Anypython
@overload
def Field(default: T, **etc) -> T: ...
@overload
def Field(default: None, **etc) -> Any: ...
@overload
def Field(default: Ellipsis, **etc) -> Any: ...
def Field(default: Any, **etc) -> Any: ...
FieldTypeField function as a FieldType.def Field(type_: Type[T], default: T, **etc) -> TFieldTypedef Field(type_: Type[T], default: T, **etc) -> AnyFieldType)FieldType is generic and Field returns a FieldType[T] (this is actually what attrs does, though the library mechanics are sufficiently different to cause different issues)def Field(default: T, **etc) -> FieldType[T] def Field(default: T, type_: Type[T], **etc) -> FieldType[T]@skewty do those points change your thinking at all?
@dmontagu I hadn't mentally considered "Con: Incorrect type hint if you actually want to treat the result as a FieldType". Thank you.
Given the above, I would choose method 1.
@skewty for what it鈥檚 worth, I鈥檓 not sure it鈥檚 actually that big of a downside for the following reasons:
FieldType class directlyField function anywayField as a FieldType anywayField is going to be rarely/never used to produce something interacted with as a FieldType, it might make sense to just use # type: ignore in the few places you use for that purpose anyway.I created a separate issue for this, but there may be another alternative: typing_extensions.Annotated (now supported by mypy). See #837 for more detail; this would allow us to remove the Field function that returns a FieldType but is annotated as returning Any.
@tiangolo this could also be useful to support in fastapi (where I could grab the Depends from the associated Annotated data, rather than assigning a default value).
Most helpful comment
Perfect.
So, the current
Schemaclass will be calledFieldInfo?And how should the current
Fieldclass be called?FieldAttr,FieldObject,FieldElement,Attr, (something else). Or should we keep it justField?In summary, the next parts will be:
Fieldfunction that returns aFieldInfoclass instance.Schemafunction that returns aFieldInfoclass instance and logs a warning.FieldInfoclass that will replace the currentSchemaclass.Fieldclass (or do we just keep thisFieldclass)?