Skip to content

python-stdlib: Add typing modules that can be frozen or mip installable.#1051

Open
Josverl wants to merge 5 commits into
micropython:masterfrom
Josverl:add_typing_py
Open

python-stdlib: Add typing modules that can be frozen or mip installable.#1051
Josverl wants to merge 5 commits into
micropython:masterfrom
Josverl:add_typing_py

Conversation

@Josverl
Copy link
Copy Markdown
Contributor

@Josverl Josverl commented Oct 14, 2025

As this PR have changed very significantly I have completely rewritten the PR Description.
The old description can be found at the end.

This is a MicroPython alternative to:

based on all the above and tuned for reduced firmware/frozen size, while keeping provable consistence with the relevant typing PEPs for features that are supported by MicroPython.

It consists of a number of modules that can be mip-installed or frozen as part of a firmware..
Note that the modules have been optimized for size - somewhat impacting readability - but where relevant the orignal code has been kept as comments.

For other related changes see later in this section.

Fixes: #190
Closes: micropython/micropython#15911
Closes: #584

I want to explicitly recognize that this work is in part based on and inspired the work by in PRs @stinos and @andrewleech and other individuals that have directly contributed to the typing stubs on the MicroPython-Stubs repo.

Summary of the modules:

  • __future__ : contains the annotations feature, which allows for postponed evaluation
  • typing : contains the core typing features, such as TypeVar, Generic, Protocol, Union, Optional, Callable and more.
  • typing_extensions : features that are/were not yet part of the standard library for Python 3.4/3.5.
  • collections.abc : contains the abstract base classes for collections, such as Iterable, Sequence, Mapping and more.
  • abc : contains the ABC and abstractmethod features, while they are deprecated in Python 3.11, they are still relevant.

Implementation details:

The __future__ already existed as a basic list of assignments, and has been extended with only the annotations feature.

In order to save space in the firmware or on the file system the typing module uses a module level __getattr__ to lazily load the features of the module, rather than implement each typing class, method or decorator in detail. Care has been taken to match the runtime signatures of the CPython/Micropythn runtime , and reduce runtime overhead as much as possible.

The typing_extensions, abc and collections.abc modules re-use the same __getattr__ from the typing module and also include only limited implementations, again to save space.

each module can be frozen or installed individually, it will automatically pull in any required dependencies.

In order to further simplify installation or freezing a bundle-typing has been created that contains:

  • __future__
  • typing
  • typing_extensions
    And optionally ( extensions=true ) also:
  • abc
  • collections.abc

The bundle also set the opt_level to 3, which will further reduce the size of the modules, but may impact readability of tracebacks.

# typing related modules
require(
    "bundle-typing",
    # extensions=False,  # Set to True to include collections.abc
    # opt_level=3,
)

Install from this PR:

The modules can be installed from this PR using the following command:

# install the bundle-typing 
mpremote mip install --index https://josverl.github.io/micropython-lib/mip/add_typing_py bundle-typing
# or each module individually
mpremote mip install --index https://josverl.github.io/micropython-lib/mip/add_typing_py __future__ typing typing_extensions collections-abc abc

# unittest 0.11.0
mpremote mip install --index https://josverl.github.io/micropython-lib/mip/add_typing_py unittest

( Do try this at home :-)

Testing:

I have spent perhaps more time on creating the tests than on the implementation, as I wanted to make sure that the implementation is consistent with the relevant PEPs and also to have a good coverage of the implemented features.
Currently there are 187 typing runtime tests, which are based on:

  • The examples provided in the relevant PEPs, such as PEP 484, PEP 544, PEP 560 and more.
  • A number of tests from the CPython test suite, such as test_typing and test_typing_extensions.
  • Existing tests created for the MicroPython-stubs project
  • AI generated, hand curated, tests for edge cases and features that are not covered by the above sources.
  • A report generator that uses the existing test results to simplify comparison of the different implementations and variants.

Tests and test results

  • Tests for all typing modules are located in micropython:tests/typing_runtime
  • A tests for the new functionality of unittest has been added to micropython-lib:python-stdlib/unittest/tests

Test matrix

in order to compare the different optionas for implementation on both size and functionaly the followin variants have been tested:

