typing_extensions

repository·main·Indexed 20 days ago

https://github.com/python/typing_extensions

A library providing backported and experimental type system features for Python 3.9+. It complements the standard typing module by offering runtime support for type hints specified in PEPs, allowing developers to use new typing features on older Python versions or experiment with proposed PEPs before they are officially integrated into CPython.

Tokens
12.8K
Snippets
52
Records
64
Agent score
70%

What's inside typing_extensions

  1. What is typing_extensions and when to use it

    main

    typing_extensions is a library that complements the standard-library typing module. It provides runtime support for type hints specified in PEPs (Python Enhancement Proposals).

    Use typing_extensions for two main purposes:

    1. Backporting features: Use new type system features (like typing.TypeGuard) on older Python versions that do not yet have them in the standard library.
    2. Experimentation: Use type system features proposed in new PEPs before they are officially accepted and added to the typing module in CPython.

    Note: typing_extensions re-exports most names from the typing module so you can import everything from one place without worrying about which Python version introduced which object. Exceptions include deprecated or removed items like typing.ByteString, typing.io, and typing.re.

  2. Security guarantees of typing_extensions

    main

    To maintain a secure and transparent release process, typing_extensions adheres to the following commitments:

    • Pure Python: The package will never include native extensions; it consists only of pure Python code.
    • Zero Dependencies: The package will not have any third-party dependencies.
    • Secure Release Process: The maintainers follow industry best practices for secure releases.
  3. Use typing_extensions for backporting typing features

    main
    The typing_extensions module provides backports of features from newer Python versions to older ones. This allows you to use modern type hinting features in environments running older Python versions. Many symbols in typing_extensions are direct mirrors of those found in the standard typing module.
  4. Security considerations for annotation introspection

    main

    Functions in typing_extensions designed to introspect annotations at runtime (such as get_annotations or evaluate_forward_ref) may execute code contained within those annotations. This can lead to arbitrary code execution, system calls, or infinite loops if the annotations are untrusted.

    Security Best Practices:

    • Do not accept untrusted input: Never pass strings or other input from an untrusted source to APIs that introspect annotations (e.g., by manually editing an __annotations__ dictionary or creating a ForwardRef object).
    • Be aware of imports: Importing code that contains untrusted annotations can trigger arbitrary operations immediately.
    • Note on Python 3.14+: Accessing the object.__annotations__ attribute may also trigger these behaviors.
  5. Guidelines for safe runtime type introspection

    main

    If your library performs runtime introspection of types, follow these guidelines to minimize compatibility risks when typing_extensions updates or re-exports objects:

    • Check both modules: Always check for both the typing and typing_extensions versions of an object, even if they are currently identical. Future releases might re-export a separate version to backport a fix or feature.
    • Use public APIs: Access internal type information using public functions like get_origin and get_original_bases instead of accessing private attributes directly.

    Below is a resilient pattern for checking if an object belongs to a specific typing name across both modules using caching for performance.

    import functools
    import typing
    import typing_extensions
    from typing import Tuple, Any
    
    # Use an unbounded cache for this function, for optimal performance
    @functools.lru_cache(maxsize=None)
    def get_typing_objects_by_name_of(name: str) -> Tuple[Any, ...]:
        result = tuple(
            getattr(module, name)
            for module in (typing, typing_extensions)
            if hasattr(module, name)
        )
        if not result:
            raise ValueError(
                f"Neither typing nor typing_extensions has an object called {name!r}"
            )
        return result
    
    # Use a cache here as well, but make it a bounded cache
    @functools.lru_cache()
    def is_typing_name(obj: object, name: str) -> bool:
        return any(obj is thing for thing in get_typing_objects_by_name_of(name))
    
    # Example usage:
    # >>> import typing, typing_extensions
    # >>> from functools import partial
    # >>> from typing_extensions import get_origin
    # >>> is_literal = partial(is_typing_name, name="Literal")
    # >>> is_literal(typing.Literal)
    # True
    # >>> is_literal(typing_extensions.Literal)
    # True
  6. How to depend on typing_extensions safely

    main

    Because typing_extensions follows Semantic Versioning, you can safely depend on it using compatible release specifiers. You should specify the minimum version that includes the features you require.

    Recommended pattern: Use typing_extensions ~=x.y, where x.y is the first version containing the features you need. This is equivalent to typing_extensions >=x.y, <(x+1).

    Warning: Avoid using ~= x.y.z (specifying a patch version) unless you have a specific reason, as it limits the ability to receive minor feature updates which is the primary purpose of the library's versioning model.

    # Example dependency specification
    typing_extensions ~= 4.10
  7. Versioning and dependency management for typing_extensions

    main

    Since version 4.0.0, typing_extensions follows Semantic Versioning.

    • Major versions are incremented for backwards-incompatible changes.
    • Feature releases (e.g., 4.N.0) occur when new features accumulate. These are preceded by release candidates (e.g., 4.N.0rc1) for testing.
    • Bugfix releases (e.g., 4.N.1) are released to address discovered bugs.

    Safe Dependency Pattern: It is safe to depend on typing_extensions using a range that excludes the next major version: typing_extensions >=x.y, <(x+1) (where x.y is the minimum version required for your features).

  8. Use ParamSpec for decorator type safety

    main

    A ParamSpec (Parameter Specification) is used to forward the parameter types of one callable to another. This is primarily used in higher-order functions like decorators to ensure that the decorated function maintains its original signature in the eyes of a type checker.

    ParamSpec provides .args and .kwargs properties for use within Callable or Concatenate annotations.

    from typing import Callable, TypeVar
    from typing_extensions import ParamSpec
    import logging
    
    T = TypeVar('T')
    P = ParamSpec('P')
    
    def add_logging(f: Callable[P, T]) -> Callable[P, T]:
        '''A type-safe decorator to add logging to a function.'''
        def inner(*args: P.args, **kwargs: P.kwargs) -> T:
            logging.info(f'{f.__name__} was called')
            return f(*args, **kwargs)
        return inner
    
    @add_logging
    def add_two(x: float, y: float) -> float:
        return x + y
  9. Use Buffer for buffer protocol support

    main

    If collections.abc.Buffer is available in your environment, typing_extensions.Buffer provides a backport. It is an ABC used to indicate support for the buffer protocol (e.g., for classes implementing __buffer__).

    It is useful for static type checking. Common standard library buffer classes like memoryview, bytearray, and bytes are registered with this ABC.

  10. Use dataclass_transform decorator

    main

    The @dataclass_transform decorator (PEP 681) allows you to make a class or function behave like a dataclass for type checkers. It is backported from Python 3.11.

    Arguments:

    • eq_default: bool
    • order_default: bool
    • kw_only_default: bool
    • frozen_default: bool (Added in 4.5.0, backported from Python 3.12)
    • field_specifiers: (formerly field_descriptors) used to specify how fields are handled.
    from typing_extensions import dataclass_transform
    
    @dataclass_transform(frozen_default=True)
    class MyTransformingClass:
        ... 
  11. Use override decorator

    main

    The @override decorator (PEP 698) marks a method as overriding a method in a base class. It is backported from Python 3.12. In version 4.5.0+, the decorator attempts to set the __override__ attribute on the decorated object for runtime introspection.

    from typing_extensions import override
    
    class Base:
        def method(self) -> None: ...
    
    class Derived(Base):
        @override
        def method(self) -> None: ...
  12. Use ParamSpec with default values

    main

    The typing_extensions.ParamSpec class supports the default= argument (from PEP 696), allowing you to specify a default value for parameters in a ParamSpec.

    Key features:

    • Supports passing an ellipsis literal (...) to default on Python 3.10 and lower.
    • Provides a has_default() method for compatibility with Python 3.13+.
    • If no value is passed, __default__ is set to NoDefault. If None is passed, __default__ is set to None.
    from typing_extensions import ParamSpec, NoDefault
    
    # Example of using default with ParamSpec
    P = ParamSpec("P", default=NoDefault)