How to evaluate Forward/Strings Refs
Hi folks, I'm working on a library that makes extensive of use of type hints at runtime and I found this use case where I need to evaluate a forward reference ```python def test_forward_references(): @dataclasses.dataclass class User: username: str @dataclasses.dataclass class Product: reviews: List["Review"] @dataclasses.dataclass class Review: text: str ``` as youy can see List["Review"] has a reference to the not yet declared class Review. My use case is to generate class based on the type hints of these 3 classes, unfortunately when I try to inspect the field type of Product.reviews, I get List[ForwardRef("Review)] and I'm not sure how I could get the actual type at run time. I've made a repl that shows what I've tried so far: https://repl.it/@patrick91/dcth-2 I've tried getting the global namespace for the module, but that won't work in this case, since the classes are defined inside a function. I have another option, which is to store all the decorated classes in a global dict (I'm using a different decorator that the dataclasses one), but I don't want to do that as my users could register multiple types with the same name. Any ideas on how I could solve this?
Perhaps you can pass the locals dict to get_type_hints()? https://docs.python.org/3/library/typing.html#typing.get_type_hints On Thu, Jul 16, 2020 at 06:52 Patrick Arminio <patrick.arminio@gmail.com> wrote:
Hi folks, I'm working on a library that makes extensive of use of type hints at runtime and I found this use case where I need to evaluate a forward reference
```python def test_forward_references():
@dataclasses.dataclass class User: username: str
@dataclasses.dataclass class Product: reviews: List["Review"]
@dataclasses.dataclass class Review: text: str ```
as youy can see List["Review"] has a reference to the not yet declared class Review. My use case is to generate class based on the type hints of these 3 classes, unfortunately when I try to inspect the field type of Product.reviews, I get List[ForwardRef("Review)] and I'm not sure how I could get the actual type at run time.
I've made a repl that shows what I've tried so far: https://repl.it/@patrick91/dcth-2
I've tried getting the global namespace for the module, but that won't work in this case, since the classes are defined inside a function.
I have another option, which is to store all the decorated classes in a global dict (I'm using a different decorator that the dataclasses one), but I don't want to do that as my users could register multiple types with the same name.
Any ideas on how I could solve this? _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: guido@python.org
-- --Guido (mobile)
On Thu, 16 Jul 2020 at 15:17, Guido van Rossum <guido@python.org> wrote:
Perhaps you can pass the locals dict to get_type_hints()?
locals would probably the right options, but unfortunately I don't have access to the locals where the type was defined, as I'd need to call get_type_hints from another function. Maybe I'm missing something obvious?
On Thu, Jul 16, 2020 at 07:21 Patrick Arminio <patrick.arminio@gmail.com> wrote:
On Thu, 16 Jul 2020 at 15:17, Guido van Rossum <guido@python.org> wrote:
Perhaps you can pass the locals dict to get_type_hints()?
locals would probably the right options, but unfortunately I don't have access to the locals where the type was defined, as I'd need to call get_type_hints from another function.
Maybe I'm missing something obvious?
Looks like you have to change your protocol. Why are those classes hidden inside a function instead of living in a module? -- --Guido (mobile)
On Thu, 16 Jul 2020 at 15:24, Guido van Rossum <guido@python.org> wrote:
On Thu, Jul 16, 2020 at 07:21 Patrick Arminio <patrick.arminio@gmail.com> wrote:
On Thu, 16 Jul 2020 at 15:17, Guido van Rossum <guido@python.org> wrote:
Perhaps you can pass the locals dict to get_type_hints()?
locals would probably the right options, but unfortunately I don't have access to the locals where the type was defined, as I'd need to call get_type_hints from another function.
Maybe I'm missing something obvious?
Looks like you have to change your protocol. Why are those classes hidden inside a function instead of living in a module?
Mainly because I'm writing them in tests. I guess I could "force" users of the library to not do something like that, as it is a very specific use case (I can't think of anything else other than tests for something like this). So, I'm guess the only solution would be to move the classes outside the tests and import the module dynamically, when dealing with ForwardRefs, like I did here? [...snip] def get_first_field_type(type: Type) -> Type: field = dataclasses.fields(type)[0] field_type = field.type # one option would be to use something like this, but it would # only work when the type was declared in the module namespace # and also might introduce side effects if the imported module # runs code at import time # def resolve_hint(hint): # def f(x: field_type): pass # namespace = importlib.import_module(type.__module__).__dict__ # return get_type_hints(f, None, namespace)['x'] -- Patrick Arminio
That’s probably best. I have seen ‘global SomeClass’ used in tests as a hack, too. On Thu, Jul 16, 2020 at 07:32 Patrick Arminio <patrick.arminio@gmail.com> wrote:
On Thu, 16 Jul 2020 at 15:24, Guido van Rossum <guido@python.org> wrote:
On Thu, Jul 16, 2020 at 07:21 Patrick Arminio <patrick.arminio@gmail.com> wrote:
On Thu, 16 Jul 2020 at 15:17, Guido van Rossum <guido@python.org> wrote:
Perhaps you can pass the locals dict to get_type_hints()?
locals would probably the right options, but unfortunately I don't have access to the locals where the type was defined, as I'd need to call get_type_hints from another function.
Maybe I'm missing something obvious?
Looks like you have to change your protocol. Why are those classes hidden inside a function instead of living in a module?
Mainly because I'm writing them in tests. I guess I could "force" users of the library to not do something like that, as it is a very specific use case (I can't think of anything else other than tests for something like this).
So, I'm guess the only solution would be to move the classes outside the tests and import the module dynamically, when dealing with ForwardRefs, like I did here?
[...snip]
def get_first_field_type(type: Type) -> Type: field = dataclasses.fields(type)[0]
field_type = field.type
# one option would be to use something like this, but it would # only work when the type was declared in the module namespace # and also might introduce side effects if the imported module # runs code at import time
# def resolve_hint(hint): # def f(x: field_type): pass # namespace = importlib.import_module(type.__module__).__dict__ # return get_type_hints(f, None, namespace)['x']
-- Patrick Arminio
-- --Guido (mobile)
On Thu, Jul 16, 2020 at 08:18 Patrick Arminio <patrick.arminio@gmail.com> wrote:
On Thu, 16 Jul 2020 at 15:58, Guido van Rossum <guido@python.org> wrote:
That’s probably best. I have seen ‘global SomeClass’ used in tests as a hack, too.
Thank you both. So the importlib "hack" is a good way to fetch the global scope of the module, right?
Sure. Though sys.modules should also work.
-- --Guido (mobile)
On 16 Jul 2020, at 16:32, Patrick Arminio <patrick.arminio@gmail.com> wrote:
On Thu, 16 Jul 2020 at 15:24, Guido van Rossum <guido@python.org> wrote:
On Thu, Jul 16, 2020 at 07:21 Patrick Arminio <patrick.arminio@gmail.com> wrote: On Thu, 16 Jul 2020 at 15:17, Guido van Rossum <guido@python.org> wrote: Perhaps you can pass the locals dict to get_type_hints()?
locals would probably the right options, but unfortunately I don't have access to the locals where the type was defined, as I'd need to call get_type_hints from another function.
Maybe I'm missing something obvious?
Looks like you have to change your protocol. Why are those classes hidden inside a function instead of living in a module? Mainly because I'm writing them in tests. I guess I could "force" users of the library to not do something like that, as it is a very specific use case (I can't think of anything else other than tests for something like this).
So, I'm guess the only solution would be to move the classes outside the tests and import the module dynamically, when dealing with ForwardRefs, like I did here?
[...snip]
def get_first_field_type(type: Type) -> Type: field = dataclasses.fields(type)[0]
field_type = field.type
# one option would be to use something like this, but it would # only work when the type was declared in the module namespace # and also might introduce side effects if the imported module # runs code at import time
# def resolve_hint(hint): # def f(x: field_type): pass # namespace = importlib.import_module(type.__module__).__dict__ # return get_type_hints(f, None, namespace)['x']
-- Patrick Arminio _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: jakub@stasiak.at
I work around this by declaring global MyClass before class MyClass: … And I have a finally: del MyClass block in the test function where I needed a similar behavior. Jakub
participants (3)
-
Guido van Rossum
-
Jakub Stasiak
-
Patrick Arminio