Variant Description
standard MicroPython unix standard variant. no typing modules.
1_mp-stubs standard + frozen MicroPython-stubs implementation of the typing modules, which is a pure Python implementation that is not optimized for size.
2a_typing standard + frozen require("typing") from this PR, which is optimized for size and uses lazy loading.
2b_bundle_typing_S standard + frozen require("bundle-typing") which includes the core typing features and is optimized for size.
2c_bundle_typing_L standard + frozen require("bundle-typing", extensions=True) with which includes all typing modules and is optimized for size.
3_builtintypingmodule the C-implementation of the typing module micropython/micropython#15911, rebased to get a accurate size comparison.
5_standard_mip_installed standard + mip install all the typing modules.
6_coverage unix coverage + frozen require("bundle-typing", extensions=True).
RPI_PICO2 regular MicroPython v1.28.0 RPI_PICO2 + mpremote mip install <all typing modules>

Summary of test results

  • Specified test(s): typing_runtime
    • micropython: yetanothertypingbranch (a0dcc1e43a8a)
    • micropython-lib: add_typing_py (0291143)
  • Generated: 2026-06-04 22:08:51
Variant PASS xfail skip FAIL False Positive ERROR total text data bss size Δsize
standard 3 1 183 0 0 0 187 784_958 70_904 2_960 858_822 ref
1_mp-stubs 122 29 36 0 0 0 187 790_390 72_056 2_960 865_406 +6_584
2a_typing 107 27 53 0 0 0 187 786_830 71_352 2_960 861_142 +2_320
2b_bundle_typing_S 122 29 36 0 0 0 187 787_614 71_512 2_960 862_086 +3_264
2c_bundle_typing_L 127 29 31 0 0 0 187 788_486 71_704 2_960 863_150 +4_328
3_builtintypingmodule 128 31 8 16 2 2 187 786_870 71_352 2_960 861_182 +2_360
5_standard_mip_installed 152 33 2 0 0 0 187 784_958 70_904 2_960 858_822 +0
6_coverage 152 33 2 0 0 0 187 1_844_803 353_696 264_608 2_463_107 +1_604_285
RPI_PICO2 152 33 2 0 0 0 187 - - - - -

Legend:

  • ok=passed, xfail=expected failure, skip=test skipped
  • FAIL=test failed, False Positive=unexpected success
  • ERROR=test errored (also used when the test module crashed).
  • Firmware sizes are reported in bytes from size <binary>.

typing_runtime/check_all_modules.py

probe module availability
test import only
Class Test standard 1_mp-stubs 2a_typing 2b_bundle_typing_S 2c_bundle_typing_L 3_builtintypingmodule 5_standard_mip_installed 6_coverage RPI_PICO2
TestModuleIncluded test_typing skip ok ok ok ok ok ok ok ok
TestModuleIncluded test_typing_extensions skip ok ok ok ok ok ok ok ok
TestModuleIncluded test_future skip ok skip ok ok ok ok ok ok
TestModuleIncluded test_abc skip skip skip skip skip ok ok ok ok
TestModuleIncluded test_collections skip ok ok ok ok ok ok ok ok
TestModuleIncluded test_collections_abc skip skip skip skip ok skip ok ok ok

See the full report.md

PR and merge dependencies

See the companion PR: micropython/micropython#19310

I'm open to recommendation on how the different parts needed to implement and test this functionality should be seperated into different PRs, across the different repos.

Apart from this PR there are at the following components needed:

  1. unittest@0.11.0
    In order to be able to create tests, and clear reporting across the alternative implementations, I needed the test suite to be able to make use unittest.expectedFailure, and for unittest to report the Expected Failures and unexpected successes to the run-test.py test runner. This allows to distinguish on things that are not implemented in MicroPython, but are expected to fail ( User defined Generics[], MetaClass), from things that are implemented but not working as expected.

  2. micropython:tests/run-test.py
    In turn, the run-test.py test runner needed to be updated to handle the additional information.

  3. micropython:tools/make_report.py
    In order to keep an overview of all test results I have created a new tool make_report.py that builds a markdown report of the test results, which can be used to compare the results of different implementations and variants. This tool could also be used to compare other sets of PRs that implement the same features.

    Example report runner

    # example report run 
    report tests="typing_runtime": make_test_venv
        MICROPYPATH="{{MICROPYPATH_MIP}}" uv run tools/make_report.py \
            --variant standard=/home/jos/micropython.worktrees/runtime_typing/ports/unix/build-standard/micropython \
            --variant 1_mp-stubs=/home/jos/micropython.worktrees/runtime_typing/ports/unix/build-typing1/micropython \
            --variant 2a_typing=/home/jos/micropython.worktrees/runtime_typing/ports/unix/build-typing2a/micropython \
            --variant 2b_bundle_typing_S=/home/jos/micropython.worktrees/runtime_typing/ports/unix/build-typing2b/micropython \
            --variant 2c_bundle_typing_L=/home/jos/micropython.worktrees/runtime_typing/ports/unix/build-typing2c/micropython \
            --variant 3_builtintypingmodule=/home/jos/micropython.worktrees/runtime_typing/ports/unix/build-typing3/micropython \
            --variant 5_standard_mip_installed=/home/jos/micropython.worktrees/runtime_typing/ports/unix/build-standard/micropython \
            --variant 6_coverage=/home/jos/micropython.worktrees/runtime_typing/ports/unix/build-coverage/micropython \
            --keep-path 5_standard_mip_installed \
            --board RPI_PICO2=/dev/ttyACM0 \
            --board ESP32_C5=/dev/ttyACM1 \
            --out report.md \
            {{tests}}

