Skip to content
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

Merged
merged 7 commits into from
Oct 11, 2023

Conversation

benpankow
Copy link
Member

@benpankow benpankow commented Sep 12, 2023

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.

@benpankow benpankow changed the title [pythonic config] Update config conversion logic to rely on annotation rather than Pydantic model type [2/n][pythonic config] Update config conversion logic to rely on annotation rather than Pydantic model type Sep 12, 2023
Copy link
Member

@schrockn schrockn left a 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

Copy link
Member

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...

Copy link
Member Author

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.

Copy link
Member

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.

Comment on lines 200 to 224
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

Copy link
Member

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?

Copy link
Member Author

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.

@benpankow benpankow force-pushed the benpankow/restructure-pythonic-config-module branch from 4168aea to f00ba77 Compare September 14, 2023 23:21
@benpankow benpankow force-pushed the benpankow/restructure-pythonic-config-module-2 branch from 203badb to 6c0fe9b Compare September 14, 2023 23:21
Base automatically changed from benpankow/restructure-pythonic-config-module to master September 14, 2023 23:48
@benpankow benpankow force-pushed the benpankow/restructure-pythonic-config-module-2 branch from 6c0fe9b to 1f4df49 Compare September 28, 2023 02:53
@github-actions
Copy link

github-actions bot commented Sep 28, 2023

Deploy preview for dagit-core-storybook ready!

✅ Preview
https://dagit-core-storybook-60jccysj3-elementl.vercel.app
https://benpankow-restructure-pythonic-config-module-2.core-storybook.dagster-docs.io

Built with commit a6aba5f.
This pull request is being automatically deployed with vercel-action

@github-actions
Copy link

github-actions bot commented Sep 28, 2023

Deploy preview for dagit-storybook ready!

✅ Preview
https://dagit-storybook-hounguv1v-elementl.vercel.app
https://benpankow-restructure-pythonic-config-module-2.components-storybook.dagster-docs.io

Built with commit 2c5ee94.
This pull request is being automatically deployed with vercel-action

@benpankow benpankow force-pushed the benpankow/restructure-pythonic-config-module-2 branch from 1f4df49 to b6e711d Compare September 28, 2023 03:16
@benpankow benpankow requested a review from schrockn September 28, 2023 03:33
@benpankow benpankow force-pushed the benpankow/restructure-pythonic-config-module-2 branch from b6e711d to 039c24b Compare September 28, 2023 03:33
Copy link
Member

@schrockn schrockn left a 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?

Comment on lines 26 to 32
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)
)
Copy link
Member

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:

  1. Separating the cases of Union and UnionType 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.
  2. Explaining to how Optional maps to Union, 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).

Copy link
Member Author

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.

@benpankow benpankow force-pushed the benpankow/restructure-pythonic-config-module-2 branch from 039c24b to a627a90 Compare October 1, 2023 22:03
@benpankow
Copy link
Member Author

Build against py3.8-3.11: https://buildkite.com/dagster/dagster/builds/67955

@benpankow benpankow requested a review from schrockn October 1, 2023 22:39
Copy link
Member

@schrockn schrockn left a 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.

Comment on lines +43 to +44
# The Python 3.10 pipe syntax evaluates to a UnionType
# rather than a Union, so we need to check for that as well
Copy link
Member

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.

@benpankow benpankow force-pushed the benpankow/restructure-pythonic-config-module-2 branch from e9127ce to 2c5ee94 Compare October 10, 2023 16:51
Copy link
Collaborator

@smackesey smackesey left a 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

@benpankow benpankow force-pushed the benpankow/restructure-pythonic-config-module-2 branch from 28fc043 to a6aba5f Compare October 11, 2023 22:45
@benpankow
Copy link
Member Author

benpankow commented Oct 11, 2023

Merge activity

@benpankow benpankow merged commit dcd28b7 into master Oct 11, 2023
2 of 3 checks passed
@benpankow benpankow deleted the benpankow/restructure-pythonic-config-module-2 branch October 11, 2023 23:24
benpankow added a commit that referenced this pull request Oct 17, 2023
… 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants