-
-
Notifications
You must be signed in to change notification settings - Fork 382
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
fix: DTO factory narrowed with a generic alias. #2791
base: main
Are you sure you want to change the base?
Conversation
12dcc37
to
b69ae5f
Compare
This PR is an attempt at handling DTOs that are narrowed with a `_GenericAlias` of a type supported by the DTO factory type. Closes #2500
b69ae5f
to
2f2df02
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.
This'll need a few iterations.
@@ -110,7 +111,7 @@ def __init__( | |||
rename_fields=self.dto_factory.config.rename_fields, | |||
) | |||
self.transfer_model_type = self.create_transfer_model_type( | |||
model_name=model_type.__name__, field_definitions=self.parsed_field_definitions | |||
model_name=(get_origin(model_type) or model_type).__name__, field_definitions=self.parsed_field_definitions |
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.
With this feature, anywhere that we could previously assume that the type that narrowed the dto was just a regular class, we now have to account for the fact that it could be an instance of _GenericAlias
.
In the my first pass of this PR I've taken the quickest and dirtiest approach to get things passing, but we might need some abstraction over the type that handles the differences.
@@ -29,7 +31,8 @@ class DataclassDTO(AbstractDTO[T], Generic[T]): | |||
def generate_field_definitions( | |||
cls, model_type: type[DataclassProtocol] | |||
) -> Generator[DTOFieldDefinition, None, None]: | |||
dc_fields = {f.name: f for f in fields(model_type)} | |||
model_origin = get_origin(model_type) or model_type | |||
dc_fields = {f.name: f for f in fields(model_origin)} |
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.
We'll have to test this against all of the other dto factory types that support generics too, b/c there is bound to be cases where the _GenericAlias
instances break things in there too.
if isinstance(self.annotation, _GenericAlias) and self.origin not in (ClassVar, Literal): | ||
cl_args = get_args(cl) | ||
cl_origin = get_origin(cl) or cl | ||
return ( | ||
issubclass(self.origin, cl_origin) | ||
and (len(cl_args) == len(self.args) if cl_args else True) | ||
and ( | ||
all(t.is_subclass_of(cl_arg) for t, cl_arg in zip(self.inner_types, cl_args)) | ||
if cl_args | ||
else True | ||
) | ||
) | ||
|
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.
Trying to determine when a _GenericAlias
type should be considered a sub-type of another type.
This says that when A
is a _GenericAlias
instance, then it is a subclass of B
when:
- if
B
is parametrized, thenA
s origin is a subtype ofB
s origin, otherwiseA
's origin is a subtype ofB
. - if
B
is parametrized, thenA
andB
must have the same number of type parameters declared - if
B
is parametrized, thenA
s type params are pairwise subtypes ofB
s type params
With 2 and 3, if B
is not parametrized, and origin of A
is a subtype of B
then I think we should treat that as B[Any, ...]
so the type parameters only come into it if both types have args declared.
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.
I'll have to add a lot of tests for this.
if not (parameters := getattr(annotation, "__parameters__", None)): | ||
return type_hints | ||
typevar_map = {p: p for p in parameters} |
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 support using this function without knowing up front if the type we are passing in is a generic type or not. If not, it won't have the parameters attribute, and so we just return the type hints without any post processing.
@@ -161,3 +163,28 @@ class SubType(Model): | |||
assert ( | |||
dto_type._dto_backends["handler_id"]["data_backend"].parsed_field_definitions[-1].name == "c" # pyright: ignore | |||
) | |||
|
|||
|
|||
def test_type_narrowing_with_generic_type() -> 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.
should name it test_get_model_type_hints_with_generic_type()
Kudos, SonarCloud Quality Gate passed! |
Documentation preview will be available shortly at https://litestar-org.github.io/litestar-docs-preview/2791 |
Pull Request Checklist
Description
This PR is an attempt at handling DTOs that are narrowed with a
_GenericAlias
of a type supported by the DTO factory type.Close Issue(s)
Closes #2500