CoreMetadata refactor with an emphasis on documentation and reducing complexity#10675
CoreMetadata refactor with an emphasis on documentation and reducing complexity#10675sydney-runkle merged 20 commits intomainfrom
CoreMetadata refactor with an emphasis on documentation and reducing complexity#10675Conversation
…lue is overridden by introduced complexity and overhead
Deploying pydantic-docs with
|
| Latest commit: |
2a08ddf
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://1b3fed65.pydantic-docs.pages.dev |
| Branch Preview URL: | https://metadata-stuck-on-a-flight.pydantic-docs.pages.dev |
CodSpeed Performance ReportMerging #10675 will not alter performanceComparing Summary
|
CoreMetadata refactor as a consequence of a very long flightCoreMetadata refactor with an emphasis on documentation and reducing complexity
…py and migrating to json_schema.py
…fers some typing support - to be improved with migration to pydantic-core
| pydantic_js_prefer_positional_arguments: bool | None = None, | ||
| pydantic_js_input_core_schema: CoreSchema | None = None, |
There was a problem hiding this comment.
This is probably overkill (these params specifically). Happy to remove. The others make more sense given that they need special update logic.
| def get_json_schema_update_func( | ||
| json_schema_update: JsonSchemaValue, json_schema_extra: JsonDict | typing.Callable[[JsonDict], None] | None | ||
| ) -> GetJsonSchemaFunction: | ||
| def json_schema_update_func( | ||
| core_schema_or_field: CoreSchemaOrField, handler: GetJsonSchemaHandler | ||
| ) -> JsonSchemaValue: | ||
| json_schema = {**handler(core_schema_or_field), **json_schema_update} | ||
| add_json_schema_extra(json_schema, json_schema_extra) | ||
| return json_schema | ||
|
|
||
| return json_schema_update_func | ||
|
|
||
|
|
||
| def add_json_schema_extra( | ||
| json_schema: JsonSchemaValue, json_schema_extra: JsonDict | typing.Callable[[JsonDict], None] | None | ||
| ): | ||
| if isinstance(json_schema_extra, dict): | ||
| json_schema.update(to_jsonable_python(json_schema_extra)) | ||
| elif callable(json_schema_extra): | ||
| json_schema_extra(json_schema) | ||
|
|
||
|
|
There was a problem hiding this comment.
Also, shouldn't be in json_schema.py
| def _update_class_schema(self, json_schema: JsonSchemaValue, cls: type[Any], config: ConfigDict) -> None: | ||
| """Update json_schema with the following, extracted from `config` and `cls`: | ||
|
|
||
| * title | ||
| * description | ||
| * additional properties | ||
| * json_schema_extra | ||
| * deprecated | ||
|
|
||
| Done in place, hence there's no return value as the original json_schema is mutated. | ||
| No ref resolving is involved here, as that's not appropriate for simple updates. |
There was a problem hiding this comment.
This now has lots of the logic that was mistakenly in _generate_schema.py
| schema_to_update = self.get_schema_from_definitions(JsonRef(ref)) | ||
| if schema_to_update is None: | ||
| raise RuntimeError(f'Cannot update undefined schema for $ref={ref}') | ||
| return self.resolve_ref_schema(schema_to_update) |
There was a problem hiding this comment.
Renamed and simplified. There's a function called this in GenerateJsonSchemaHandler, and ideally, we'd get rid of that eventually, so I think this helps draw some parallels to make that simplification easier in the future.
| def test_typeddict_with_extra_behavior_allow(): | ||
| class Model: | ||
| @classmethod | ||
| def __get_pydantic_core_schema__(cls, source_type: Any, handler: GetCoreSchemaHandler) -> CoreSchema: | ||
| return core_schema.typed_dict_schema( | ||
| {'a': core_schema.typed_dict_field(core_schema.str_schema())}, | ||
| extra_behavior='allow', | ||
| ) | ||
|
|
||
| assert TypeAdapter(Model).json_schema() == { | ||
| 'type': 'object', | ||
| 'properties': {'a': {'title': 'A', 'type': 'string'}}, | ||
| 'required': ['a'], | ||
| 'additionalProperties': True, | ||
| } |
There was a problem hiding this comment.
So, I think we should only be pulling the extra_behavior from the config here. If folks disagree, I can pull from the schema as well, though not sure why we have this setting there - seems redundant.
There was a problem hiding this comment.
extra items are going to be supported soon for typed dicts in Python. Is Pydantic support for it going to be easy to implement with these changes?
Coverage reportClick to see where and how coverage changed
This report was generated by python-coverage-comment-action |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
dmontagu
left a comment
There was a problem hiding this comment.
Generally looking good to me other than the couple comments I made above about the warning message; not sure if you plan to address any of the TODO comments you added before merging, but I'm okay even if they aren't addressed.
|
Wasn't planning on addressing the TODOs before merging - I feel like this is big enough already, and the callable stuff would represent a pretty significant structural change. I will work on getting the fastapi CI step to green, though. |
Viicos
left a comment
There was a problem hiding this comment.
Changes look reasonable and indeed having less stuff in the GenerateSchema class is much better.
Move the
CoreMetadatastructure to rust so that we can pull out some of thecastcalls and just get natural type checking benefits there.
Will we get any benefits to move things to Rust here? Will this introduce an extra level of indirection, making things harder to understand?
| def test_typeddict_with_extra_behavior_allow(): | ||
| class Model: | ||
| @classmethod | ||
| def __get_pydantic_core_schema__(cls, source_type: Any, handler: GetCoreSchemaHandler) -> CoreSchema: | ||
| return core_schema.typed_dict_schema( | ||
| {'a': core_schema.typed_dict_field(core_schema.str_schema())}, | ||
| extra_behavior='allow', | ||
| ) | ||
|
|
||
| assert TypeAdapter(Model).json_schema() == { | ||
| 'type': 'object', | ||
| 'properties': {'a': {'title': 'A', 'type': 'string'}}, | ||
| 'required': ['a'], | ||
| 'additionalProperties': True, | ||
| } |
There was a problem hiding this comment.
extra items are going to be supported soon for typed dicts in Python. Is Pydantic support for it going to be easy to implement with these changes?
My only thought here was then we could then type |
This is where moving this to rust would be super helpful, then we can type |
Yes, I haven't removed the |
I would rather support the PEP 728 added feature and slowly deprecate pulling the info from the config (or support both, but disallow having both specified at the same time?) |
Viicos
left a comment
There was a problem hiding this comment.
I'm also fine with removing extra_behavior for now, and re-add it in some way with PEP 728 coming soon
| if TYPE_CHECKING: | ||
| from ..annotated_handlers import GetJsonSchemaHandler | ||
| pass |
| # Remove the first one once that test is fixed, see https://github.com/pydantic/pydantic/pull/10029 | ||
| # the remaining tests all failing bc we now correctly add a `'deprecated': True` attribute to the JSON schema, | ||
| # So it's the FastAPI tests that need to be updated here | ||
| ./scripts/test.sh -vv \ |
There was a problem hiding this comment.
FYI I think the first one is already passing and the others should be fixed by https://github.com/fastapi/fastapi/pull/12971/files.
There was a problem hiding this comment.
Awesome, thanks!
If you wouldn't mind, could you open a PR against pydantic to remove these once your FastAPI PR is merged? Thanks!!
There was a problem hiding this comment.
I'd be happy to! Judging by my past experience with FastAPI I wouldn't expect that to happen any time soon 🙃
There was a problem hiding this comment.
Thanks so much!!
I would recommend reviewing this commit by commit, though looking over the whole thing afterwards gives some helpful perspective as well.
Changes in this PR:
CoreMetadataHandlerstructure - it offered minimal type checking benefits, some overhead, and a good amount of unnecessary complexity.CoreMetadatadict so that it's clear what we're adding to it and why._generate_schema.pyand_known_annotated_metadata.pyand consolidate it injson_schema.pyjson_schema_extraupdates, no longer build a callable ahead of time.TODOs, in the future:
CoreMetadatastructure to rust so that we can pull out some of thecastcalls and just get natural type checking benefits there. One hiccup here is that replacingmetadata: dict[str, Any]withmetadata: CoreMetadata, doesn't allow for arbitrary injection (say, in a custom schema), so I'm not sure what we want to do about that. It also then raises errors when we do our temporary injects for say, discriminator application, but we shouldn't be doing that anyway.pydantic_js_functionsandpydantic_js_annotation_functions