Skip to main content

Backports Python Typing Stubs

Project description

unpy

Unified Python

Transpiles .pyi stubs from Python 3.13 to 3.10

unpy - PyPI unpy - Python Versions unpy - license

unpy - CI unpy - pre-commit unpy - basedmypy unpy - basedpyright unpy - ruff


[!IMPORTANT] This project is in the alpha stage: You probably shouldn't use it in production.

Installation

$ pip install unpy

Usage

$ unpy --help
Usage: unpy [OPTIONS] SOURCE [OUTPUT]

Arguments:
  SOURCE    Path to the input .pyi file or '-' to read from stdin.  [required]
  [OUTPUT]  Path to the output .pyi file. Defaults to stdout.

Options:
  --version                       Show the version and exit
  --diff                          Show the changes between the input and
                                  output in unified diff format
  --target [3.10|3.11|3.12|3.13]  The minimum Python version that should be
                                  supported.  [default: 3.10]
  --help                          Show this message and exit.

Examples

Some simple examples of Python 3.13 stubs that are backported to Python 3.10.

Imports

$ unpy --target 3.10 --diff examples/imports.pyi
+++ -
@@ -1,6 +1,4 @@
- from types import CapsuleType
- from typing import override
- from warnings import deprecated
+ from typing_extensions import CapsuleType, deprecated, override

  @deprecated("RTFM")
  class Spam:
      __pyx_capi__: dict[str, CapsuleType]
      @override
      def __hash__(self, /) -> int: ...

Note the alphabetical order of the generated imports.

Type Aliases

$ unpy --target 3.10 --diff examples/type_aliases.pyi
+++ -
@@ -1,7 +1,15 @@
  from collections.abc import Callable
+ from typing import ParamSpec, TypeAlias, TypeVar
+ from typing_extensions import TypeAliasType, TypeVarTuple, Unpack

- type Binary = bytes | bytearray | memoryview
- type Vector[R: float] = tuple[R, ...]
- type tciD[V, K] = dict[K, V]
- type Things[*Ts] = tuple[*Ts]
- type Callback[**Tss] = Callable[Tss, None]
+ _R = TypeVar("_R", bound=float)
+ _V = TypeVar("_V")
+ _K = TypeVar("_K")
+ _Ts = TypeVarTuple("_Ts")
+ _Tss = ParamSpec("_Tss")
+
+ Binary: TypeAlias = bytes | bytearray | memoryview
+ Vector: TypeAlias = tuple[_R, ...]
+ tciD = TypeAliasType("tciD", dict[_K, _V], type_params=(_V, _K))
+ Things: TypeAlias = tuple[Unpack[_Ts]]
+ Callback: TypeAlias = Callable[_Tss, None]

Note that TypeAlias cannot be used with tciD because the definition order of the type parameters (at the left-hand side) does not match the order in which they are accessed (at the right-hand side), and the backported TypeAliasType must be used instead.

Functions

$ unpy --target 3.10 --diff examples/functions.pyi
+++ -
@@ -1,6 +1,11 @@
+ _T = TypeVar("_T")
+ _S = TypeVar("_S", str, bytes)
+ _X = TypeVar("_X")
+ _Theta = ParamSpec("_Theta")
+ _Y = TypeVar("_Y")
  from collections.abc import Callable as Def
- from typing import Concatenate as Concat
+ from typing import Concatenate as Concat, ParamSpec, TypeVar

- def noop[T](x: T, /) -> T: ...
- def concat[S: (str, bytes)](left: S, right: S) -> S: ...
- def curry[X, **Theta, Y](f: Def[Concat[X, Theta], Y], /) -> Def[[X], Def[Theta, Y]]: ...
+ def noop(x: _T, /) -> _T: ...
+ def concat(left: _S, right: _S) -> _S: ...
+ def curry(f: Def[Concat[_X, _Theta], _Y], /) -> Def[[_X], Def[_Theta, _Y]]: ...

Generic classes and protocols

$ unpy --target 3.10 --diff examples/generics.pyi
+++ -
@@ -1,17 +1,25 @@
- from typing import Protocol, overload
+ from typing import Generic, Protocol, overload
+ from typing_extensions import TypeVar
+
+ _T_contra = TypeVar("_T_contra", contravariant=True)
+ _T_co = TypeVar("_T_co", covariant=True)
+ _T = TypeVar("_T", infer_variance=True)
+ _D = TypeVar("_D")
+ _NameT = TypeVar("_NameT", infer_variance=True, bound=str)
+ _QualNameT = TypeVar("_QualNameT", infer_variance=True, bound=str, default=_NameT)

  class Boring: ...

- class CanGetItem[T_contra, T_co](Protocol):
-     def __getitem__(self, k: T_contra, /) -> T_co: ...
+ class CanGetItem(Protocol[_T_contra, _T_co]):
+     def __getitem__(self, k: _T_contra, /) -> _T_co: ...

- class Stack[T]:
-     def push(self, value: T, /) -> None: ...
+ class Stack(Generic[_T, _D]):
+     def push(self, value: _T, /) -> None: ...
      @overload
-     def pop(self, /) -> T: ...
+     def pop(self, /) -> _T: ...
      @overload
-     def pop[D](self, default: D, /) -> T | D: ...
+     def pop(self, default: _D, /) -> _T | _D: ...

- class Named[NameT: str, QualNameT: str = NameT]:
-     __name__: NameT
-     __qualname__: QualNameT
+ class Named(Generic[_NameT, _QualNameT]):
+     __name__: _NameT
+     __qualname__: _QualNameT

Note how TypeVar is (only) imported from typing_extensions here, which wasn't the case in the previous example. This is a consequence of the infer_variance parameter, which has been added in Python 3.12.

Project goals

Here's the alpha version of a prototype of a rough sketch of some initial ideas for the potential goals of unpy:

  1. Towards the past
    • Get frustrated while stubbing scipy
    • Transpile Python 3.13 .pyi stubs to Python 3.10 stubs
    • Package-level analysis and conversion
    • Tooling for stub-only project integration
    • Use this in scipy-stubs
    • Gradually introduce this into numpy
  2. Towards the future
    • Beyond Python: $\text{Unpy} \supset \text{Python}$
    • Language support & tooling for all .py projects
  3. Towards each other
    • Unified typechecking: Fast, reasonable, and language-agnostic

Features

Tooling

  • Target Python versions
    • 3.13
    • 3.12
    • 3.11
    • 3.10
    • 3.9
  • Language support
    • .pyi
    • .py
  • Conversion
    • stdin => stdout
    • module => module
    • package => package
    • project => project (including the pyproject.toml)
  • Configuration
    • --diff: Unified diffs
    • --target: Target Python version, defaults to 3.10
    • Project-based config in pyproject.toml under [tools.unpy]
    • ...
  • Integration
    • File watcher
    • Pre-commit
    • LSP
    • UV
    • VSCode extension
    • (based)mypy plugin
    • Project build tools
    • Configurable type-checker integration
    • Configurable formatter integration, e.g. ruff format
  • Performance
    • Limit conversion to changed files

Stub backporting

  • Python 3.13 => 3.12
    • PEP 742
      • typing.TypeIs => typing_extensions.TypeIs
    • PEP 705
      • typing.ReadOnly => typing_extensions.ReadOnly
    • PEP 702
      • warnings.deprecated => typing_extensions.deprecated
    • PEP 696
      • Backport PEP 695 type signatures with a default
      • typing.NoDefault => typing_extensions.NoDefault
    • Exceptions
      • asyncio.QueueShutDown => builtins.Exception
      • pathlib.UnsupportedOperation => builtins.NotImplementedError
      • queue.ShutDown => builtins.Exception
      • re.PatternError => re.error
    • Typing
      • types.CapsuleType => typing_extensions.CapsuleType
      • typing.{ClassVar,Final} => typing_extensions.{ClassVar,Final} when nested
  • Python 3.12 => 3.11
    • PEP 698
      • typing.override => typing_extensions.override
    • PEP 695
      • Backport type _ aliases
      • Backport generic functions
      • Backport generic classes and protocols
      • typing.TypeAliasType => typing_extensions.TypeAliasType
    • PEP 688
      • collections.abc.Buffer => typing_extensions.Buffer
      • inspect.BufferFlags => int
  • Python 3.11 => 3.10
    • PEP 681
      • typing.dataclass_transform => typing_extensions.dataclass_transform
    • PEP 675
      • typing.LiteralString => typing_extensions.LiteralString
    • PEP 673
      • typing.Self => typing_extensions.Self
    • PEP 655
      • typing.[Not]Required => typing_extensions.[Not]Required
    • PEP 654
      • builtins.BaseExceptionGroup
      • builtins.ExceptionGroup
    • PEP 646
      • typing.TypeVarTuple => typing_extensions.TypeVarTuple
      • typing.Unpack => typing_extensions.Unpack
      • *Ts => typing_extensions.Unpack[Ts] with Ts: TypeVarTuple
    • asyncio
      • asyncio.TaskGroup
    • enum
      • enum.ReprEnum => enum.Enum
      • enum.StrEnum => str & enum.Enum
    • typing
      • typing.Any => typing_extensions.Any if subclassed (not recommended)
  • Generated TypeVars
    • De-duplicate extracted typevar-likes with same name if equivalent
    • Prefix the names of extracted typevar-likes with _
    • Rename incompatible typevar-likes with the same name (jorenham/unpy#86)

Simplification and refactoring

  • Generic type parameters
    • Convert default=Any with bound=T to default=T
    • Remove bound=Any and bound=object
    • Infer variance of PEP 695 type parameters (jorenham/unpy#44)
      • If never used, it's redundant (and bivariant) (jorenham/unpy#46)
      • If constraints are specified, it's invariant
      • If suffixed with _co/_contra, it's covariant/contravariant
      • If used as public instance attribute, it's invariant
      • If only used as return-type (excluding __init__ and __new__), or for read-only attributes, it's covariant
      • If only used as parameter-type, it's contravariant
      • Otherwise, assume it's invariant
  • Methods
    • Default return types for specific "special method" (jorenham/unpy#55)
    • Transform self method parameters to be positional-only
  • Typing operators
    • type[S] | type[T] => type[S | T]
    • Flatten & de-duplicate unions of literals
    • Remove redundant union values, e.g. bool | int => int

Beyond Python

  • @sealed types (jorenham/unpy/#42)
  • Unified type-ignore comments (jorenham/unpy/#68)
  • Set-based Literal syntax (jorenham/unpy/#76)
  • Reusable method signature definitions
  • Type-mappings, a DRY alternative to @overload
  • Intersection types (as implemented in basedmypy)
  • Higher-kinded types (see python/typing#548)
  • Inline callable types (inspired by PEP 677)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

unpy-0.3.0.tar.gz (29.8 kB view details)

Uploaded Source

Built Distribution

unpy-0.3.0-py3-none-any.whl (29.1 kB view details)

Uploaded Python 3

File details

Details for the file unpy-0.3.0.tar.gz.

File metadata

  • Download URL: unpy-0.3.0.tar.gz
  • Upload date:
  • Size: 29.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.4.18

File hashes

Hashes for unpy-0.3.0.tar.gz
Algorithm Hash digest
SHA256 9014554a4a336c59f28b29c0e29acc08bca7f2ef3b4719e5acc8a14495bd6187
MD5 0faeedc83845266d39c5d035ec396af4
BLAKE2b-256 97d30211ddf0ef9b4758493dc010cf99b2806da5851bec9a20390ad4b7b4dfdc

See more details on using hashes here.

File details

Details for the file unpy-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: unpy-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 29.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.4.18

File hashes

Hashes for unpy-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 82c3ce103051c153832099f1729e7891c2d0801d5b9be4c41c1716d5217c276a
MD5 c6b3a1ae682c22a0dd9779f8d9dd40ee
BLAKE2b-256 e21821dfce6d218795e248d6b0b3adbdfdbe7c6242510383b81732b37b6ef587

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page