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.