-
Notifications
You must be signed in to change notification settings - Fork 3
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
Corner case: What about built-in generics' subclasses? #9
Comments
The easiest (and my personal) approach would be to assume the following:
We can make the |
It's an interesting point that I hadn't thought about. I imagine a fix would have to replace |
This is a working idea in most cases, but a bit hackish. If we look up the MRO of the origin, like So, for example: eval_type_backport(ForwardRef('DictSubclass[str, int]'), globals()) would give Annotated[typing.Dict[str, int], AfterValidator(DictSubclass)] |
Replacing You'll have to elaborate your pydantic validator idea. But it sounds like it's adding worrying complexity. |
Sorry, I submitted the message before finishing it 😄 |
Hence the |
Alright, I think I have the best idea without |
What about In any case, I think it should simply return an accurate type, not some combination of pieces like this. Even within pydantic, this should work smoothly outside of just validation. And really this package shouldn't know about pydantic at all. It'd also be acceptable to just raise an informative error in this case. The user just has to fix one or maybe a handful of class definitions, and we can tell them exactly how. The inconvenience generally won't 'scale up' the way it does with annotating each field with |
I found a solution that works for this example, doesn't know pydantic, and scales well. from __future__ import annotations
import typing
from typing import TYPE_CHECKING, TypeVar
from eval_type_backport.eval_type_backport import new_generic_types
if TYPE_CHECKING:
from typing import Any
_T = TypeVar('_T')
def get_mro_entries(obj: Any, bases: tuple[type[Any], ...]) -> tuple[type[Any], ...]:
"""Return the MRO entries for an object."""
if hasattr(obj, '__mro_entries__'):
return obj.__mro_entries__(bases)
if isinstance(obj, type):
return (obj,)
raise TypeError(f'{obj!r} is not a type')
def backport(subclass: type[_T]) -> type[_T]:
"""Backport a built-in generic type to Python 3.10+."""
bases = subclass.__bases__
for base in bases:
generic_base_name = new_generic_types.get(base)
if generic_base_name is not None:
generic_base = getattr(typing, generic_base_name)
# get_args() and get_origin() are too high-level for this
subclass.__origin__ = generic_base.__origin__
subclass.__parameters__ = generic_base.__parameters__
self_index = bases.index(base)
new_bases = get_mro_entries(
generic_base,
(*bases[:self_index], generic_base, *bases[self_index + 1 :]),
)
subclass.__bases__ = new_bases
break
return subclass
@backport
class DictSubclass(dict):
pass
print(DictSubclass.__bases__) # (<class 'dict'>, <class 'typing.Generic'>)
print(DictSubclass[int, str]) # __main__.DictSubclass[int, str] Tested on Python 3.8. |
In order to support built-in generics outside |
It feels dangerous to dynamically modify This whole library is already questionable on several levels, let's not get carried away on this corner case. I'd rather just produce a helpful error message than try to magically solve every possible problem for the user. |
Also this program: class D(dict):
pass
d: D[str, int] = D() produces this error on the latest |
I fully agree. Monkey-patching the class isn't good a direction. Let's discuss how the traceback should look like. This is my suggestion:
Well, unless the other one isn't clear enough. That would be just a little less work. |
I am wondering what would happen if the user uses a subclass that comes from somewhere else, for example from some other library... that quickly gets pretty complicated. But this is like a corner case of a corner case... probably? |
It would be nice to write a troubleshooting document of |
Start with a PR that just does the bare minimum, raising an exception with an appropriate message. |
To ensure full compatibility, I think that
should not raise an error on Python 3.8, since it doesn't on Python 3.9.
Normally you would re-bind the Generic signature, using
but we know that in Python 3.8 it's simply impossible without subclassing
typing.Dict
.What do you think?
The text was updated successfully, but these errors were encountered: