retrofy
A tool which takes modern Python typing code, and makes it compatible with older Python versions.
The idea is to be able to maintain the modern typing in your repository, and then as part of the build stage, convert the code to the older form. You should continue to test your project against older versions (e.g. in CI) for full confidence in the compatibility.
Build-time transformation
retrofy includes the ability to customise the build to
transform Python files into the compatibility form when creating a wheel
using any PEP-517 build backend. This includes support for editable installs
(PEP-660), which transforms the code at import-time using standard import hook
machinery.
To setup a build-time conversion, add the multistage_build backend within
pyproject.toml, for example:
[build-system]
requires = ["multistage-build", "setuptools", "wheel", "setuptools_scm==7.*", "retrofy"]
build-backend = "multistage_build:backend"
[tool.multistage-build]
build-backend = "setuptools.build_meta"
Python compatibility
retrofy can be used with Python 3.9+, and can produce code (and wheels) which are
compatible with Python 3.7.
It is imperative that you test the produced wheels with the target versions, as there
may be syntax which is not yet handled in retrofy, resulting in a SyntaxError on your
desired Python version.
Available transformations
For all transformations, necessary imports (typing, collections.abc, etc.) will be injected where necessary
and appropriate.
-
A | B->typing.Union[A, B] -
PEP-572 - walrus operator
-
PEP-636 - match statements (structural pattern matching):
- Literal patterns:
case 42:->if value == 42: - Variable binding:
case x:->x = value - Sequence patterns:
case [x, y]:->if isinstance(value, collections.abc.Sequence) and not isinstance(value, str) and len(value) == 2: x, y = value - Mapping patterns:
case {"key": value}:->if isinstance(value, dict) and "key" in value: value = value["key"] - Class patterns:
case Point(x=0, y=y):->if isinstance(value, Point) and value.x == 0: y = value.y - Guard clauses:
case x if x > 0:->if x > 0: x = value - OR patterns:
case 1 | 2:->if value in (1, 2): - Wildcard patterns:
case _:->else: - Star patterns:
case [x, *rest]:->if len(value) >= 1: x = value[0]; rest = value[1:] - As patterns:
case [x, y] as point:->if len(value) == 2: point = value; x, y = value - Complex nested patterns with full recursive support
- Literal patterns:
-
PEP-695 - type statements, generic classes, and generic functions:
- Type statements:
type Point = tuple[float, float]->Point = tuple[float, float]type GenericPoint[T] = tuple[T, T]->T = typing.TypeVar("T"); GenericPoint: typing.TypeAlias = tuple[T, T]type BoundedPoint[T: int] = tuple[T, T]->T = typing.TypeVar("T", bound=int); BoundedPoint: typing.TypeAlias = tuple[T, T]
- Generic classes:
class ClassA[T]: ...->from typing import Generic, TypeVar; T = TypeVar("T"); class ClassA(Generic[T]): ...class ClassA[T: str]: ...->T = TypeVar("T", bound=str); class ClassA(Generic[T]): ...
- Generic functions:
def func[T](a: T) -> T: ...->T = typing.TypeVar("T"); def func(a: T) -> T: ...def func[T: str](a: T) -> T: ...->T = typing.TypeVar("T", bound=str); def func(a: T) -> T: ...
- Type statements:
-
dataclasses - the match_args attribute is added to the class (necessary for match statement support)
Transformations not yet implemented
A | None->typing.Optional[A]
Release files for retrofy 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| retrofy-0.3.0.tar.gz | 71.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| retrofy-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 139.1 kB
Release files / retrofy-0.3.0.tar.gz
| Download URL | retrofy-0.3.0.tar.gz |
|---|---|
| Size | 71.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4ab09c758ad65d3b3c9ab36747276527d952065d16b121ccdb74a2342edbc9a3
|
|
BLAKE2b-256 checksum How to use checksums |
c3393382f5d65a6acea8ec4237d53e3276772535cb39af0f00d480c79dd988b4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.
Transparency logRelease files / retrofy-0.3.0-py3-none-any.whl
| Download URL | retrofy-0.3.0-py3-none-any.whl |
|---|---|
| Size | 67.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
74c66088530aa05442c3e8095b0eb5ab11b2c043419052577a077c9470c29ac7
|
|
BLAKE2b-256 checksum How to use checksums |
afb4b3c04f4a5b21c93024bd989609d5885fd695e24b88d11a1482ad64e45d4b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.
Transparency log