Trade-offs and Alternatives

  • Size versus accuracy:
    The implementation is optimized for size, which may impact readability and traceability of errors.
    Also due to the use of __getattr__ for lazy loading, some features may appear to be available, while they are actually not.
    For example:

    • from typing import NoSuchThing will not raise an ImportError at runtime.
    • import typing; dir(typing) will not work as expected but show a list of all QSTR's in the firmware.
      This is deemed acceptable as static type checkers and linter will catch this at development time.
  • Size versus composability:
    While the freeezing of all .py files is larger than a fuctionally equivalant C implementation, it has the advanatge that is can be mip installed and also easily extended with new features, without the need to recompile the firmware.
    or any median can be accomplished by freezing only the core bundle-typing and mip installing other modules as appropriate.
    vfs size impact:

    $ mpremote df tree -s
    filesystem     size     used    avail use% mounted on
    <VfsLfs2>   3145728    45056  3100672    1 /
    tree :
    :/
    └── lib
        ├── [      194]  __future__.mpy
        ├── [      139]  abc.mpy
        ├── collections
        │   ├── [      274]  __init__.mpy
        │   └── [      112]  abc.mpy
        ├── [      642]  typing.mpy
        └── [      130]  typing_extensions.mpy
    
  • Freezing methods:
    durign the quest to save firmware size, I have experimented with different freezing method. I noticed in particular that the __future__ made the fimware size increase by more that its .py file size. and could actually be stored smaller using freeze_as_str() at the cost of additional runtime overhead while loading. This was explained in Discussion
    This PR currently uses the require() method for all modules,to reduce or avoid memory fragmentation that is likely caused by compilation at runtime.

  • unittest@0.11.0 and tools/run-tests.py:
    In order to be able to create tests for the typing modules, and to have clear reporting of expected failures and unexpected successes, I have added CPython compatible functionality to unittest

    That was defintly needed in order to develop this PR, but just guarding against regressiosn could be done using unittest@0.10.5, but that would require either removing.chaining the expected failure tests, or putting them behind a version check, which would make the tests less clear and more difficult to maintain.

    The update to tools/run-tests.py is minor, and adds just the abaility to parse the additional information, and is backward compatible with unittest@0.10.5.

    There is however no way for (a) unittest to check by which version of tools/run-tests.py it is being run.

Generative AI

I used generative AI tools when creating this PR, but a human has checked the
code and is responsible for the code and the description above.

Outdated Original PR Summary

This is a improved MicroPython alternative to

based on all the above and tuned for reduced firmware/frozen size, while keeping provable consistence with the relevant typing PEPs for features that are supported by MicroPython.

It consists of a number of modules that can be mip-installed or frozen as part of a firmware..
Note that the modules have been optimized for size - somewhat impacting readability - but where relevant the orignal code has been kept as comments.

cloases: #190

Documentation

Documentation is not created yet.

  • Document availability of the modules
  • Document the use of the bundle-typing and it's options

Testing:

The same set of tests has been run against both implementations and while there are a few differences , both pass the majority of tests.
The remaining differences to CPython should be documented as such, and then can be excluded from the verification tests.

The tests are created at part of MicroPython's tests suite , and are mot included in this PR.
Currently they are part of micropython/micropython#15911, but could be separated for testing ( may require a new variant for testing in this in the MicroPython repo)

