- Small Batches
- Posts
- Supporting multiple simultaneous Python versions
Supporting multiple simultaneous Python versions
Note: Time has moved on and Python 3.6 is now obsolete, along with newer 3.9 and 3.10 Python releases. However the fundamental processes described here for mitigating the impact of changing features between Python releases are still valid.
Until recently I had been able to specify a Python version and work with it. Hereās a brief outline of some work I did recently to support a language feature that changed across a few Python versions.
Fortunately Iām not having to support Python2 versus Python3 features. To date, the language features that Iāve used have been uniform across the major 3.x Python versions (3.6, 3.7 and 3.8).
So for a little context: my development system is running Python 3.8 by default. Iām āin the flowā, working away and I get some mypy errors complaining about undeclared dictionary types so I quickly implement use of typing.TypedDict. In accordance with PEP-589, static analysis tools such as mypy will report type violations based on type annotations and thus you can use typing.TypedDict to satisfy mypy regarding dictionary type structure. Useful.
But then I push to CI which checks all the tests across multiple Python versions and TypedDict is not implemented in Python 3.6 or 3.7 for which I have to maintain language compatibility. So what to do?
I could drop the use of TypedDict and line-by-line suppress mypyerrorsā¦The problem is that I would still like to have some kind of dictionary validation which probably means using something like schema which Iāve used before and I like, so maybe itās all good?
Or maybe thereās a way to back-port language features from 3.8 to the earlier versions?
It turns out thereās a way to do thatā¦
Observation #1
Thereās an official package called typing-extensions for back-porting selected typing features to older Python releases.
But if I include that in my project dependencies, doesnāt that mean that Python 3.8 would have this extra baggage of an unnecessary package that conceivably might also accidentally cause conflicts, or even bugs?ā¦
Observation #2
PEP-508 specifies a means for describing Python release specific project dependencies which is also supported by my favourite package manager flit.
So my pyproject.toml just needs to include something like this:
# pyproject.tomlrequires = [ # Python 3.6, 3.7 support for Python 3.8 typing features: "typing-extensions; python_version <'3.8'",]
Now the typing-extensions package is only included for Python releases earlier than 3.8. Nice.
Observation #3
You can conditionally import packages. Using the standard sys.version_info data you can condition the import to import different packages depending on the currently running Python version.
# my_project/typing.pyimport sysif ((sys.version_info[0] == 3) and (sys.version_info[1] >= 8)) or ( sys.version_info[0] >= 4): from typing import TypedDictelif (sys.version_info[0] == 3) and (sys.version_info[1] in [6, 7]): from typing_extensions import TypedDictelse: raise RuntimeError("Unsupported Python version, {0}".format(sys.version_info))
Iām only interested in the TypedDict feature at present, but hopefully itās obvious that you could import any of the other features in typing_extensions if you needed them.
By adding a new typing.py module in my project that localises the conditional import to a single location I can continue to create maintainable, uniform code that will use the TypedDict language feature.
# my_project/some_module.pyfrom my_project.typing import TypedDictdef my_function(some_value: TypedDict)->None: raise NotImplementedError()
And weāre done
Hopefully this was an informative description of one way to cleanly support newer language features across multiple Python versions.
Reply