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 thecan_assign
method, and place it in anAnnotated
annotation. The return value is equivalent to that ofpyanalyze.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 aboveCustomCheck
overrode thecan_be_assigned
method instead, a value of typeAnnotated[str, LiteralOnly()]
could only be passed to functions that take aLiteral
parameter.A
CustomCheck
can also be generic over aTypeVar
. To implement support forTypeVar
, two more methods must be overridden:walk_values()
should yield allTypeVar
objects contained in the check, wrapped in apyanalyze.value.TypeVarValue
.substitute_typevars()
takes a map fromTypeVar
topyanalyze.value.Value
objects and returns a newCustomCheck
.
- 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]).
- class pyanalyze.extensions.AsynqCallable(args: Literal[Ellipsis] | Tuple[object, ...], return_type: object)¶
Represents an asynq function (a function decorated with
@asynq()
).Similar to
Callable
, butAsynqCallable
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)