pyanalyze.extensions

Extensions to the type system supported by pyanalyze. These can be imported at runtime and used in user code.

Several type system extensions are used with the Annotated type from PEP 593. This allows them to be gracefully ignored by other type checkers.

class pyanalyze.extensions.CustomCheck

A mechanism for extending the type system with user-defined checks.

To use this, create a subclass of CustomCheck that overrides the can_assign method, and place it in an Annotated annotation. The return value is equivalent to that of pyanalyze.value.Value.can_assign().

A simple example is LiteralOnly, which is also exposed by pyanalyze itself:

class LiteralOnly(CustomCheck):
    def can_assign(self, value: "Value", ctx: "CanAssignContext") -> "CanAssign":
        for subval in pyanalyze.value.flatten_values(value):
            if not isinstance(subval, pyanalyze.value.KnownValue):
                return pyanalyze.value.CanAssignError("Value must be a literal")
        return {}

def func(arg: Annotated[str, LiteralOnly()]) -> None:
    ...

func("x")  # ok
func(str(some_call()))  # error

It is also possible to customize checks in the other direction by overriding the can_be_assigned() method. For example, if the above CustomCheck overrode the can_be_assigned method instead, a value of type Annotated[str, LiteralOnly()] could only be passed to functions that take a Literal parameter.

A CustomCheck can also be generic over a TypeVar. To implement support for TypeVar, two more methods must be overridden:

class pyanalyze.extensions.LiteralOnly

Custom check that allows only values pyanalyze infers as literals.

Example:

def func(arg: Annotated[str, LiteralOnly()]) -> None:
    ...

func("x")  # ok
func(str(some_call()))  # error

This can be useful to prevent user-controlled input in security-sensitive APIs.

class pyanalyze.extensions.NoAny(deep: bool = False, allowed_sources: ~typing.Container[AnySource] = <factory>)

Custom check that disallows passing Any.

deep: bool = False

If true, disallow Any in nested positions (e.g., list[Any]).

allowed_sources: Container[AnySource]

Allow Any with these sources.

class pyanalyze.extensions.AsynqCallable(args: Literal[Ellipsis] | Tuple[object, ...], return_type: object)

Represents an asynq function (a function decorated with @asynq()).

Similar to Callable, but AsynqCallable also supports calls through .asynq(). Because asynq functions can also be called synchronously, an asynq function is assignable to a non-asynq function, but not the reverse.

The first argument should be the argument list, as for Callable. Examples:

AsynqCallable[..., int]  # may take any arguments, returns an int
AsynqCallable[[int], str]  # takes an int, returns a str
class pyanalyze.extensions.ParameterTypeGuard(varname: str, guarded_type: object)

A guard on an arbitrary parameter. Used with Annotated.

Example usage:

def is_int(arg: object) -> Annotated[bool, ParameterTypeGuard["arg", int]]:
    return isinstance(arg, int)
class pyanalyze.extensions.NoReturnGuard(varname: str, guarded_type: object)

A no-return guard on an arbitrary parameter. Used with Annotated.

If the function returns, then the condition is true.

Example usage:

def assert_is_int(arg: object) -> Annotated[bool, NoReturnGuard["arg", int]]:
    assert isinstance(arg, int)
class pyanalyze.extensions.HasAttrGuard(varname: str, attribute_name: object, attribute_type: object)

A guard on an arbitrary parameter that checks for the presence of an attribute. Used with Annotated.

A return type of Annotated[bool, HasAttrGuard[param, attr, type]] means that param has an attribute named attr of type type if the function returns True.

Example usage:

def has_time(arg: object) -> Annotated[bool, HasAttrGuard["arg", Literal["time"], int]]:
    attr = getattr(arg, "time", None)
    return isinstance(attr, int)

T = TypeVar("T", bound=str)

def hasattr(obj: object, name: T) -> Annotated[bool, HasAttrGuard["obj", T, Any]]:
    try:
        getattr(obj, name)
        return True
    except AttributeError:
        return False
class pyanalyze.extensions.TypeGuard(guarded_type: object)

Type guards, as defined in PEP 647.

New code should instead use typing_extensions.TypeGuard or (in Python 3.10 and higher) typing.TypeGuard.

Example usage:

def is_int_list(arg: list[Any]) -> TypeGuard[list[int]]:
    return all(isinstance(elt, int) for elt in arg)
class pyanalyze.extensions.ExternalType(type_path: str)

ExternalType is a way to refer to a type that is not imported at runtime. The type must be given as a string representing a fully qualified name.

