├── .gitignore ├── .python-version ├── LICENSE ├── README.md ├── poetry.lock ├── pyproject.toml └── src └── compatlib ├── __init__.py ├── test_class.py └── test_module.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | 134 | # pytype static type analyzer 135 | .pytype/ 136 | 137 | # Cython debug symbols 138 | cython_debug/ 139 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.6.15 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Tyler M. Kontra 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # compatlib 2 | 3 | Easily write backwards-and-forwards compatible libraries. 4 | 5 | NOTE: you probably [don't want to use this](https://tylerkontra.com/posts/compatlib-python-method-overloading-magic/) 6 | 7 | 8 | # Overview 9 | 10 | If you have a library, you may want it to run across multiple python release versions. You might want to _prefer_ certain features that aren't available in older versions. Use compatlib to keep your code clean. 11 | 12 | # Usage 13 | 14 | ```python3 15 | import asyncio 16 | from compatlib import compat 17 | 18 | @compat.after(3, 4) 19 | def main() -> None: 20 | asyncio.get_event_loop().run_until_complete(coro()) 21 | return 3.4 22 | 23 | @compat.after(3, 7) 24 | def main() -> None: 25 | asyncio.run(coro()) 26 | return 3.7 27 | ``` 28 | 29 | compatlib will resolve the latest-usable version at runtime, by comparing the overloaded methods with the interpreter `sys.version_info`. 30 | 31 | When running the above code on python 3.6, it will use the first `main`. If you run it on python 3.7 it will run the second `main`. 32 | 33 | # Acknowledgement 34 | 35 | Thanks to @wesselb for authoring the great [plum](https://github.com/wesselb/plum) multiple dispatch library, which `compatlib` uses as the basis of its decorator implementation. Instead of dispatching on type signatures, it dispatches on `sys.version_info` tuples. 36 | 37 | # TODO 38 | 39 | - Support dispatching on versions of libraries. 40 | - Support dispatching on the presence of an attribute (effectively, a _boolean_ dispatcher) 41 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "atomicwrites" 3 | version = "1.4.0" 4 | description = "Atomic file writes." 5 | category = "dev" 6 | optional = false 7 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 8 | 9 | [[package]] 10 | name = "attrs" 11 | version = "21.2.0" 12 | description = "Classes Without Boilerplate" 13 | category = "dev" 14 | optional = false 15 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 16 | 17 | [package.extras] 18 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] 19 | docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] 20 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] 21 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] 22 | 23 | [[package]] 24 | name = "black" 25 | version = "21.9b0" 26 | description = "The uncompromising code formatter." 27 | category = "dev" 28 | optional = false 29 | python-versions = ">=3.6.2" 30 | 31 | [package.dependencies] 32 | click = ">=7.1.2" 33 | dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""} 34 | mypy-extensions = ">=0.4.3" 35 | pathspec = ">=0.9.0,<1" 36 | platformdirs = ">=2" 37 | regex = ">=2020.1.8" 38 | tomli = ">=0.2.6,<2.0.0" 39 | typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\""} 40 | typing-extensions = [ 41 | {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}, 42 | {version = "!=3.10.0.1", markers = "python_version >= \"3.10\""}, 43 | ] 44 | 45 | [package.extras] 46 | colorama = ["colorama (>=0.4.3)"] 47 | d = ["aiohttp (>=3.6.0)", "aiohttp-cors (>=0.4.0)"] 48 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] 49 | python2 = ["typed-ast (>=1.4.2)"] 50 | uvloop = ["uvloop (>=0.15.2)"] 51 | 52 | [[package]] 53 | name = "click" 54 | version = "8.0.3" 55 | description = "Composable command line interface toolkit" 56 | category = "dev" 57 | optional = false 58 | python-versions = ">=3.6" 59 | 60 | [package.dependencies] 61 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 62 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 63 | 64 | [[package]] 65 | name = "colorama" 66 | version = "0.4.4" 67 | description = "Cross-platform colored terminal text." 68 | category = "dev" 69 | optional = false 70 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 71 | 72 | [[package]] 73 | name = "dataclasses" 74 | version = "0.8" 75 | description = "A backport of the dataclasses module for Python 3.6" 76 | category = "dev" 77 | optional = false 78 | python-versions = ">=3.6, <3.7" 79 | 80 | [[package]] 81 | name = "importlib-metadata" 82 | version = "4.8.1" 83 | description = "Read metadata from Python packages" 84 | category = "dev" 85 | optional = false 86 | python-versions = ">=3.6" 87 | 88 | [package.dependencies] 89 | typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} 90 | zipp = ">=0.5" 91 | 92 | [package.extras] 93 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 94 | perf = ["ipython"] 95 | testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] 96 | 97 | [[package]] 98 | name = "iniconfig" 99 | version = "1.1.1" 100 | description = "iniconfig: brain-dead simple config-ini parsing" 101 | category = "dev" 102 | optional = false 103 | python-versions = "*" 104 | 105 | [[package]] 106 | name = "mypy" 107 | version = "0.910" 108 | description = "Optional static typing for Python" 109 | category = "dev" 110 | optional = false 111 | python-versions = ">=3.5" 112 | 113 | [package.dependencies] 114 | mypy-extensions = ">=0.4.3,<0.5.0" 115 | toml = "*" 116 | typed-ast = {version = ">=1.4.0,<1.5.0", markers = "python_version < \"3.8\""} 117 | typing-extensions = ">=3.7.4" 118 | 119 | [package.extras] 120 | dmypy = ["psutil (>=4.0)"] 121 | python2 = ["typed-ast (>=1.4.0,<1.5.0)"] 122 | 123 | [[package]] 124 | name = "mypy-extensions" 125 | version = "0.4.3" 126 | description = "Experimental type system extensions for programs checked with the mypy typechecker." 127 | category = "dev" 128 | optional = false 129 | python-versions = "*" 130 | 131 | [[package]] 132 | name = "packaging" 133 | version = "21.0" 134 | description = "Core utilities for Python packages" 135 | category = "dev" 136 | optional = false 137 | python-versions = ">=3.6" 138 | 139 | [package.dependencies] 140 | pyparsing = ">=2.0.2" 141 | 142 | [[package]] 143 | name = "pathspec" 144 | version = "0.9.0" 145 | description = "Utility library for gitignore style pattern matching of file paths." 146 | category = "dev" 147 | optional = false 148 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 149 | 150 | [[package]] 151 | name = "platformdirs" 152 | version = "2.4.0" 153 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 154 | category = "dev" 155 | optional = false 156 | python-versions = ">=3.6" 157 | 158 | [package.extras] 159 | docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] 160 | test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] 161 | 162 | [[package]] 163 | name = "pluggy" 164 | version = "1.0.0" 165 | description = "plugin and hook calling mechanisms for python" 166 | category = "dev" 167 | optional = false 168 | python-versions = ">=3.6" 169 | 170 | [package.dependencies] 171 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 172 | 173 | [package.extras] 174 | dev = ["pre-commit", "tox"] 175 | testing = ["pytest", "pytest-benchmark"] 176 | 177 | [[package]] 178 | name = "plum-dispatch" 179 | version = "1.5.4" 180 | description = "Multiple dispatch in Python" 181 | category = "main" 182 | optional = false 183 | python-versions = ">=3.6" 184 | 185 | [[package]] 186 | name = "py" 187 | version = "1.10.0" 188 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 189 | category = "dev" 190 | optional = false 191 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 192 | 193 | [[package]] 194 | name = "pyparsing" 195 | version = "2.4.7" 196 | description = "Python parsing module" 197 | category = "dev" 198 | optional = false 199 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 200 | 201 | [[package]] 202 | name = "pytest" 203 | version = "6.2.5" 204 | description = "pytest: simple powerful testing with Python" 205 | category = "dev" 206 | optional = false 207 | python-versions = ">=3.6" 208 | 209 | [package.dependencies] 210 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 211 | attrs = ">=19.2.0" 212 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 213 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 214 | iniconfig = "*" 215 | packaging = "*" 216 | pluggy = ">=0.12,<2.0" 217 | py = ">=1.8.2" 218 | toml = "*" 219 | 220 | [package.extras] 221 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 222 | 223 | [[package]] 224 | name = "regex" 225 | version = "2021.10.8" 226 | description = "Alternative regular expression module, to replace re." 227 | category = "dev" 228 | optional = false 229 | python-versions = "*" 230 | 231 | [[package]] 232 | name = "rope" 233 | version = "0.20.1" 234 | description = "a python refactoring library..." 235 | category = "dev" 236 | optional = false 237 | python-versions = "*" 238 | 239 | [package.extras] 240 | dev = ["pytest", "pytest-timeout"] 241 | 242 | [[package]] 243 | name = "toml" 244 | version = "0.10.2" 245 | description = "Python Library for Tom's Obvious, Minimal Language" 246 | category = "dev" 247 | optional = false 248 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 249 | 250 | [[package]] 251 | name = "tomli" 252 | version = "1.2.1" 253 | description = "A lil' TOML parser" 254 | category = "dev" 255 | optional = false 256 | python-versions = ">=3.6" 257 | 258 | [[package]] 259 | name = "typed-ast" 260 | version = "1.4.3" 261 | description = "a fork of Python 2 and 3 ast modules with type comment support" 262 | category = "dev" 263 | optional = false 264 | python-versions = "*" 265 | 266 | [[package]] 267 | name = "typing-extensions" 268 | version = "3.10.0.2" 269 | description = "Backported and Experimental Type Hints for Python 3.5+" 270 | category = "dev" 271 | optional = false 272 | python-versions = "*" 273 | 274 | [[package]] 275 | name = "zipp" 276 | version = "3.6.0" 277 | description = "Backport of pathlib-compatible object wrapper for zip files" 278 | category = "dev" 279 | optional = false 280 | python-versions = ">=3.6" 281 | 282 | [package.extras] 283 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 284 | testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] 285 | 286 | [metadata] 287 | lock-version = "1.1" 288 | python-versions = ">=3.6.2" 289 | content-hash = "3779a30dd2e0af9cbb6f71fa24b38bf158bec1a31cd07db26c81913171b0e2d0" 290 | 291 | [metadata.files] 292 | atomicwrites = [ 293 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 294 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 295 | ] 296 | attrs = [ 297 | {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, 298 | {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, 299 | ] 300 | black = [ 301 | {file = "black-21.9b0-py3-none-any.whl", hash = "sha256:380f1b5da05e5a1429225676655dddb96f5ae8c75bdf91e53d798871b902a115"}, 302 | {file = "black-21.9b0.tar.gz", hash = "sha256:7de4cfc7eb6b710de325712d40125689101d21d25283eed7e9998722cf10eb91"}, 303 | ] 304 | click = [ 305 | {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, 306 | {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, 307 | ] 308 | colorama = [ 309 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 310 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 311 | ] 312 | dataclasses = [ 313 | {file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"}, 314 | {file = "dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97"}, 315 | ] 316 | importlib-metadata = [ 317 | {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"}, 318 | {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"}, 319 | ] 320 | iniconfig = [ 321 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 322 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 323 | ] 324 | mypy = [ 325 | {file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"}, 326 | {file = "mypy-0.910-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb"}, 327 | {file = "mypy-0.910-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9"}, 328 | {file = "mypy-0.910-cp35-cp35m-win_amd64.whl", hash = "sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e"}, 329 | {file = "mypy-0.910-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921"}, 330 | {file = "mypy-0.910-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6"}, 331 | {file = "mypy-0.910-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212"}, 332 | {file = "mypy-0.910-cp36-cp36m-win_amd64.whl", hash = "sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885"}, 333 | {file = "mypy-0.910-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0"}, 334 | {file = "mypy-0.910-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de"}, 335 | {file = "mypy-0.910-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703"}, 336 | {file = "mypy-0.910-cp37-cp37m-win_amd64.whl", hash = "sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a"}, 337 | {file = "mypy-0.910-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504"}, 338 | {file = "mypy-0.910-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9"}, 339 | {file = "mypy-0.910-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072"}, 340 | {file = "mypy-0.910-cp38-cp38-win_amd64.whl", hash = "sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811"}, 341 | {file = "mypy-0.910-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e"}, 342 | {file = "mypy-0.910-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b"}, 343 | {file = "mypy-0.910-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2"}, 344 | {file = "mypy-0.910-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97"}, 345 | {file = "mypy-0.910-cp39-cp39-win_amd64.whl", hash = "sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8"}, 346 | {file = "mypy-0.910-py3-none-any.whl", hash = "sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d"}, 347 | {file = "mypy-0.910.tar.gz", hash = "sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150"}, 348 | ] 349 | mypy-extensions = [ 350 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 351 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 352 | ] 353 | packaging = [ 354 | {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, 355 | {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, 356 | ] 357 | pathspec = [ 358 | {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, 359 | {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, 360 | ] 361 | platformdirs = [ 362 | {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"}, 363 | {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"}, 364 | ] 365 | pluggy = [ 366 | {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, 367 | {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, 368 | ] 369 | plum-dispatch = [ 370 | {file = "plum-dispatch-1.5.4.tar.gz", hash = "sha256:bd690dbc0179b4b632f5dfa22d8f38c6b0750a57dc22bbd06c6d073bffafaa2e"}, 371 | ] 372 | py = [ 373 | {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, 374 | {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, 375 | ] 376 | pyparsing = [ 377 | {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, 378 | {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, 379 | ] 380 | pytest = [ 381 | {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, 382 | {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, 383 | ] 384 | regex = [ 385 | {file = "regex-2021.10.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:981c786293a3115bc14c103086ae54e5ee50ca57f4c02ce7cf1b60318d1e8072"}, 386 | {file = "regex-2021.10.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51feefd58ac38eb91a21921b047da8644155e5678e9066af7bcb30ee0dca7361"}, 387 | {file = "regex-2021.10.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea8de658d7db5987b11097445f2b1f134400e2232cb40e614e5f7b6f5428710e"}, 388 | {file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1ce02f420a7ec3b2480fe6746d756530f69769292eca363218c2291d0b116a01"}, 389 | {file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39079ebf54156be6e6902f5c70c078f453350616cfe7bfd2dd15bdb3eac20ccc"}, 390 | {file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ff24897f6b2001c38a805d53b6ae72267025878d35ea225aa24675fbff2dba7f"}, 391 | {file = "regex-2021.10.8-cp310-cp310-win32.whl", hash = "sha256:c6569ba7b948c3d61d27f04e2b08ebee24fec9ff8e9ea154d8d1e975b175bfa7"}, 392 | {file = "regex-2021.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:45cb0f7ff782ef51bc79e227a87e4e8f24bc68192f8de4f18aae60b1d60bc152"}, 393 | {file = "regex-2021.10.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fab3ab8aedfb443abb36729410403f0fe7f60ad860c19a979d47fb3eb98ef820"}, 394 | {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74e55f8d66f1b41d44bc44c891bcf2c7fad252f8f323ee86fba99d71fd1ad5e3"}, 395 | {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d52c5e089edbdb6083391faffbe70329b804652a53c2fdca3533e99ab0580d9"}, 396 | {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1abbd95cbe9e2467cac65c77b6abd9223df717c7ae91a628502de67c73bf6838"}, 397 | {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9b5c215f3870aa9b011c00daeb7be7e1ae4ecd628e9beb6d7e6107e07d81287"}, 398 | {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f540f153c4f5617bc4ba6433534f8916d96366a08797cbbe4132c37b70403e92"}, 399 | {file = "regex-2021.10.8-cp36-cp36m-win32.whl", hash = "sha256:1f51926db492440e66c89cd2be042f2396cf91e5b05383acd7372b8cb7da373f"}, 400 | {file = "regex-2021.10.8-cp36-cp36m-win_amd64.whl", hash = "sha256:5f55c4804797ef7381518e683249310f7f9646da271b71cb6b3552416c7894ee"}, 401 | {file = "regex-2021.10.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb2baff66b7d2267e07ef71e17d01283b55b3cc51a81b54cc385e721ae172ba4"}, 402 | {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e527ab1c4c7cf2643d93406c04e1d289a9d12966529381ce8163c4d2abe4faf"}, 403 | {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c98b013273e9da5790ff6002ab326e3f81072b4616fd95f06c8fa733d2745f"}, 404 | {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:55ef044899706c10bc0aa052f2fc2e58551e2510694d6aae13f37c50f3f6ff61"}, 405 | {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0ab3530a279a3b7f50f852f1bab41bc304f098350b03e30a3876b7dd89840e"}, 406 | {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a37305eb3199d8f0d8125ec2fb143ba94ff6d6d92554c4b8d4a8435795a6eccd"}, 407 | {file = "regex-2021.10.8-cp37-cp37m-win32.whl", hash = "sha256:2efd47704bbb016136fe34dfb74c805b1ef5c7313aef3ce6dcb5ff844299f432"}, 408 | {file = "regex-2021.10.8-cp37-cp37m-win_amd64.whl", hash = "sha256:924079d5590979c0e961681507eb1773a142553564ccae18d36f1de7324e71ca"}, 409 | {file = "regex-2021.10.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b09d3904bf312d11308d9a2867427479d277365b1617e48ad09696fa7dfcdf59"}, 410 | {file = "regex-2021.10.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f125fce0a0ae4fd5c3388d369d7a7d78f185f904c90dd235f7ecf8fe13fa741"}, 411 | {file = "regex-2021.10.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f199419a81c1016e0560c39773c12f0bd924c37715bffc64b97140d2c314354"}, 412 | {file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:09e1031e2059abd91177c302da392a7b6859ceda038be9e015b522a182c89e4f"}, 413 | {file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c070d5895ac6aeb665bd3cd79f673775caf8d33a0b569e98ac434617ecea57d"}, 414 | {file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:176796cb7f82a7098b0c436d6daac82f57b9101bb17b8e8119c36eecf06a60a3"}, 415 | {file = "regex-2021.10.8-cp38-cp38-win32.whl", hash = "sha256:5e5796d2f36d3c48875514c5cd9e4325a1ca172fc6c78b469faa8ddd3d770593"}, 416 | {file = "regex-2021.10.8-cp38-cp38-win_amd64.whl", hash = "sha256:e4204708fa116dd03436a337e8e84261bc8051d058221ec63535c9403a1582a1"}, 417 | {file = "regex-2021.10.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b8b6ee6555b6fbae578f1468b3f685cdfe7940a65675611365a7ea1f8d724991"}, 418 | {file = "regex-2021.10.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:973499dac63625a5ef9dfa4c791aa33a502ddb7615d992bdc89cf2cc2285daa3"}, 419 | {file = "regex-2021.10.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88dc3c1acd3f0ecfde5f95c32fcb9beda709dbdf5012acdcf66acbc4794468eb"}, 420 | {file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4786dae85c1f0624ac77cb3813ed99267c9adb72e59fdc7297e1cf4d6036d493"}, 421 | {file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe6ce4f3d3c48f9f402da1ceb571548133d3322003ce01b20d960a82251695d2"}, 422 | {file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9e3e2cea8f1993f476a6833ef157f5d9e8c75a59a8d8b0395a9a6887a097243b"}, 423 | {file = "regex-2021.10.8-cp39-cp39-win32.whl", hash = "sha256:82cfb97a36b1a53de32b642482c6c46b6ce80803854445e19bc49993655ebf3b"}, 424 | {file = "regex-2021.10.8-cp39-cp39-win_amd64.whl", hash = "sha256:b04e512eb628ea82ed86eb31c0f7fc6842b46bf2601b66b1356a7008327f7700"}, 425 | {file = "regex-2021.10.8.tar.gz", hash = "sha256:26895d7c9bbda5c52b3635ce5991caa90fbb1ddfac9c9ff1c7ce505e2282fb2a"}, 426 | ] 427 | rope = [ 428 | {file = "rope-0.20.1.tar.gz", hash = "sha256:505a2f6b4ac7b18e0429be179f4d8712243a194da5c866b0731f9d91ce7590bb"}, 429 | ] 430 | toml = [ 431 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 432 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 433 | ] 434 | tomli = [ 435 | {file = "tomli-1.2.1-py3-none-any.whl", hash = "sha256:8dd0e9524d6f386271a36b41dbf6c57d8e32fd96fd22b6584679dc569d20899f"}, 436 | {file = "tomli-1.2.1.tar.gz", hash = "sha256:a5b75cb6f3968abb47af1b40c1819dc519ea82bcc065776a866e8d74c5ca9442"}, 437 | ] 438 | typed-ast = [ 439 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, 440 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, 441 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, 442 | {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, 443 | {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, 444 | {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, 445 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, 446 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, 447 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, 448 | {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, 449 | {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, 450 | {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, 451 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, 452 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, 453 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, 454 | {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, 455 | {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, 456 | {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, 457 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, 458 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, 459 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, 460 | {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, 461 | {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, 462 | {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, 463 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, 464 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, 465 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, 466 | {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, 467 | {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, 468 | {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, 469 | ] 470 | typing-extensions = [ 471 | {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, 472 | {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, 473 | {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, 474 | ] 475 | zipp = [ 476 | {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, 477 | {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, 478 | ] 479 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "compatlib" 3 | version = "0.1.0" 4 | description = "utilities for compatibility with multiple versions" 5 | authors = ["Tyler M Kontra"] 6 | license = "MIT" 7 | packages = [ 8 | { include = "compatlib", from = "src" }, 9 | ] 10 | 11 | [tool.poetry.dependencies] 12 | python = ">=3.6.2" 13 | plum-dispatch = "^1.5.4" 14 | 15 | [tool.poetry.dev-dependencies] 16 | pytest = "^6.2.5" 17 | rope = "^0.20.1" 18 | mypy = "^0.910" 19 | black = "^21.9b0" 20 | 21 | [build-system] 22 | requires = ["poetry>=0.12"] 23 | build-backend = "poetry.masonry.api" 24 | -------------------------------------------------------------------------------- /src/compatlib/__init__.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | import functools 3 | import sys 4 | from typing import Sequence 5 | import plum 6 | from plum import Dispatcher, ClassFunction, Function as _Function 7 | from plum.function import _BoundFunction 8 | from plum.util import * 9 | import heapq 10 | 11 | # cache the interpreter version info 12 | sys_ver_info = sys.version_info 13 | 14 | def _invoke_bound(self, ver_info): 15 | @wraps(self.f._f) 16 | def wrapped_method(*args, **kw_args): 17 | method = self.f.invoke(ver_info) 18 | return method(self.instance, *args, **kw_args) 19 | 20 | return wrapped_method 21 | 22 | _BoundFunction.invoke = _invoke_bound 23 | 24 | class Function(_Function): 25 | """Dispatch a function based on python version info 26 | """ 27 | def __init__(self, *args, **kwargs): 28 | super().__init__(*args, **kwargs) 29 | 30 | def register(self, method, ver_info): 31 | self._pending.append((method, ver_info)) 32 | 33 | def __call__(self, *args, **kw_args): 34 | # First resolve pending registrations, because the value of 35 | # `self._runtime_type_of` depends on it. 36 | if len(self._pending) != 0: 37 | self._resolve_pending_registrations() 38 | 39 | dispatch_version = kw_args.pop('override_ver_info', sys_ver_info) 40 | try: 41 | # Attempt to use cache. This will also be done in 42 | # `self.resolve_method`, but checking here as well speed up 43 | # cached calls significantly. 44 | method = self._cache[dispatch_version] 45 | return method(*args, **kw_args) 46 | except KeyError: 47 | method = self.resolve_method(dispatch_version) 48 | return method(*args, **kw_args) 49 | 50 | def invoke(self, ver_info): 51 | """Invoke a method for a particular sys.version_info. 52 | 53 | Args: 54 | ver_info: sys.version_info to resolve. 55 | 56 | Returns: 57 | function: Method. 58 | """ 59 | method = self.resolve_method(ver_info) 60 | 61 | @wraps(self._f) 62 | def wrapped_method(*args, **kw_args): 63 | return method(*args, **kw_args) 64 | 65 | return wrapped_method 66 | 67 | def _get_method_for_version(self, ver_info): 68 | for ver_key, method in reversed(list(self._methods.items())): 69 | if ver_key <= ver_info: 70 | break 71 | return method 72 | 73 | def resolve_method(self, ver_info): 74 | """Get the method and return type corresponding to types of arguments. 75 | 76 | Args: 77 | *types (type): Types of arguments. 78 | 79 | Returns: 80 | tuple: Tuple containing method and return type. 81 | """ 82 | # New registrations may invalidate cache, so resolve pending 83 | # registrations first. 84 | self._resolve_pending_registrations() 85 | 86 | # Attempt to use cache. 87 | try: 88 | return self._cache[ver_info] 89 | except KeyError: 90 | pass 91 | 92 | if self._owner: 93 | try: 94 | method = self._get_method_for_version(ver_info) 95 | except plum.NotFoundLookupError as e: 96 | method = None 97 | 98 | # Walk through the classes in the class's MRO, except for this 99 | # class, and try to get the method. 100 | for c in self._owner.mro()[1:]: 101 | try: 102 | method = getattr(c, self._f.__name__) 103 | 104 | # Ignore abstract methods. 105 | if ( 106 | hasattr(method, "__isabstractmethod__") 107 | and method.__isabstractmethod__ 108 | ): 109 | method = None 110 | continue 111 | 112 | # We found a good candidate. Break. 113 | break 114 | except AttributeError: 115 | pass 116 | 117 | if method == object.__init__: 118 | # The constructor of `object` has been found. This 119 | # happens when there a constructor is called and no 120 | # appropriate method can be found. Raise the original 121 | # exception. 122 | raise e 123 | 124 | if not method: 125 | # If no method has been found after walking through the 126 | # MRO, raise the original exception. 127 | raise e 128 | else: 129 | # Not in a class. Simply resolve. 130 | method = self._get_method_for_version(ver_info) 131 | 132 | # Cache lookup. 133 | self._cache[ver_info] = method 134 | return method 135 | 136 | def _resolve_pending_registrations(self): 137 | # Keep track of whether anything registered. 138 | registered = False 139 | 140 | # Perform any pending registrations. 141 | for f, ver_info in self._pending: 142 | registered = True 143 | 144 | # If a method with the same signature has already been defined, then that 145 | # is fine: we simply overwrite that method. 146 | 147 | # If the return type is `object`, then set it to `default_obj_type`. This 148 | # allows for a fast check to speed up cached calls. 149 | 150 | self._methods[ver_info] = f 151 | # self._precedences[signature] = precedence 152 | 153 | # Add to resolved registrations. 154 | self._resolved.append((f, ver_info)) 155 | 156 | if registered: 157 | methods = list(self._methods.items()) 158 | heapq.heapify(methods) 159 | self._methods = dict(methods) 160 | self._pending = [] 161 | 162 | # Clear cache. 163 | # TODO: Be more clever, but careful about the tracking of parametric types. 164 | self.clear_cache(reregister=False) 165 | 166 | class Compat(Dispatcher): 167 | """A namespace for functions.""" 168 | 169 | def __init__(self): 170 | self._functions = {} 171 | self._classes = {} 172 | 173 | def after(self, *ver: Sequence[int]): 174 | """Decorator for after a particular version. 175 | Args: 176 | precedence (int, optional): Precedence of the signature. Defaults to `0`. 177 | Returns: 178 | function: Decorator. 179 | """ 180 | def decorate(func): 181 | def construct_function(owner): 182 | return self._add_method( 183 | func, 184 | owner=owner, 185 | ver_info=tuple(ver) 186 | ) 187 | 188 | # Defer the construction if `method` is in a class. We defer the construction to 189 | # allow the function to hold a reference to the class. 190 | if is_in_class(func): 191 | return ClassFunction(get_class(func), construct_function) 192 | else: 193 | return construct_function(None) 194 | return decorate 195 | 196 | def _get_function(self, method, owner) -> Function: 197 | name = method.__name__ 198 | 199 | # If a class is the owner, use a namespace specific for that class. Otherwise, 200 | # use the global namespace. 201 | if owner: 202 | if owner not in self._classes: 203 | self._classes[owner] = {} 204 | namespace = self._classes[owner] 205 | else: 206 | namespace = self._functions 207 | 208 | # Create a new function only if the function does not already exist. 209 | if name not in namespace: 210 | namespace[name] = Function(method, owner=owner) 211 | 212 | return namespace[name] 213 | 214 | def _add_method(self, method, owner, ver_info): 215 | f = self._get_function(method, owner) 216 | f.register(method, ver_info) 217 | return f 218 | 219 | def clear_cache(self): 220 | """Clear cache.""" 221 | for f in self._functions.values(): 222 | f.clear_cache() 223 | 224 | 225 | compat = Compat() 226 | -------------------------------------------------------------------------------- /src/compatlib/test_class.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from compatlib import compat 3 | 4 | 5 | class MyCompatibleClass: 6 | def __init__(self, data): 7 | self.data = data 8 | 9 | @compat.after(3, 6) 10 | def process(self): 11 | print("running 3.6!") 12 | return 3.6 13 | 14 | @compat.after(3, 7) 15 | def process(self): 16 | print("running 3.7!") 17 | return 3.7 18 | 19 | if __name__ == "__main__": 20 | ver_info = sys.version_info 21 | _36 = MyCompatibleClass("ok").process.invoke(ver_info)() 22 | assert _36 == 3.6, _36 23 | ver_info = (3,7) 24 | _37 = MyCompatibleClass("ok").process.invoke(ver_info)() 25 | assert _37 == 3.7, _37 26 | 27 | -------------------------------------------------------------------------------- /src/compatlib/test_module.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from compatlib import compat 4 | 5 | def coro(): 6 | yield 7 | 8 | @compat.after(3, 4) 9 | def main() -> None: 10 | asyncio.get_event_loop().run_until_complete(coro()) 11 | return 3.4 12 | 13 | @compat.after(3, 7) 14 | def main() -> None: 15 | asyncio.run(coro()) 16 | return 3.7 17 | 18 | assert main() == 3.4 --------------------------------------------------------------------------------