Expose pydantic config class to user#705
Expose pydantic config class to user#705dantownsend merged 1 commit intopiccolo-orm:masterfrom waldner:master
Conversation
Codecov Report
@@ Coverage Diff @@
## master #705 +/- ##
=======================================
Coverage 91.29% 91.30%
=======================================
Files 99 99
Lines 7238 7244 +6
=======================================
+ Hits 6608 6614 +6
Misses 630 630
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. |
extra policyextra policy; propagate choices to pydantic
|
@waldner Thanks a lot for this - it looks good. I need to check if passing the choices to the Pydantic model has any effect on Piccolo Admin. If so, we might just have to add an option - like @sinisaos I don't know if this is something we've tested before - do you remember? |
|
@dantownsend I only remember this PR and the integer validation didn't work. I hope this PR fixes that. We probably need to make changes to Piccolo Admin as well. |
|
My understanding is that piccolo admin was using the |
|
@waldner You're right - Piccolo Admin just uses the I'd better check quickly though, as having stronger validation on the Pydantic models might cause Piccolo Admin to fail in some way when posting back data. @sinisaos Thanks for looking into it. |
|
So FWIW, it does indeed seem that piccolo admin has trouble with the new changes. Using the following table schema: When I try to add a new row to the table using piccolo admin, no fields are shown and the following error is produced in the python interpreter (only the relevant part): presumably because piccolo admin would set those fiels as empty initially, or with a placeholder value not among the allowed choices. What about setting it to the first value of the enumeration? EDIT: in fact the error appears to come from |
|
If the propagation of |
|
@waldner As @dantownsend already said, Piccolo admin uses an piccolo/piccolo/columns/base.py Line 217 in 94c55f8 display_name to nice display in the select dropdown and the value it uses as data.
I think with your changes we would have to use schema definitions for choices, but we need to see how or ignore it for Piccolo Admin. |
|
It would be great if Piccolo Admin worked properly with Pydantic choices, but I'm not sure how much work it will be currently. So maybe lets move it out of this PR, and into another one, so we can get this merged in. |
|
Done. |
extra policy; propagate choices to pydanticextra policy
piccolo/utils/pydantic.py
Outdated
| deserialize_json: bool = False, | ||
| recursion_depth: int = 0, | ||
| max_recursion_depth: int = 5, | ||
| pydantic_extra_fields: str = "ignore", |
There was a problem hiding this comment.
Sorry - one minor thing. Can we make this:
pydantic_extra_fields: t.Literal["ignore", "allow", "forbid"] = "ignore",There was a problem hiding this comment.
Originally I actually wanted to do this:
pydantic_extra_fields: pydantic.Extra = pydantic.Extra.ignore
Is there a stylistic or other reason for your suggested change? (I'm really curious, not trying to be confrontational, I'll implement whatever you decide)
There was a problem hiding this comment.
Yeah, that works too:
pydantic_extra_fields: pydantic.Extra = pydantic.Extra.ignoreI don't think there's clearly a best. The reason I suggested Literal is it means a linter can detect typos in the string.
I think the Enum is probably better though. It's slightly more verbose to use, but means that if Pydantic adds another option in the future, we don't have to update Literal to include the new options.
Ideally it would be good to expose all of Pydantic's config options. We could take a different approach entirely, and do this:
def create_pydantic_model(pydantic_config: t.Type[BaseConfig] = BaseConfig, ...):
...
class CustomConfig(pydantic_config):
schema_extra = {
"help_text": table._meta.help_text,
**schema_extra_kwargs,
}
extra = pydantic.Extra(pydantic_extra_fields)
json_encoders: t.Dict[t.Any, t.Callable] = JSON_ENCODERS
arbitrary_types_allowed = TrueWhich means that every Pydantic option is exposed to the user. What do you think?
There was a problem hiding this comment.
Looks good to me . I'll work on this.
There was a problem hiding this comment.
I see there are quite a few places in piccolo-api that import Config from utils/pydantic, IIUC that class would now go away since we'd now be instantiating just CustomConfig based on pydantic_config, shall I (try to) adjust those other places too?
There was a problem hiding this comment.
Ah, yeah - good point. I wonder if we keep Config, then this is possible?
def create_pydantic_model(pydantic_config: t.Type[BaseConfig] = BaseConfig, ...):
...
class CustomConfig(Config, pydantic_config):
schema_extra = {
"help_text": table._meta.help_text,
**schema_extra_kwargs,
}
extra = pydantic.Extra(pydantic_extra_fields)I think the mro still works, even though both classes inherit from BaseConfig.
There was a problem hiding this comment.
Sorry for being a PITA, but now I wonder what we should do with the original pydantic_extra_fields parameter, as now the user could just do
class MyConfig(pydantic.BaseConfig):
extra = 'forbid'
model = create_pydantic_model(pydantic_config=MyConfig, ...)
and obtain the same result as passing pydantic_extra_fields='forbid'. I don't have a strong opinion on this, although using pydantic_extra_fields seems marginally easier. Comments?
There was a problem hiding this comment.
We probably don't need them both. I agree that having a pydantic_extra_fields argument is more convenient. But over time, as we support more Pydantic options, create_pydantic_model could end up with loads of arguments.
We could accept a dict, and anything passed into it automatically applied to the config (rather than having to pass in a config object):
create_pydantic_model(pydantic_config={'extra': Extra.ignore})It's more convenient, but there's no type checking. We could use TypedDict, but would constantly be updating it as Pydantic adds more arguments.
Sorry, I'm kind of thinking out loud here ... what do you think? I think passing in the Config is probably best.
`create_pydantic_model` accepts a new argument specifying the base class to use for the generated model config.
extra policy|
@waldner This is great, thanks! Sorry about all of the back and forth. I think we have a nice design. |
create_pydantic_modelaccepts a new argument specifying the base classto use for the generated model config.
Should partially fix #467.