Example usage:

from pyanalyze.extensions import ExternalType

def function(arg: ExternalType["other_module.Type"]) -> None:
    pass

To resolve the type, pyanalyze will import other_module, but the module using ExternalType does not have to import other_module.

typing.TYPE_CHECKING can be used in a similar fashion, but ExternalType can be more convenient when programmatically generating types. Our motivating use case is our database schema definition file: we would like to map each column to the enum it corresponds to, but those enums are defined in code that should not be imported by the schema definition.

pyanalyze.extensions.reveal_type(value: _T) _T

Inspect the inferred type of an expression.

Calling this function will make pyanalyze print out the argument’s inferred value in a human-readable format. At runtime it does nothing.

This is automatically exposed as a global during type checking, so in code that is not run at import, reveal_type() can be used without being imported.

Example:

def f(x: int) -> None:
    reveal_type(x)  # Revealed type is "int"

At runtime this returns the argument unchanged.

pyanalyze.extensions.reveal_locals() None

Reveal the types of all local variables.

When the type checker encounters a call to this function, it prints the type of all variables in the local scope.

This does nothing at runtime.

pyanalyze.extensions.assert_type(val: _T, typ: Any) _T

Assert the inferred static type of an expression.

When a static type checker encounters a call to this function, it checks that the inferred type of val matches the typ argument, and if it dooes not, it emits an error.

Example:

def f(x: int) -> None:
    assert_type(x, int)  # ok
    assert_type(x, str)  # error

This is useful for checking that the type checker interprets a complicated set of type annotations in the way the user intended.

At runtime this returns the first argument unchanged.

pyanalyze.extensions.assert_error() Iterator[None]

Context manager that asserts that code produces a type checker error.

Example:

with assert_error():  # ok
    1 + "x"

with assert_error():  # error: no error found in this block
    1 + 1
pyanalyze.extensions.deprecated(__msg: str) Callable[[_T], _T]

Indicate that a class, function or overload is deprecated.

Usage:

@deprecated("Use B instead")
class A:
    pass
@deprecated("Use g instead")
def f():
    pass
@deprecated("int support is deprecated")
@overload
def g(x: int) -> int: ...
@overload
def g(x: str) -> int: ...

When this decorator is applied to an object, the type checker will generate a diagnostic on usage of the deprecated object.

No runtime warning is issued. The decorator sets the __deprecated__ attribute on the decorated object to the deprecation message passed to the decorator.

See PEP 702 for details.

pyanalyze.extensions.get_overloads(fully_qualified_name: str) List[Callable[[...], Any]]

Return all defined runtime overloads for this fully qualified name.

pyanalyze.extensions.get_type_evaluations(fully_qualified_name: str) Sequence[Callable[[...], Any]]

Return the type evaluation function for this fully qualified name, or None.

pyanalyze.extensions.overload(func: Callable[[...], Any]) Callable[[...], Any]

A version of typing.overload that is inspectable at runtime.

If this decorator is used for a function some_module.some_function, calling pyanalyze.extensions.get_overloads("some_module.some_function")() will return all the runtime overloads.

pyanalyze.extensions.patch_typing_overload() None

Monkey-patch typing.overload with our custom @overload decorator.

This allows files imported after this file to use the @overload decorator and have it be recognized by pyanalyze.

pyanalyze.extensions.evaluated(func: Callable[[...], Any]) Callable[[...], Any]

Marks a type evaluation function.

pyanalyze.extensions.is_provided(arg: Any) bool

Helper function for type evaluators.

May not be called at runtime.

pyanalyze.extensions.is_positional(arg: Any) bool

Helper function for type evaluators.

May not be called at runtime.

pyanalyze.extensions.is_keyword(arg: Any) bool

Helper function for type evaluators.

May not be called at runtime.

pyanalyze.extensions.is_of_type(arg: Any, type: Any, *, exclude_any: bool = False) bool

Helper function for type evaluators.

May not be called at runtime.

pyanalyze.extensions.show_error(message: str, *, argument: Any | None = None) bool

Helper function for type evaluators.

May not be called at runtime.

pyanalyze.extensions.has_extra_keys(value_type: object = typing.Any) Callable[[_T], _T]

Decorator for TypedDict types, indicating that the dict has additional keys of the given type.

This is an experimental feature.

Example usage:

@has_extra_keys(str)
class TD(TypedDict):
    a: int

def f(x: TD) -> None:
    assert_type(x["a"], int)
    assert_type(x["arbitrary_key"], str)