The PR includes the bundle-typing to allow simple addition to a board defintition

# typing related modules
require(
    "bundle-typing",
    # extensions=False,  # Set to True to include collections.abc
    # opt_level=2,
)

This also defaults the mpy-cross opt level to 3, to reduce firmware size.

The impact to firmware size of the modtyping.c and the typing.py variant are surprisingly close,
although I think there may be room for additional optimisation for the modtyping.c alternative.

Comparison without collections.abc

Note: both PRs are changing , so the below is a point-in-time comparison ( 14/10/'25)

method modules included size (bytes) diff
standard - 841_295 0
modtyping.c no collections.abc 843887 2592
typing.py no collections.abc 843807 2.512 (-80)

Test Results:

Module count FIXME
typing.py 3
- [ ] FIXME: from collections.abc import Callable
- [ ] FIXME: document cpy_diff - get_args(int) should be ()
- [ ] FIXME: document cpydiff : Final cannot be used with container types
modtyping.c 6
- [ ] FIXME: Difference or Crash - multiple bases have instance lay-out conflict: multiple bases have instance lay-out conflict
- [ ] FIXME: Difference or Crash - multiple bases have instance lay-out conflict: multiple bases have instance lay-out conflict
- [ ] FIXME: from collections.abc import Callable
- [ ] FIXME: document cpy_diff - get_origin(str) should be None
- [ ] FIXME: document cpy_diff - get_args(int) should be ()
- [ ] FIXME: document cpydiff : Final cannot be used with container types

Comparison including collections.abc

method modules included size (bytes) diff
standard - 841_295 0
modtyping.c no collections.abc 843_887 2592
typing.py with collections.abc 844_063 2668 (+76 bytes)

Test results:

Module count FIXME
typing.py 2 Size: 844063
- [ ] FIXME: document cpy_diff - get_args(int) should be ()
- [ ] FIXME: document cpydiff : Final cannot be used with container types
modtyping.c 6 Size: 843887
- [ ] FIXME: Difference or Crash - multiple bases have instance lay-out conflict: multiple bases have instance lay-out conflict
- [ ] FIXME: Difference or Crash - multiple bases have instance lay-out conflict: multiple bases have instance lay-out conflict
- [ ] FIXME: from collections.abc import Callable
- [ ] FIXME: document cpy_diff - get_origin(str) should be None
- [ ] FIXME: document cpy_diff - get_args(int) should be ()
- [ ] FIXME: document cpydiff : Final cannot be used with container types

@Josverl
Copy link
Copy Markdown
Contributor Author

Josverl commented Oct 14, 2025

@stinos, could you please review ?

@stinos
Copy link
Copy Markdown

stinos commented Oct 14, 2025

To make comparisons easier, could you share gcc/micropython version and a script which you used, or even just a set of commands, to get these results? Having rebased my code on master and comparing sizes and results, things seem different for me so I want to make sure we're doing roughly the same thing; for instance:

  • a unix build with everything from this PR enabled is 3072 bytes larger than the standard build, compared to 2668 from your results. That seems substantial.
  • in that same build, typing.get_origin(str) is None is True but according to your results it should pass

In any case, I'm hitting something funny; I made a windows msvc build with your typing bundle, I can see frozen_content.c including the frozen typing code code, yet I get an ImportError trying to import any of the modules contained in the manifest. Probably I'm gonna try and sort that out first.

@Josverl
Copy link
Copy Markdown
Contributor Author

Josverl commented Oct 14, 2025

@stinos ,
please find the notebook and the supporting files for the 'jos' variant in the gist.

https://gist.github.com/Josverl/f953fb49a4cac63759bd2056f728a725#file-readme-md

I built locally on WSL Ubuntu 22.04.5 LTS with :

gcc (Ubuntu 11.4.0-1ubuntu1~22.04.2) 11.4.0

@stinos
Copy link
Copy Markdown

stinos commented Oct 16, 2025

I get an ImportError trying to import any of the modules contained in the manifest

Oops, my MICROPYPATH didn't have a .frozen entry.

please find the notebook and the supporting files for the 'jos' variant in the gist.

Ok thanks. I was doing almost the same, however using multiple variants in different branches where I might have messed something up at the time I wrote the previous comment, and using gcc 9.4 or so. Will check again next week. Not sure if this is of any use, but here's a PS script for comparing typing variants: https://gist.github.com/stinos/2ee6138679a39a75628f85b5765a71b1

@Josverl Josverl added the enhancement Feature requests, new feature implementations label Oct 16, 2025
Comment thread python-stdlib/typing/typing.py Outdated
extras.append("expected failures=%d" % res.expectedFailuresNum)
if res.unexpectedSuccessesNum > 0:
extras.append("unexpected successes=%d" % res.unexpectedSuccessesNum)
if (
Copy link
Copy Markdown
Contributor

@agatti agatti May 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (res.failuresNum + res.errorsNum + res.unexpectedSuccessesNum) > 0: saves 9 bytes here, although it won't work correctly if any of those values ends up being negative.

Copy link
Copy Markdown
Contributor Author

@Josverl Josverl May 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my tests your optimization works, and I think the counts will never will be negative.

the changes to unittest are a spin-off for a make_report.py ( need marketing advise here) tool that runs part of the test suite across multiple variants , and provides a more readable format of the results and the firmware sizes.
report.md

I think that make_report , and the these changes to unittest may be useful to compare other implementations ( such as enum) and these should that should be its own PR(s). I kept it in here for now to avoid needing to manage even more git worktrees
it is still WIP, also as the xfail/xpass Pytest concepts don't fit unittest.

Comment thread python-stdlib/typing_extensions/typing_extensions.py Outdated
Comment thread python-stdlib/collections-abc/collections/abc.py Outdated
Comment thread python-stdlib/__future__/manifest.py Outdated
@Josverl Josverl force-pushed the add_typing_py branch 8 times, most recently from 20e3a20 to c5c42c0 Compare June 3, 2026 20:53
Josverl and others added 4 commits June 4, 2026 20:54
typing, typing_extensions and collections.abc

Signed-off-by: Jos Verlinde <jos_verlinde@hotmail.com>
`__future__`, `abc`, `collection`

Signed-off-by: Jos Verlinde <Jos_Verlinde@hotmail.com>
Signed-off-by: Jos Verlinde <jos_verlinde@hotmail.com>
This is needed to access the the "__future__" package.

Signed-off-by: Jos Verlinde <Jos_Verlinde@hotmail.com>
The could be submitted as a separate PR,
but is included here to allow better reporting
of the tests results of different typing variants.

Signed-off-by: Jos Verlinde <Jos_Verlinde@hotmail.com>
@Josverl
Copy link
Copy Markdown
Contributor Author

Josverl commented Jun 4, 2026

I think this PR is now feature complete. (As it has changed significantly - I have updated the initial PR comment.)

Now it comes to the real decision: which of the various alternatives is the best- and by what measure.
@stinos , @andrewleech , @agatti , @projectgus , @dpgeorge ,
I have done my best to allow you to compare on the various capabilities and impact , please let me know if you need additional clarification or information.

Clearly I like this PR, but even if this would be merged that still leaves the question of which typing modules to freeze, or leave them all to be installed on demand.

To help make that decision there is this summary, and a more complete report is linked below.

Variant PASS xfail skip FAIL False Positive ERROR total text data bss size Δsize
standard 3 1 183 0 0 0 187 784_958 70_904 2_960 858_822 ref
1_mp-stubs 122 29 36 0 0 0 187 790_390 72_056 2_960 865_406 +6_584
2a_typing 107 27 53 0 0 0 187 786_830 71_352 2_960 861_142 +2_320
2b_bundle_typing_S 122 29 36 0 0 0 187 787_614 71_512 2_960 862_086 +3_264
2c_bundle_typing_L 127 29 31 0 0 0 187 788_486 71_704 2_960 863_150 +4_328
3_builtintypingmodule 128 31 8 16 2 2 187 786_870 71_352 2_960 861_182 +2_360
5_standard_mip_installed 152 33 2 0 0 0 187 784_958 70_904 2_960 858_822 +0
6_coverage 152 33 2 0 0 0 187 1_844_803 353_696 264_608 2_463_107 +1_604_285
RPI_PICO2 152 33 2 0 0 0 187 - - - - -

Legend:

  • ok=passed, xfail=expected failure, skip=test skipped
  • FAIL=test failed, False Positive=unexpected success
  • ERROR=test errored (also used when the test module crashed).
  • Firmware sizes are reported in bytes from size <binary>.

report.md

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Feature requests, new feature implementations

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Typing module

3 participants