pycroscope.extensions

Extensions to the type system supported by pycroscope. 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 pycroscope.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 pycroscope.value.Value.can_assign().

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

class LiteralOnly(CustomCheck):
    def can_assign(self, value: "Value", ctx: "CanAssignContext") -> "CanAssign":
        for subval in pycroscope.value.flatten_values(value):
            if not isinstance(subval, pycroscope.value.KnownValue):
                return pycroscope.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 pycroscope.extensions.PredicateCheck

Base class for checks that represent a predicate constraint.

class pycroscope.extensions.LiteralOnly

Custom check that allows only values pycroscope 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 pycroscope.extensions.NoAny(deep: bool = False, allowed_sources: ~collections.abc.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 pycroscope.extensions.ValidRegex

Custom check that allows only values that are valid regular expressions.

Example:

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

func(".*")  # ok
func("[")  # error
class pycroscope.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 pycroscope.extensions.Intersection(args: tuple[object, ...])

An intersection of two (or more) types.

The intersection of two fully static types is the static type that contains all elements common to both types. The intersection of two gradual types is a gradual type that can materialize to all types that are materializations of both elements of the intersection.

class pycroscope.extensions.Overlapping(arg: object)

A type that accepts values whose type overlaps with another type.

Overlapping[T] accepts a value of type U if T & U is not Never.

class pycroscope.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 pycroscope.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 pycroscope.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 pycroscope.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 pycroscope.extensions import ExternalType

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

To resolve the type, pycroscope 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.

pycroscope.extensions.reveal_type(value: _T) _T

Inspect the inferred type of an expression.

Calling this function will make pycroscope 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.

pycroscope.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.

pycroscope.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.

pycroscope.extensions.assert_error() Generator[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
pycroscope.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.

pycroscope.extensions.get_overloads(fully_qualified_name: str) list[Callable[[...], Any]]

Return all defined runtime overloads for this fully qualified name.

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

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

pycroscope.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 pycroscope.extensions.get_overloads("some_module.some_function") will return all the runtime overloads.

pycroscope.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 pycroscope.

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

Marks a type evaluation function.

pycroscope.extensions.is_provided(arg: Any) bool

Helper function for type evaluators.

May not be called at runtime.

pycroscope.extensions.is_positional(arg: Any) bool

Helper function for type evaluators.

May not be called at runtime.

pycroscope.extensions.is_keyword(arg: Any) bool

Helper function for type evaluators.

May not be called at runtime.

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

Helper function for type evaluators.

May not be called at runtime.

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

Helper function for type evaluators.

May not be called at runtime.

pycroscope.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)
class pycroscope.extensions.EnumName

A type representing the names of members of an enum.

Equivalent to a Literal type, but using this will produce nicer error messages for users.