Can type bounds/constraints be stored in global variables and later used in generic function definitions with Python 3.14+?
10:37 31 Mar 2026

With PEP 649 in Python 3.14+ annotations are lazily evaluated by default, and this led me to wonder if it's acceptable to store type bounds or constraints in a global variable at the module level and use said variable in the definition of generic functions.

Some background:
I'm implementing a thin validation layer over dict data loaded in from a JSON file (with json.load), and I considered defining a few type aliases and type tuples for the data types that the loaded dictionary may contain, for example:

# in types.py
type Scalar = bool | int | str |float
SCALAR_TYPES = (bool, int, str, float)

In a separate extract.py module I wanted to define a generic function to extract a value from the dictionary and verify that it is an instance of an expected type, like this:
def extract_scalar[T: Scalar](data: dict, key: str, expected: type[T]) -> T: ...
or even as:
def extract_scalar[T: SCALAR_TYPES](data: dict, key: str, expected: type[T]) -> T: ...

With my understanding being that the first signature bounds T to any of the subtypes of the Scalar type union, and the second signature constrains T to be one of the types in the SCALAR_TYPES tuple.

I've tested both approaches and verified that they work with Python 3.14 both at runtime and during static type checking with mypy and pyright.

My curiosity is whether this approach is "allowed" or fully supported, and whether it could fall within the umbrella of best practices. It feels like this should be valid, especially since it offers a cleaner alternative to duplicating the type union (or tuple) every time it's needed (as PEP 695 seems to suggest by requiring that type constraints to be a literal tuple expression).

python python-3.14