konradhalas / dacite

Simple creation of data classes from dictionaries.
MIT License
1.76k stars 106 forks source link

Try to use typing_extensions for Literal in < python 3.8 #100

Closed camerongraybill closed 4 months ago

camerongraybill commented 4 years ago

in dacite/types.py:66 it is directly importing from typing - could it also try importing from typing_extensions?

def is_literal(type_: Type) -> bool:
    try:
        from typing import Literal  # type: ignore

        return is_generic(type_) and type_.__origin__ == Literal
    except ImportError:
        return False

def is_literal(type_: Type) -> bool:
    try:
        from typing import Literal  # type: ignore

        return is_generic(type_) and type_.__origin__ == Literal
    except ImportError:
        try:
            from typing_extensions import Literal # type: ignore

            return is_generic(type_) and type_.__origin__ == Literal
       except ImportError:
            return False

I'm writing a parsing library for internal use that needs to work for both 3.7 and 3.8 - this would be super helpful.

camerongraybill commented 4 years ago

Should be linked to https://github.com/konradhalas/dacite/pull/101, not sure how to link.

konradhalas commented 4 years ago

Hi @camerongraybill - typing_extensions is an external lib, it's not provided with the standard library. I rejected similar request in the past - https://github.com/konradhalas/dacite/pull/93

But... it looks that many people are using it, so I will try to figure out something or at least I will update docs.

Thank your for PR - I will review it.

c-ghigny commented 4 years ago

For anybody else walking on this issue, if you really can't wait for an official fix, you can "hack" (monkey-patch) dacite to handle typing_extensions.Literal.

Do this, first thing in your main.py before using dacite for anything else

from dacite import types
from typing_extensions import Literal
setattr(types, 'is_literal', lambda type_: types.is_generic(type_) and type_.__origin__ == Literal)

Or, instead of the lambda, you can use the function you defined in #101


from dacite import types
from types import is_generic

def is_literal(type_: Type) -> bool:
    try:
        from typing import Literal  # type: ignore

        return is_generic(type_) and type_.__origin__ == Literal
    except ImportError:
        try:
            from typing_extensions import Literal  # type: ignore

            return is_generic(type_) and type_.__origin__ == Literal
        except ImportError:
            return False

setattr(types, 'is_literal', is_literal)
Agalin commented 3 years ago

It's also possible to do it the other way around (should be more future proof in case of dacite changes):

from typing_extensions import Literal
import typing
typing.Literal = Literal
from typing import Literal # no exception raised

Considering that in 3.8 those are the same object there is not a lot of danger in doing so.

Also keep in mind that typing_extensions is an official backport, not some random PyPI library, it resides in official PEP484 repo. Anyway what happened to #101?

Agalin commented 3 years ago

Ok. Never mind. Both solutions fail on python3.6 as typing_extensions.Literal is not a generic there so is_generic returns False and there is no __origin__.

It should be something like:

def is_literal(type_: Type) -> bool:
    try:
        from typing import Literal  # type: ignore

        return is_generic(type_) and type_.__origin__ == Literal
    except ImportError:
        try:
            from typing_extensions import Literal  # type: ignore

            return type(type_) == type(Literal)
        except ImportError:
            return False