Python typing - is there a way to avoid importing of optional type if it's None?

Viewed 3334

Let's say we have a function definition like this:

def f(*, model: Optional[Type[pydantic.BaseModel]] = None)

So the function doesn't require pydantic to be installed until you pass something as a model. Now let's say we want to pack the function into pypi package. And my question is if there's a way to avoid bringing pydantic into the package dependencies only the sake of type checking?

3 Answers

I tried to follow dspenser's advice, but I found mypy still giving me Name 'pydantic' is not defined error. Then I found this chapter in the docs and it seems to be working in my case too:

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    import pydantic

You can use normal clases (instead of string literals) with __future__.annotations (python 3.8.1):

from __future__ import annotations

from typing import TYPE_CHECKING, Optional, Type

if TYPE_CHECKING:
    import pydantic


def f(*, model: Optional[Type[pydantic.BaseModel]] = None):
    pass

If for some reason you can't use __future__.annotations, e.g. you're on python < 3.7, use typing with string literals from dspenser's solution.

Python's typing module can use string type names, as well as the type itself, as you have in your example.

If you don't want the type to be evaluated when the code is run, and an exception thrown if the module has not been imported, then you may prefer to use the string name. Your code snippet would then be:

def f(*, model: Optional[Type["pydantic.BaseModel"]] = None)

Your static type checking using mypy should continue to work, but your package will no longer always require the dependency on pydantic.

As you discovered in the docs, python's typing module includes a way to specify that some imports are only required for (string literal) type annotations:

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    import pydantic

This will execute the import pydantic statement only when TYPE_CHECKING is True, primarily when using mypy to analyze the types in the code. When running the program normally, the import statement is not executed. For this reason, the option is only to be used with string literal type hints; otherwise, the missing import will cause the program's normal execution to fail.

As lazy evaluation of type hints is to be implemented in Python 4.0,

from __future__ import annotations

can be used to enable this behaviour in Python 3. With this import statement, string literal types are not required.

I would do something like

try:
    from pydantic import BaseModel as PydanticBaseModel
except ImportError:
    PydanticBaseModel = None

I don't think it's better than what dspencer proposed, but sometimes some weird styleguide may forbid you from using string type names, so I just wanted to point another solution out.

Related