-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
[2/n][pythonic config] Update config conversion logic to rely on annotation rather than Pydantic model type #16435
[2/n][pythonic config] Update config conversion logic to rely on annotation rather than Pydantic model type #16435
Conversation
Current dependencies on/for this PR:
This comment was auto-generated by Graphite. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good. I just have some questions about some code paths I do not understand (and should probably be commented?)
for key, field in self.__fields__.items(): | ||
if field.required and key not in modified_data: | ||
modified_data[key] = None | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@benpankow can you speak to what is going on with these changes? These look pretty core...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is to accommodate the changes in Pydantic 2.0 around the meaning of required and optional fields. This change ensures that all required fields are passed a value when calling the BaseModel __init__
.
In Pydantic 1.0, this is a no-op, since this is implicitly done behind the scenes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the context. Let's put this explanation in the code as a comment.
python_modules/dagster/dagster/_config/pythonic_config/conversion_utils.py
Outdated
Show resolved
Hide resolved
if safe_is_subclass(get_origin(potential_dagster_type), List): | ||
list_inner_type = get_args(potential_dagster_type)[0] | ||
return Array(_config_type_for_type_on_pydantic_field(list_inner_type)) | ||
elif _is_optional(potential_dagster_type): | ||
optional_inner_type = next( | ||
arg for arg in get_args(potential_dagster_type) if arg is not type(None) | ||
) | ||
return Noneable(_config_type_for_type_on_pydantic_field(optional_inner_type)) | ||
elif safe_is_subclass(get_origin(potential_dagster_type), Dict) or safe_is_subclass( | ||
get_origin(potential_dagster_type), Mapping | ||
): | ||
key_type, value_type = get_args(potential_dagster_type) | ||
return Map( | ||
key_type, | ||
_config_type_for_type_on_pydantic_field(value_type), | ||
) | ||
|
||
from .config import Config, infer_schema_from_config_class | ||
|
||
if safe_is_subclass(potential_dagster_type, Config): | ||
inferred_field = infer_schema_from_config_class( | ||
potential_dagster_type, | ||
) | ||
return inferred_field.config_type | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you comment as to what is going on here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes - here we're reworking the type case/switching in which we originally referred to the Pydantic internal type system. Now, we just check against the raw Python type annotations, which is simpler and works with Pydantic 2.0 (which eliminates the internal type system).
This logic is compacted a bit from what we had previously - it used to be done both here and in the _wrap_config_type
and _get_inner_field_if_exists
functions, which are no longer used.
4168aea
to
f00ba77
Compare
203badb
to
6c0fe9b
Compare
6c0fe9b
to
1f4df49
Compare
Deploy preview for dagit-core-storybook ready! ✅ Preview Built with commit a6aba5f. |
Deploy preview for dagit-storybook ready! ✅ Preview Built with commit 2c5ee94. |
1f4df49
to
b6e711d
Compare
b6e711d
to
039c24b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok one final pass.
It's also essential that you do a BK run across all Python versions before merging. I believe we can kick that off manually?
python_modules/dagster/dagster/_config/pythonic_config/resource.py
Outdated
Show resolved
Hide resolved
def is_optional(annotation: Type) -> bool: | ||
"""Returns true if the annotation is Optional[T] or Union[T, None].""" | ||
return ( | ||
get_origin(annotation) in (Union, UnionType) | ||
and len(get_args(annotation)) == 2 | ||
and type(None) in get_args(annotation) | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment isn't quite accurate. This returns true if any of the generic arguments to Union
is None
, I believe.
I think this is worth:
- Separating the cases of
Union
andUnionType
more clearly. Without really deep knowledge of this corner of the Python type system internals it is hard to understand how your description maps to the code. - Explaining to how
Optional
maps toUnion
, linking to docs if necessary.
This is the type of code that you can easily see causing bugs across different Python versions, so I think we should bias towards being super explicit and over-explaining. (That's kind of true for a bunch of the stuff in this PR).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed docstring + expanded in comments on some of the typing nuances.
039c24b
to
a627a90
Compare
Build against py3.8-3.11: https://buildkite.com/dagster/dagster/builds/67955 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great. Please heed final comments.
python_modules/dagster/dagster/_config/pythonic_config/conversion_utils.py
Outdated
Show resolved
Hide resolved
python_modules/dagster/dagster/_config/pythonic_config/resource.py
Outdated
Show resolved
Hide resolved
# The Python 3.10 pipe syntax evaluates to a UnionType | ||
# rather than a Union, so we need to check for that as well |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. Also I wonder why 3.10 does this. Seems like this will bite everyone.
e9127ce
to
2c5ee94
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, I don't see anything alarming
28fc043
to
a6aba5f
Compare
Merge activity
|
… values (#17242) ## Summary Updates our handling of passing defaulted values back into Pydantic model constructors. Previously, we'd pass in a model's config dictionary into its constructor, but this breaks the Pydantic [validator expectation](https://docs.pydantic.dev/latest/concepts/validators/#validation-of-default-values) that validators only run when a user explicitly provides input. This was already not working as expected, but was further obfuscated by some changes in #16435. ## Test Plan Adds some unit tests to test out this behavior in various config/resource scenarios.
Summary
PR stack which re-implements #16257, in hopes of making it easier to review.
This PR holds the meat of the actual changes. It reworks some of the underlying conversion code to look at the annotation (e.g. actual Python type objects) on config classes instead of the Pydantic internal type representations, when converting to Dagster config types under the hood.
Test Plan
Existing unit tests.