From a994cd869e3ed47f68431ca862e25ddbc268a088 Mon Sep 17 00:00:00 2001 From: Doctor Date: Wed, 12 Mar 2025 20:53:21 +0300 Subject: [PATCH 1/3] build: migrate from `setup.py` to `pyproject.toml` --- .gitignore | 3 + pyproject.toml | 51 ++ uv.lock | 1564 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1618 insertions(+) create mode 100644 pyproject.toml create mode 100644 uv.lock diff --git a/.gitignore b/.gitignore index 4ed77d83..8e4018a8 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ target.cfg.d build/ dist/ *.egg-info + +# Intellij IDEs +.idea diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..3956cd6c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,51 @@ +[project] +name = "websockify" +version = "0.13.0" +description = "Websockify." +readme = "README.md" +authors = [ + { name = "Joel Martin", email = "github@martintribe.org" } +] +requires-python = ">=3.6" +dependencies = [ + "jwcrypto>=1.5.1", + "numpy>=1.19.0", + "redis>=4.3.0", + "requests>=2.27.0", +] +classifiers = [ + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] +keywords = ["noVNC", "websockify"] +license = { text = "LGPLv3" } + +[project.urls] +Repository ="https://github.com/novnc/websockify" + +[project.scripts] +websockify = "websockify.websocketproxy:websockify_init" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + + +[tool.hatch.build.targets.wheel] +packages = [ + "websockify", +] + +[dependency-groups] +dev = [ + "mypy>=0.971", + "ruff>=0.0.17", +] diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..61763662 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1564 @@ +version = 1 +revision = 1 +requires-python = ">=3.6" +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", + "python_full_version == '3.7.*'", + "python_full_version >= '3.6.8' and python_full_version < '3.7'", + "python_full_version < '3.6.8'", +] + +[[package]] +name = "async-timeout" +version = "4.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.6.8' and python_full_version < '3.7'", + "python_full_version < '3.6.8'", +] +dependencies = [ + { name = "typing-extensions", version = "4.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/6e/9678f7b2993537452710ffb1750c62d2c26df438aa621ad5fa9d1507a43a/async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15", size = 8221 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/c1/8991e7c5385b897b8c020cdaad718c5b087a6626d1d11a23e1ea87e325a7/async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c", size = 5763 }, +] + +[[package]] +name = "async-timeout" +version = "4.0.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.7.*'", +] +dependencies = [ + { name = "typing-extensions", version = "4.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/d6/21b30a550dafea84b1b8eee21b5e23fa16d010ae006011221f33dcd8d7f8/async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f", size = 8345 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/fa/e01228c2938de91d47b307831c62ab9e4001e747789d0b05baf779a6488c/async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028", size = 5721 }, +] + +[[package]] +name = "async-timeout" +version = "5.0.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233 }, +] + +[[package]] +name = "certifi" +version = "2025.1.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, +] + +[[package]] +name = "cffi" +version = "1.15.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.7.*'", + "python_full_version >= '3.6.8' and python_full_version < '3.7'", + "python_full_version < '3.6.8'", +] +dependencies = [ + { name = "pycparser", version = "2.21", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2b/a8/050ab4f0c3d4c1b8aaa805f70e26e84d0e27004907c5b8ecc1d31815f92a/cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9", size = 508501 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/ff/c4b7a358526f231efa46a375c959506c87622fb4a2c5726e827c55e6adf2/cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21", size = 179233 }, + { url = "https://files.pythonhosted.org/packages/ea/be/c4ad40ad441ac847b67c7a37284ae3c58f39f3e638c6b0f85fb662233825/cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185", size = 174280 }, + { url = "https://files.pythonhosted.org/packages/ed/a3/c5f01988ddb70a187c3e6112152e01696188c9f8a4fa4c68aa330adbb179/cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd", size = 421712 }, + { url = "https://files.pythonhosted.org/packages/ef/41/19da352d341963d29a33bdb28433ba94c05672fb16155f794fad3fd907b0/cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc", size = 449886 }, + { url = "https://files.pythonhosted.org/packages/af/da/9441d56d7dd19d07dcc40a2a5031a1f51c82a27cee3705edf53dadcac398/cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f", size = 450520 }, + { url = "https://files.pythonhosted.org/packages/aa/02/ab15b3aa572759df752491d5fa0f74128cd14e002e8e3257c1ab1587810b/cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e", size = 446015 }, + { url = "https://files.pythonhosted.org/packages/88/89/c34caf63029fb7628ec2ebd5c88ae0c9bd17db98c812e4065a4d020ca41f/cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4", size = 441830 }, + { url = "https://files.pythonhosted.org/packages/32/bd/d0809593f7976828f06a492716fbcbbfb62798bbf60ea1f65200b8d49901/cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01", size = 434743 }, + { url = "https://files.pythonhosted.org/packages/0e/65/0d7b5dad821ced4dcd43f96a362905a68ce71e6b5f5cfd2fada867840582/cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e", size = 464113 }, + { url = "https://files.pythonhosted.org/packages/9f/52/1e2b43cfdd7d9a39f48bc89fcaee8d8685b1295e205a4f1044909ac14d89/cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2", size = 170412 }, + { url = "https://files.pythonhosted.org/packages/0e/e2/a23af3d81838c577571da4ff01b799b0c2bbde24bd924d97e228febae810/cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d", size = 179060 }, + { url = "https://files.pythonhosted.org/packages/23/8b/2e8c2469eaf89f7273ac685164949a7e644cdfe5daf1c036564208c3d26b/cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac", size = 179198 }, + { url = "https://files.pythonhosted.org/packages/f9/96/fc9e118c47b7adc45a0676f413b4a47554e5f3b6c99b8607ec9726466ef1/cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83", size = 174221 }, + { url = "https://files.pythonhosted.org/packages/10/72/617ee266192223a38b67149c830bd9376b69cf3551e1477abc72ff23ef8e/cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9", size = 441694 }, + { url = "https://files.pythonhosted.org/packages/91/bc/b7723c2fe7a22eee71d7edf2102cd43423d5f95ff3932ebaa2f82c7ec8d0/cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c", size = 470613 }, + { url = "https://files.pythonhosted.org/packages/5d/4e/4e0bb5579b01fdbfd4388bd1eb9394a989e1336203a4b7f700d887b233c1/cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325", size = 472199 }, + { url = "https://files.pythonhosted.org/packages/37/5a/c37631a86be838bdd84cc0259130942bf7e6e32f70f4cab95f479847fb91/cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c", size = 462588 }, + { url = "https://files.pythonhosted.org/packages/71/d7/0fe0d91b0bbf610fb7254bb164fa8931596e660d62e90fb6289b7ee27b09/cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef", size = 450543 }, + { url = "https://files.pythonhosted.org/packages/d3/56/3e94aa719ae96eeda8b68b3ec6e347e0a23168c6841dc276ccdcdadc9f32/cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8", size = 474253 }, + { url = "https://files.pythonhosted.org/packages/87/ee/ddc23981fc0f5e7b5356e98884226bcb899f95ebaefc3e8e8b8742dd7e22/cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d", size = 170313 }, + { url = "https://files.pythonhosted.org/packages/43/a0/cc7370ef72b6ee586369bacd3961089ab3d94ae712febf07a244f1448ffd/cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104", size = 179001 }, + { url = "https://files.pythonhosted.org/packages/7c/3e/5d823e5bbe00285e479034bcad44177b7353ec9fdcd7795baac5ccf82950/cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7", size = 178921 }, + { url = "https://files.pythonhosted.org/packages/b5/80/ce5ba093c2475a73df530f643a61e2969a53366e372b24a32f08cd10172b/cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6", size = 434577 }, + { url = "https://files.pythonhosted.org/packages/47/51/3049834f07cd89aceef27f9c56f5394ca6725ae6a15cff5fbdb2f06a24ad/cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d", size = 434474 }, + { url = "https://files.pythonhosted.org/packages/b3/b8/89509b6357ded0cbacc4e430b21a4ea2c82c2cdeb4391c148b7c7b213bed/cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a", size = 429057 }, + { url = "https://files.pythonhosted.org/packages/03/7b/259d6e01a6083acef9d3c8c88990c97d313632bb28fa84d6ab2bb201140a/cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405", size = 379662 }, + { url = "https://files.pythonhosted.org/packages/3a/12/d6066828014b9ccb2bbb8e1d9dc28872d20669b65aeb4a86806a0757813f/cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e", size = 402952 }, + { url = "https://files.pythonhosted.org/packages/5d/6f/3a2e167113eabd46ed300ff3a6a1e9277a3ad8b020c4c682f83e9326fcf7/cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf", size = 174687 }, + { url = "https://files.pythonhosted.org/packages/69/bf/335f8d95510b1a26d7c5220164dc739293a71d5540ecd54a2f66bac3ecb8/cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497", size = 187274 }, + { url = "https://files.pythonhosted.org/packages/b5/7d/df6c088ef30e78a78b0c9cca6b904d5abb698afb5bc8f5191d529d83d667/cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375", size = 178906 }, + { url = "https://files.pythonhosted.org/packages/c2/0b/3b09a755ddb977c167e6d209a7536f6ade43bb0654bad42e08df1406b8e4/cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e", size = 405992 }, + { url = "https://files.pythonhosted.org/packages/5b/1a/e1ee5bed11d8b6540c05a8e3c32448832d775364d4461dd6497374533401/cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82", size = 435560 }, + { url = "https://files.pythonhosted.org/packages/d3/e1/e55ca2e0dd446caa2cc8f73c2b98879c04a1f4064ac529e1836683ca58b8/cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b", size = 435478 }, + { url = "https://files.pythonhosted.org/packages/2e/7a/68c35c151e5b7a12650ecc12fdfb85211aa1da43e9924598451c4a0a3839/cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c", size = 430395 }, + { url = "https://files.pythonhosted.org/packages/93/d0/2e2b27ea2f69b0ec9e481647822f8f77f5fc23faca2dd00d1ff009940eb7/cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426", size = 427911 }, + { url = "https://files.pythonhosted.org/packages/50/34/4cc590ad600869502c9838b4824982c122179089ed6791a8b1c95f0ff55e/cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9", size = 169721 }, + { url = "https://files.pythonhosted.org/packages/32/2a/63cb8c07d151de92ff9d897b2eb27ba6a0e78dda8e4c5f70d7b8c16cd6a2/cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045", size = 179301 }, + { url = "https://files.pythonhosted.org/packages/87/4b/64e8bd9d15d6b22b6cb11997094fbe61edf453ea0a97c8675cb7d1c3f06f/cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3", size = 178940 }, + { url = "https://files.pythonhosted.org/packages/22/c6/df826563f55f7e9dd9a1d3617866282afa969fe0d57decffa1911f416ed8/cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a", size = 421947 }, + { url = "https://files.pythonhosted.org/packages/c1/25/16a082701378170559bb1d0e9ef2d293cece8dc62913d79351beb34c5ddf/cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5", size = 449906 }, + { url = "https://files.pythonhosted.org/packages/df/02/aef53d4aa43154b829e9707c8c60bab413cd21819c4a36b0d7aaa83e2a61/cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca", size = 451028 }, + { url = "https://files.pythonhosted.org/packages/79/4b/33494eb0adbcd884656c48f6db0c98ad8a5c678fb8fb5ed41ab546b04d8c/cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02", size = 446520 }, + { url = "https://files.pythonhosted.org/packages/b7/8b/06f30caa03b5b3ac006de4f93478dbd0239e2a16566d81a106c322dc4f79/cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192", size = 442655 }, + { url = "https://files.pythonhosted.org/packages/47/97/137f0e3d2304df2060abb872a5830af809d7559a5a4b6a295afb02728e65/cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314", size = 170236 }, + { url = "https://files.pythonhosted.org/packages/c9/e3/0a52838832408cfbbf3a59cb19bcd17e64eb33795c9710ca7d29ae10b5b7/cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5", size = 178835 }, + { url = "https://files.pythonhosted.org/packages/18/8f/5ff70c7458d61fa8a9752e5ee9c9984c601b0060aae0c619316a1e1f1ee5/cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585", size = 179249 }, + { url = "https://files.pythonhosted.org/packages/3a/75/a162315adeaf47e94a3b7f886a8e31d77b9e525a387eef2d6f0efc96a7c8/cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0", size = 174297 }, + { url = "https://files.pythonhosted.org/packages/85/1f/a3c533f8d377da5ca7edb4f580cc3edc1edbebc45fac8bb3ae60f1176629/cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415", size = 420641 }, + { url = "https://files.pythonhosted.org/packages/77/b7/d3618d612be01e184033eab90006f8ca5b5edafd17bf247439ea4e167d8a/cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d", size = 448814 }, + { url = "https://files.pythonhosted.org/packages/a9/ba/e082df21ebaa9cb29f2c4e1d7e49a29b90fcd667d43632c6674a16d65382/cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984", size = 449647 }, + { url = "https://files.pythonhosted.org/packages/af/cb/53b7bba75a18372d57113ba934b27d0734206c283c1dfcc172347fbd9f76/cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35", size = 445191 }, + { url = "https://files.pythonhosted.org/packages/2d/86/3ca57cddfa0419f6a95d1c8478f8f622ba597e3581fd501bbb915b20eb75/cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27", size = 441236 }, + { url = "https://files.pythonhosted.org/packages/ad/26/7b3a73ab7d82a64664c7c4ea470e4ec4a3c73bb4f02575c543a41e272de5/cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76", size = 433865 }, + { url = "https://files.pythonhosted.org/packages/da/ff/ab939e2c7b3f40d851c0f7192c876f1910f3442080c9c846532993ec3cef/cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3", size = 463090 }, + { url = "https://files.pythonhosted.org/packages/c6/3d/dd085bb831b22ce4d0b7ba8550e6d78960f02f770bbd1314fea3580727f8/cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee", size = 170382 }, + { url = "https://files.pythonhosted.org/packages/a8/16/06b84a7063a4c0a2b081030fdd976022086da9c14e80a9ed4ba0183a98a9/cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c", size = 179079 }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", +] +dependencies = [ + { name = "pycparser", version = "2.22", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191 }, + { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592 }, + { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024 }, + { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188 }, + { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571 }, + { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687 }, + { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211 }, + { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325 }, + { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784 }, + { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564 }, + { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804 }, + { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299 }, + { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264 }, + { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651 }, + { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, + { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 }, + { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 }, + { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 }, + { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 }, + { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 }, + { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 }, + { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 }, + { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 }, + { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 }, + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, + { url = "https://files.pythonhosted.org/packages/48/08/15bf6b43ae9bd06f6b00ad8a91f5a8fe1069d4c9fab550a866755402724e/cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b", size = 182457 }, + { url = "https://files.pythonhosted.org/packages/c2/5b/f1523dd545f92f7df468e5f653ffa4df30ac222f3c884e51e139878f1cb5/cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964", size = 425932 }, + { url = "https://files.pythonhosted.org/packages/53/93/7e547ab4105969cc8c93b38a667b82a835dd2cc78f3a7dad6130cfd41e1d/cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9", size = 448585 }, + { url = "https://files.pythonhosted.org/packages/56/c4/a308f2c332006206bb511de219efeff090e9d63529ba0a77aae72e82248b/cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc", size = 456268 }, + { url = "https://files.pythonhosted.org/packages/ca/5b/b63681518265f2f4060d2b60755c1c77ec89e5e045fc3773b72735ddaad5/cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c", size = 436592 }, + { url = "https://files.pythonhosted.org/packages/bb/19/b51af9f4a4faa4a8ac5a0e5d5c2522dcd9703d07fac69da34a36c4d960d3/cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1", size = 446512 }, + { url = "https://files.pythonhosted.org/packages/e2/63/2bed8323890cb613bbecda807688a31ed11a7fe7afe31f8faaae0206a9a3/cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8", size = 171576 }, + { url = "https://files.pythonhosted.org/packages/2f/70/80c33b044ebc79527447fd4fbc5455d514c3bb840dede4455de97da39b4d/cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1", size = 181229 }, + { url = "https://files.pythonhosted.org/packages/b9/ea/8bb50596b8ffbc49ddd7a1ad305035daa770202a6b782fc164647c2673ad/cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16", size = 182220 }, + { url = "https://files.pythonhosted.org/packages/ae/11/e77c8cd24f58285a82c23af484cf5b124a376b32644e445960d1a4654c3a/cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36", size = 178605 }, + { url = "https://files.pythonhosted.org/packages/ed/65/25a8dc32c53bf5b7b6c2686b42ae2ad58743f7ff644844af7cdb29b49361/cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8", size = 424910 }, + { url = "https://files.pythonhosted.org/packages/42/7a/9d086fab7c66bd7c4d0f27c57a1b6b068ced810afc498cc8c49e0088661c/cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576", size = 447200 }, + { url = "https://files.pythonhosted.org/packages/da/63/1785ced118ce92a993b0ec9e0d0ac8dc3e5dbfbcaa81135be56c69cabbb6/cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87", size = 454565 }, + { url = "https://files.pythonhosted.org/packages/74/06/90b8a44abf3556599cdec107f7290277ae8901a58f75e6fe8f970cd72418/cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0", size = 435635 }, + { url = "https://files.pythonhosted.org/packages/bd/62/a1f468e5708a70b1d86ead5bab5520861d9c7eacce4a885ded9faa7729c3/cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3", size = 445218 }, + { url = "https://files.pythonhosted.org/packages/5b/95/b34462f3ccb09c2594aa782d90a90b045de4ff1f70148ee79c69d37a0a5a/cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595", size = 460486 }, + { url = "https://files.pythonhosted.org/packages/fc/fc/a1e4bebd8d680febd29cf6c8a40067182b64f00c7d105f8f26b5bc54317b/cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a", size = 437911 }, + { url = "https://files.pythonhosted.org/packages/e6/c3/21cab7a6154b6a5ea330ae80de386e7665254835b9e98ecc1340b3a7de9a/cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e", size = 460632 }, + { url = "https://files.pythonhosted.org/packages/cb/b5/fd9f8b5a84010ca169ee49f4e4ad6f8c05f4e3545b72ee041dbbcb159882/cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7", size = 171820 }, + { url = "https://files.pythonhosted.org/packages/8c/52/b08750ce0bce45c143e1b5d7357ee8c55341b52bdef4b0f081af1eb248c2/cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662", size = 181290 }, +] + +[[package]] +name = "charset-normalizer" +version = "2.0.12" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.6.8' and python_full_version < '3.7'", + "python_full_version < '3.6.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/56/31/7bcaf657fafb3c6db8c787a865434290b726653c912085fbd371e9b92e1c/charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597", size = 79105 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/b3/24afc8868eba069a7f03650ac750a778862dc34941a4bebeb58706715726/charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df", size = 39623 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", + "python_full_version == '3.7.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/58/5580c1716040bc89206c77d8f74418caf82ce519aae06450393ca73475d1/charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", size = 198013 }, + { url = "https://files.pythonhosted.org/packages/d0/11/00341177ae71c6f5159a08168bcb98c6e6d196d372c94511f9f6c9afe0c6/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", size = 141285 }, + { url = "https://files.pythonhosted.org/packages/01/09/11d684ea5819e5a8f5100fb0b38cf8d02b514746607934134d31233e02c8/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", size = 151449 }, + { url = "https://files.pythonhosted.org/packages/08/06/9f5a12939db324d905dc1f70591ae7d7898d030d7662f0d426e2286f68c9/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", size = 143892 }, + { url = "https://files.pythonhosted.org/packages/93/62/5e89cdfe04584cb7f4d36003ffa2936681b03ecc0754f8e969c2becb7e24/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", size = 146123 }, + { url = "https://files.pythonhosted.org/packages/a9/ac/ab729a15c516da2ab70a05f8722ecfccc3f04ed7a18e45c75bbbaa347d61/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", size = 147943 }, + { url = "https://files.pythonhosted.org/packages/03/d2/3f392f23f042615689456e9a274640c1d2e5dd1d52de36ab8f7955f8f050/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", size = 142063 }, + { url = "https://files.pythonhosted.org/packages/f2/e3/e20aae5e1039a2cd9b08d9205f52142329f887f8cf70da3650326670bddf/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", size = 150578 }, + { url = "https://files.pythonhosted.org/packages/8d/af/779ad72a4da0aed925e1139d458adc486e61076d7ecdcc09e610ea8678db/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", size = 153629 }, + { url = "https://files.pythonhosted.org/packages/c2/b6/7aa450b278e7aa92cf7732140bfd8be21f5f29d5bf334ae987c945276639/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", size = 150778 }, + { url = "https://files.pythonhosted.org/packages/39/f4/d9f4f712d0951dcbfd42920d3db81b00dd23b6ab520419626f4023334056/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", size = 146453 }, + { url = "https://files.pythonhosted.org/packages/49/2b/999d0314e4ee0cff3cb83e6bc9aeddd397eeed693edb4facb901eb8fbb69/charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", size = 95479 }, + { url = "https://files.pythonhosted.org/packages/2d/ce/3cbed41cff67e455a386fb5e5dd8906cdda2ed92fbc6297921f2e4419309/charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", size = 102790 }, + { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995 }, + { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471 }, + { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831 }, + { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335 }, + { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862 }, + { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673 }, + { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211 }, + { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039 }, + { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939 }, + { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075 }, + { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340 }, + { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205 }, + { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441 }, + { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, + { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, + { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, + { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, + { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, + { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, + { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, + { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, + { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, + { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, + { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, + { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, + { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, + { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, + { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, + { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, + { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, + { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, + { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, + { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, + { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, + { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, + { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, + { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, + { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, + { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, + { url = "https://files.pythonhosted.org/packages/bb/a6/3a22521736a3a75263ce930a2257eb60fd2a6b8fd9293ea1271a074a202f/charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089", size = 135247 }, + { url = "https://files.pythonhosted.org/packages/c2/1e/fa099993d17c00addbb76b3bfcd3ab1692fefc4e193ba738e111c09cfbe2/charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d", size = 144536 }, + { url = "https://files.pythonhosted.org/packages/c2/95/8725b2b2fcb418abe048b5558af42e444161433a76b47759539fe5bb7992/charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf", size = 137661 }, + { url = "https://files.pythonhosted.org/packages/10/2f/6e2c727649d5e97ee17230f4c064480c69eb563f52ba87df448647d77a99/charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e", size = 138592 }, + { url = "https://files.pythonhosted.org/packages/e9/1b/0cad3a4f1906dc36a9cc89093d36b134638463e8e43479dbb96fd47d99ef/charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a", size = 141250 }, + { url = "https://files.pythonhosted.org/packages/8f/aa/73a89701dd661d9b5d24d0594c42ed0f7a3aed4448cc4f8f7315d0701399/charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd", size = 135234 }, + { url = "https://files.pythonhosted.org/packages/a8/00/b8b43690ecac81e5038dc6332681757123ee9c4c15eb4a6ecf0232393f59/charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534", size = 142457 }, + { url = "https://files.pythonhosted.org/packages/ec/a3/2db14427b37cefbf9a9ec3d3c861e1b1721e4d4507536df102c2854966dc/charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e", size = 145967 }, + { url = "https://files.pythonhosted.org/packages/0a/97/a183d752295997d95d20f2aa34d66ff6abda5af5482d0e7995dbbc9a753a/charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e", size = 145129 }, + { url = "https://files.pythonhosted.org/packages/96/c6/f62d90c0e66525a374c35a1389f4290e5d7a731cd2cfacd9740272f927f9/charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa", size = 138515 }, + { url = "https://files.pythonhosted.org/packages/0b/fe/4ade940a1b4bac031357b4a74fc8406a2ecd9dfc007dea850c5b7ee348de/charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487", size = 93486 }, + { url = "https://files.pythonhosted.org/packages/89/17/74bb34d02d62a21cc626c7d4db475b6f2207980f6beb002051db8342b139/charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d", size = 100195 }, + { url = "https://files.pythonhosted.org/packages/10/bd/6517ea94f2672e801011d50b5d06be2a0deaf566aea27bcdcd47e5195357/charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c", size = 195653 }, + { url = "https://files.pythonhosted.org/packages/e5/0d/815a2ba3f283b4eeaa5ece57acade365c5b4135f65a807a083c818716582/charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9", size = 140701 }, + { url = "https://files.pythonhosted.org/packages/aa/17/c94be7ee0d142687e047fe1de72060f6d6837f40eedc26e87e6e124a3fc6/charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8", size = 150495 }, + { url = "https://files.pythonhosted.org/packages/f7/33/557ac796c47165fc141e4fb71d7b0310f67e05cb420756f3a82e0a0068e0/charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6", size = 142946 }, + { url = "https://files.pythonhosted.org/packages/1e/0d/38ef4ae41e9248d63fc4998d933cae22473b1b2ac4122cf908d0f5eb32aa/charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c", size = 144737 }, + { url = "https://files.pythonhosted.org/packages/43/01/754cdb29dd0560f58290aaaa284d43eea343ad0512e6ad3b8b5c11f08592/charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a", size = 147471 }, + { url = "https://files.pythonhosted.org/packages/ba/cd/861883ba5160c7a9bd242c30b2c71074cda2aefcc0addc91118e0d4e0765/charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd", size = 140801 }, + { url = "https://files.pythonhosted.org/packages/6f/7f/0c0dad447819e90b93f8ed238cc8f11b91353c23c19e70fa80483a155bed/charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd", size = 149312 }, + { url = "https://files.pythonhosted.org/packages/8e/09/9f8abcc6fff60fb727268b63c376c8c79cc37b833c2dfe1f535dfb59523b/charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824", size = 152347 }, + { url = "https://files.pythonhosted.org/packages/be/e5/3f363dad2e24378f88ccf63ecc39e817c29f32e308ef21a7a6d9c1201165/charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca", size = 149888 }, + { url = "https://files.pythonhosted.org/packages/e4/10/a78c0e91f487b4ad0ef7480ac765e15b774f83de2597f1b6ef0eaf7a2f99/charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b", size = 145169 }, + { url = "https://files.pythonhosted.org/packages/d3/81/396e7d7f5d7420da8273c91175d2e9a3f569288e3611d521685e4b9ac9cc/charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e", size = 95094 }, + { url = "https://files.pythonhosted.org/packages/40/bb/20affbbd9ea29c71ea123769dc568a6d42052ff5089c5fe23e21e21084a6/charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4", size = 102139 }, + { url = "https://files.pythonhosted.org/packages/7f/c0/b913f8f02836ed9ab32ea643c6fe4d3325c3d8627cf6e78098671cafff86/charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41", size = 197867 }, + { url = "https://files.pythonhosted.org/packages/0f/6c/2bee440303d705b6fb1e2ec789543edec83d32d258299b16eed28aad48e0/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f", size = 141385 }, + { url = "https://files.pythonhosted.org/packages/3d/04/cb42585f07f6f9fd3219ffb6f37d5a39b4fd2db2355b23683060029c35f7/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2", size = 151367 }, + { url = "https://files.pythonhosted.org/packages/54/54/2412a5b093acb17f0222de007cc129ec0e0df198b5ad2ce5699355269dfe/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770", size = 143928 }, + { url = "https://files.pythonhosted.org/packages/5a/6d/e2773862b043dcf8a221342954f375392bb2ce6487bcd9f2c1b34e1d6781/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4", size = 146203 }, + { url = "https://files.pythonhosted.org/packages/b9/f8/ca440ef60d8f8916022859885f231abb07ada3c347c03d63f283bec32ef5/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537", size = 148082 }, + { url = "https://files.pythonhosted.org/packages/04/d2/42fd330901aaa4b805a1097856c2edf5095e260a597f65def493f4b8c833/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496", size = 142053 }, + { url = "https://files.pythonhosted.org/packages/9e/af/3a97a4fa3c53586f1910dadfc916e9c4f35eeada36de4108f5096cb7215f/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78", size = 150625 }, + { url = "https://files.pythonhosted.org/packages/26/ae/23d6041322a3556e4da139663d02fb1b3c59a23ab2e2b56432bd2ad63ded/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7", size = 153549 }, + { url = "https://files.pythonhosted.org/packages/94/22/b8f2081c6a77cb20d97e57e0b385b481887aa08019d2459dc2858ed64871/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6", size = 150945 }, + { url = "https://files.pythonhosted.org/packages/c7/0b/c5ec5092747f801b8b093cdf5610e732b809d6cb11f4c51e35fc28d1d389/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294", size = 146595 }, + { url = "https://files.pythonhosted.org/packages/0c/5a/0b59704c38470df6768aa154cc87b1ac7c9bb687990a1559dc8765e8627e/charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5", size = 95453 }, + { url = "https://files.pythonhosted.org/packages/85/2d/a9790237cb4d01a6d57afadc8573c8b73c609ade20b80f4cda30802009ee/charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765", size = 102811 }, + { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, +] + +[[package]] +name = "cryptography" +version = "40.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.6.8' and python_full_version < '3.7'", + "python_full_version < '3.6.8'", +] +dependencies = [ + { name = "cffi", version = "1.15.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f7/80/04cc7637238b78f8e7354900817135c5a23cf66dfb3f3a216c6d630d6833/cryptography-40.0.2.tar.gz", hash = "sha256:c33c0d32b8594fa647d2e01dbccc303478e16fdd7cf98652d5b3ed11aa5e5c99", size = 625561 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/aa/285f288e36d398db873d4cc20984c9a132ef5eace539d91babe4c4e94aaa/cryptography-40.0.2-cp36-abi3-macosx_10_12_universal2.whl", hash = "sha256:8f79b5ff5ad9d3218afb1e7e20ea74da5f76943ee5edb7f76e56ec5161ec782b", size = 5138787 }, + { url = "https://files.pythonhosted.org/packages/85/86/a17a4baf08e0ae6496b44f75136f8e14b843fd3d8a3f4105c0fd79d4786b/cryptography-40.0.2-cp36-abi3-macosx_10_12_x86_64.whl", hash = "sha256:05dc219433b14046c476f6f09d7636b92a1c3e5808b9a6536adf4932b3b2c440", size = 2753726 }, + { url = "https://files.pythonhosted.org/packages/0d/91/b2efda2ffb30b1623016d8e8ea6f59dde22b9bc86c0883bc12d965c53dca/cryptography-40.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4df2af28d7bedc84fe45bd49bc35d710aede676e2a4cb7fc6d103a2adc8afe4d", size = 3488594 }, + { url = "https://files.pythonhosted.org/packages/9c/1b/30faebcef9be2df5728a8086b8fc15fff92364fe114fb207b70cd7c81329/cryptography-40.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dcca15d3a19a66e63662dc8d30f8036b07be851a8680eda92d079868f106288", size = 3736224 }, + { url = "https://files.pythonhosted.org/packages/ff/87/cffd495cc78503fb49aa3e19babc126b610174d08aa32c0d1d75c6499afc/cryptography-40.0.2-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a04386fb7bc85fab9cd51b6308633a3c271e3d0d3eae917eebab2fac6219b6d2", size = 3474750 }, + { url = "https://files.pythonhosted.org/packages/8e/34/f54dbfc6d12fa34a50f03bf01319d585e7e9bddd68ad28299b4998e3098b/cryptography-40.0.2-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:adc0d980fd2760c9e5de537c28935cc32b9353baaf28e0814df417619c6c8c3b", size = 3741816 }, + { url = "https://files.pythonhosted.org/packages/88/87/c720c0b56f6363eaa32c582b6240523010691ad973204649526c4ce28e95/cryptography-40.0.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d5a1bd0e9e2031465761dfa920c16b0065ad77321d8a8c1f5ee331021fda65e9", size = 3586148 }, + { url = "https://files.pythonhosted.org/packages/91/89/13174c6167f452598baa8584133993e3d624b6a19e93748e5f2885a442f2/cryptography-40.0.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a95f4802d49faa6a674242e25bfeea6fc2acd915b5e5e29ac90a32b1139cae1c", size = 3813710 }, + { url = "https://files.pythonhosted.org/packages/41/96/e4c439905077508e78ae15577fdd302c1e582d0bc5f96fcc761da1681dd2/cryptography-40.0.2-cp36-abi3-win32.whl", hash = "sha256:aecbb1592b0188e030cb01f82d12556cf72e218280f621deed7d806afd2113f9", size = 2151049 }, + { url = "https://files.pythonhosted.org/packages/55/9f/53e0df7b81f86967d8997c77b71c5255d3bcabfac0c346b1cff061b80e03/cryptography-40.0.2-cp36-abi3-win_amd64.whl", hash = "sha256:b12794f01d4cacfbd3177b9042198f3af1c856eedd0a98f10f141385c809a14b", size = 2614718 }, + { url = "https://files.pythonhosted.org/packages/c6/e9/a004c5ff4a01e38da38c0d20257f4af41f0858719fb25c5a034ee46d40cd/cryptography-40.0.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:142bae539ef28a1c76794cca7f49729e7c54423f615cfd9b0b1fa90ebe53244b", size = 2616334 }, + { url = "https://files.pythonhosted.org/packages/5e/12/e3eb644d2c040a083f3b3ee12553fe2ac273ef7525722438d2ad141d984f/cryptography-40.0.2-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:956ba8701b4ffe91ba59665ed170a2ebbdc6fc0e40de5f6059195d9f2b33ca0e", size = 3382470 }, + { url = "https://files.pythonhosted.org/packages/5c/26/a5bcec07b84ce9064659e15a526976efeb1971cc7fcc61fc71f6a6b659ce/cryptography-40.0.2-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4f01c9863da784558165f5d4d916093737a75203a5c5286fde60e503e4276c7a", size = 3618935 }, + { url = "https://files.pythonhosted.org/packages/ad/d4/a9c46f0fedfad9198740e77b99cb69d4596dfb0ef0e70440f2780373fb24/cryptography-40.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3daf9b114213f8ba460b829a02896789751626a2a4e7a43a28ee77c04b5e4958", size = 2481391 }, + { url = "https://files.pythonhosted.org/packages/72/68/6e942224400261a3f947df8abad1ffe95e338e2466f7a0b5b87f33d8a196/cryptography-40.0.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48f388d0d153350f378c7f7b41497a54ff1513c816bcbbcafe5b829e59b9ce5b", size = 2616230 }, + { url = "https://files.pythonhosted.org/packages/eb/a0/496b34c04a971dafef68fa5f58222b5688f63f956f3b3f92664165a0921f/cryptography-40.0.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c0764e72b36a3dc065c155e5b22f93df465da9c39af65516fe04ed3c68c92636", size = 3383279 }, + { url = "https://files.pythonhosted.org/packages/75/9c/446d0209840eaa639abc564ccac3a8b4c716629bb3424d2f4bdb618cbf34/cryptography-40.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:cbaba590180cba88cb99a5f76f90808a624f18b169b90a4abb40c1fd8c19420e", size = 3618594 }, + { url = "https://files.pythonhosted.org/packages/66/f1/dbf368e3565c4b9b7784b4f595e45ff3b3cde57a9d54aeee9681d2c1a7e6/cryptography-40.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7a38250f433cd41df7fcb763caa3ee9362777fdb4dc642b9a349721d2bf47404", size = 2481452 }, +] + +[[package]] +name = "cryptography" +version = "44.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", + "python_full_version == '3.7.*'", +] +dependencies = [ + { name = "cffi", version = "1.15.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*' and platform_python_implementation != 'PyPy'" }, + { name = "cffi", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8' and platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/25/4ce80c78963834b8a9fd1cc1266be5ed8d1840785c0f2e1b73b8d128d505/cryptography-44.0.2.tar.gz", hash = "sha256:c63454aa261a0cf0c5b4718349629793e9e634993538db841165b3df74f37ec0", size = 710807 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/ef/83e632cfa801b221570c5f58c0369db6fa6cef7d9ff859feab1aae1a8a0f/cryptography-44.0.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:efcfe97d1b3c79e486554efddeb8f6f53a4cdd4cf6086642784fa31fc384e1d7", size = 6676361 }, + { url = "https://files.pythonhosted.org/packages/30/ec/7ea7c1e4c8fc8329506b46c6c4a52e2f20318425d48e0fe597977c71dbce/cryptography-44.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29ecec49f3ba3f3849362854b7253a9f59799e3763b0c9d0826259a88efa02f1", size = 3952350 }, + { url = "https://files.pythonhosted.org/packages/27/61/72e3afdb3c5ac510330feba4fc1faa0fe62e070592d6ad00c40bb69165e5/cryptography-44.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc821e161ae88bfe8088d11bb39caf2916562e0a2dc7b6d56714a48b784ef0bb", size = 4166572 }, + { url = "https://files.pythonhosted.org/packages/26/e4/ba680f0b35ed4a07d87f9e98f3ebccb05091f3bf6b5a478b943253b3bbd5/cryptography-44.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3c00b6b757b32ce0f62c574b78b939afab9eecaf597c4d624caca4f9e71e7843", size = 3958124 }, + { url = "https://files.pythonhosted.org/packages/9c/e8/44ae3e68c8b6d1cbc59040288056df2ad7f7f03bbcaca6b503c737ab8e73/cryptography-44.0.2-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7bdcd82189759aba3816d1f729ce42ffded1ac304c151d0a8e89b9996ab863d5", size = 3678122 }, + { url = "https://files.pythonhosted.org/packages/27/7b/664ea5e0d1eab511a10e480baf1c5d3e681c7d91718f60e149cec09edf01/cryptography-44.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4973da6ca3db4405c54cd0b26d328be54c7747e89e284fcff166132eb7bccc9c", size = 4191831 }, + { url = "https://files.pythonhosted.org/packages/2a/07/79554a9c40eb11345e1861f46f845fa71c9e25bf66d132e123d9feb8e7f9/cryptography-44.0.2-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4e389622b6927d8133f314949a9812972711a111d577a5d1f4bee5e58736b80a", size = 3960583 }, + { url = "https://files.pythonhosted.org/packages/bb/6d/858e356a49a4f0b591bd6789d821427de18432212e137290b6d8a817e9bf/cryptography-44.0.2-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f514ef4cd14bb6fb484b4a60203e912cfcb64f2ab139e88c2274511514bf7308", size = 4191753 }, + { url = "https://files.pythonhosted.org/packages/b2/80/62df41ba4916067fa6b125aa8c14d7e9181773f0d5d0bd4dcef580d8b7c6/cryptography-44.0.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1bc312dfb7a6e5d66082c87c34c8a62176e684b6fe3d90fcfe1568de675e6688", size = 4079550 }, + { url = "https://files.pythonhosted.org/packages/f3/cd/2558cc08f7b1bb40683f99ff4327f8dcfc7de3affc669e9065e14824511b/cryptography-44.0.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b721b8b4d948b218c88cb8c45a01793483821e709afe5f622861fc6182b20a7", size = 4298367 }, + { url = "https://files.pythonhosted.org/packages/71/59/94ccc74788945bc3bd4cf355d19867e8057ff5fdbcac781b1ff95b700fb1/cryptography-44.0.2-cp37-abi3-win32.whl", hash = "sha256:51e4de3af4ec3899d6d178a8c005226491c27c4ba84101bfb59c901e10ca9f79", size = 2772843 }, + { url = "https://files.pythonhosted.org/packages/ca/2c/0d0bbaf61ba05acb32f0841853cfa33ebb7a9ab3d9ed8bb004bd39f2da6a/cryptography-44.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:c505d61b6176aaf982c5717ce04e87da5abc9a36a5b39ac03905c4aafe8de7aa", size = 3209057 }, + { url = "https://files.pythonhosted.org/packages/9e/be/7a26142e6d0f7683d8a382dd963745e65db895a79a280a30525ec92be890/cryptography-44.0.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e0ddd63e6bf1161800592c71ac794d3fb8001f2caebe0966e77c5234fa9efc3", size = 6677789 }, + { url = "https://files.pythonhosted.org/packages/06/88/638865be7198a84a7713950b1db7343391c6066a20e614f8fa286eb178ed/cryptography-44.0.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81276f0ea79a208d961c433a947029e1a15948966658cf6710bbabb60fcc2639", size = 3951919 }, + { url = "https://files.pythonhosted.org/packages/d7/fc/99fe639bcdf58561dfad1faa8a7369d1dc13f20acd78371bb97a01613585/cryptography-44.0.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a1e657c0f4ea2a23304ee3f964db058c9e9e635cc7019c4aa21c330755ef6fd", size = 4167812 }, + { url = "https://files.pythonhosted.org/packages/53/7b/aafe60210ec93d5d7f552592a28192e51d3c6b6be449e7fd0a91399b5d07/cryptography-44.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6210c05941994290f3f7f175a4a57dbbb2afd9273657614c506d5976db061181", size = 3958571 }, + { url = "https://files.pythonhosted.org/packages/16/32/051f7ce79ad5a6ef5e26a92b37f172ee2d6e1cce09931646eef8de1e9827/cryptography-44.0.2-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1c3572526997b36f245a96a2b1713bf79ce99b271bbcf084beb6b9b075f29ea", size = 3679832 }, + { url = "https://files.pythonhosted.org/packages/78/2b/999b2a1e1ba2206f2d3bca267d68f350beb2b048a41ea827e08ce7260098/cryptography-44.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b042d2a275c8cee83a4b7ae30c45a15e6a4baa65a179a0ec2d78ebb90e4f6699", size = 4193719 }, + { url = "https://files.pythonhosted.org/packages/72/97/430e56e39a1356e8e8f10f723211a0e256e11895ef1a135f30d7d40f2540/cryptography-44.0.2-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d03806036b4f89e3b13b6218fefea8d5312e450935b1a2d55f0524e2ed7c59d9", size = 3960852 }, + { url = "https://files.pythonhosted.org/packages/89/33/c1cf182c152e1d262cac56850939530c05ca6c8d149aa0dcee490b417e99/cryptography-44.0.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c7362add18b416b69d58c910caa217f980c5ef39b23a38a0880dfd87bdf8cd23", size = 4193906 }, + { url = "https://files.pythonhosted.org/packages/e1/99/87cf26d4f125380dc674233971069bc28d19b07f7755b29861570e513650/cryptography-44.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8cadc6e3b5a1f144a039ea08a0bdb03a2a92e19c46be3285123d32029f40a922", size = 4081572 }, + { url = "https://files.pythonhosted.org/packages/b3/9f/6a3e0391957cc0c5f84aef9fbdd763035f2b52e998a53f99345e3ac69312/cryptography-44.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6f101b1f780f7fc613d040ca4bdf835c6ef3b00e9bd7125a4255ec574c7916e4", size = 4298631 }, + { url = "https://files.pythonhosted.org/packages/e2/a5/5bc097adb4b6d22a24dea53c51f37e480aaec3465285c253098642696423/cryptography-44.0.2-cp39-abi3-win32.whl", hash = "sha256:3dc62975e31617badc19a906481deacdeb80b4bb454394b4098e3f2525a488c5", size = 2773792 }, + { url = "https://files.pythonhosted.org/packages/33/cf/1f7649b8b9a3543e042d3f348e398a061923ac05b507f3f4d95f11938aa9/cryptography-44.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:5f6f90b72d8ccadb9c6e311c775c8305381db88374c65fa1a68250aa8a9cb3a6", size = 3210957 }, + { url = "https://files.pythonhosted.org/packages/99/10/173be140714d2ebaea8b641ff801cbcb3ef23101a2981cbf08057876f89e/cryptography-44.0.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:af4ff3e388f2fa7bff9f7f2b31b87d5651c45731d3e8cfa0944be43dff5cfbdb", size = 3396886 }, + { url = "https://files.pythonhosted.org/packages/2f/b4/424ea2d0fce08c24ede307cead3409ecbfc2f566725d4701b9754c0a1174/cryptography-44.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0529b1d5a0105dd3731fa65680b45ce49da4d8115ea76e9da77a875396727b41", size = 3892387 }, + { url = "https://files.pythonhosted.org/packages/28/20/8eaa1a4f7c68a1cb15019dbaad59c812d4df4fac6fd5f7b0b9c5177f1edd/cryptography-44.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7ca25849404be2f8e4b3c59483d9d3c51298a22c1c61a0e84415104dacaf5562", size = 4109922 }, + { url = "https://files.pythonhosted.org/packages/11/25/5ed9a17d532c32b3bc81cc294d21a36c772d053981c22bd678396bc4ae30/cryptography-44.0.2-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:268e4e9b177c76d569e8a145a6939eca9a5fec658c932348598818acf31ae9a5", size = 3895715 }, + { url = "https://files.pythonhosted.org/packages/63/31/2aac03b19c6329b62c45ba4e091f9de0b8f687e1b0cd84f101401bece343/cryptography-44.0.2-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:9eb9d22b0a5d8fd9925a7764a054dca914000607dff201a24c791ff5c799e1fa", size = 4109876 }, + { url = "https://files.pythonhosted.org/packages/99/ec/6e560908349843718db1a782673f36852952d52a55ab14e46c42c8a7690a/cryptography-44.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2bf7bf75f7df9715f810d1b038870309342bff3069c5bd8c6b96128cb158668d", size = 3131719 }, + { url = "https://files.pythonhosted.org/packages/d6/d7/f30e75a6aa7d0f65031886fa4a1485c2fbfe25a1896953920f6a9cfe2d3b/cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:909c97ab43a9c0c0b0ada7a1281430e4e5ec0458e6d9244c0e821bbf152f061d", size = 3887513 }, + { url = "https://files.pythonhosted.org/packages/9c/b4/7a494ce1032323ca9db9a3661894c66e0d7142ad2079a4249303402d8c71/cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:96e7a5e9d6e71f9f4fca8eebfd603f8e86c5225bb18eb621b2c1e50b290a9471", size = 4107432 }, + { url = "https://files.pythonhosted.org/packages/45/f8/6b3ec0bc56123b344a8d2b3264a325646d2dcdbdd9848b5e6f3d37db90b3/cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d1b3031093a366ac767b3feb8bcddb596671b3aaff82d4050f984da0c248b615", size = 3891421 }, + { url = "https://files.pythonhosted.org/packages/57/ff/f3b4b2d007c2a646b0f69440ab06224f9cf37a977a72cdb7b50632174e8a/cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:04abd71114848aa25edb28e225ab5f268096f44cf0127f3d36975bdf1bdf3390", size = 4107081 }, +] + +[[package]] +name = "deprecated" +version = "1.2.18" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wrapt", marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/97/06afe62762c9a8a86af0cfb7bfdab22a43ad17138b07af5b1a58442690a2/deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d", size = 2928744 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/c6/ac0b6c1e2d138f1002bcf799d330bd6d85084fece321e662a14223794041/Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec", size = 9998 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "importlib-metadata" +version = "4.8.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.6.8' and python_full_version < '3.7'", + "python_full_version < '3.6.8'", +] +dependencies = [ + { name = "typing-extensions", version = "4.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, + { name = "zipp", version = "3.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/ed/e65128cc5cb1580f22ee3009d9187ecdfcc43ffb3b581fe854b24e87d8e7/importlib_metadata-4.8.3.tar.gz", hash = "sha256:766abffff765960fcc18003801f7044eb6755ffae4521c8e8ce8e83b9c9b0668", size = 41979 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/a1/b153a0a4caf7a7e3f15c2cd56c7702e2cf3d89b1b359d1f1c5e59d68f4ce/importlib_metadata-4.8.3-py3-none-any.whl", hash = "sha256:65a9576a5b2d58ca44d133c42a241905cc45e34d2c06fd5ba2bafa221e5d7b5e", size = 17978 }, +] + +[[package]] +name = "importlib-metadata" +version = "6.7.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.7.*'", +] +dependencies = [ + { name = "typing-extensions", version = "4.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" }, + { name = "zipp", version = "3.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/82/f6e29c8d5c098b6be61460371c2c5591f4a335923639edec43b3830650a4/importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4", size = 53569 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/94/64287b38c7de4c90683630338cf28f129decbba0a44f0c6db35a873c73c4/importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5", size = 22934 }, +] + +[[package]] +name = "jwcrypto" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.7.*'", + "python_full_version >= '3.6.8' and python_full_version < '3.7'", + "python_full_version < '3.6.8'", +] +dependencies = [ + { name = "cryptography", version = "40.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, + { name = "cryptography", version = "44.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" }, + { name = "deprecated", marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/a6/0a230f3ef72ed77edeeff67258a40a235d3cb9faa8a5e2151c38f5995b65/jwcrypto-1.5.1.tar.gz", hash = "sha256:48bb9bf433777136253579e52b75ffe0f9a4a721d133d01f45a0b91ed5f4f1ae", size = 86838 } + +[[package]] +name = "jwcrypto" +version = "1.5.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", +] +dependencies = [ + { name = "cryptography", version = "44.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, + { name = "typing-extensions", version = "4.12.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/db/870e5d5fb311b0bcf049630b5ba3abca2d339fd5e13ba175b4c13b456d08/jwcrypto-1.5.6.tar.gz", hash = "sha256:771a87762a0c081ae6166958a954f80848820b2ab066937dc8b8379d65b1b039", size = 87168 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/58/4a1880ea64032185e9ae9f63940c9327c6952d5584ea544a8f66972f2fda/jwcrypto-1.5.6-py3-none-any.whl", hash = "sha256:150d2b0ebbdb8f40b77f543fb44ffd2baeff48788be71f67f03566692fd55789", size = 92520 }, +] + +[[package]] +name = "mypy" +version = "0.971" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.6.8' and python_full_version < '3.7'", + "python_full_version < '3.6.8'", +] +dependencies = [ + { name = "mypy-extensions", marker = "python_full_version < '3.7'" }, + { name = "tomli", version = "1.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, + { name = "typed-ast", marker = "python_full_version < '3.7'" }, + { name = "typing-extensions", version = "4.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/66/00f7f751140fe6953603fb0cd56dee0314842cfe358884ca3025589ca81c/mypy-0.971.tar.gz", hash = "sha256:40b0f21484238269ae6a57200c807d80debc6459d444c0489a102d7c6a75fa56", size = 2757982 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/97/ff71b0cdf61065db040ffe34ae88852d2a47de8b2b49c51608caf03771ed/mypy-0.971-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2899a3cbd394da157194f913a931edfd4be5f274a88041c9dc2d9cdcb1c315c", size = 18495630 }, + { url = "https://files.pythonhosted.org/packages/18/e1/d3e577229691dae4c8039cd87ef981482812ba7c5f5999fd67127af0f8a1/mypy-0.971-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:98e02d56ebe93981c41211c05adb630d1d26c14195d04d95e49cd97dbc046dc5", size = 11062018 }, + { url = "https://files.pythonhosted.org/packages/f9/be/e5c50777159473c8dfb7cb512e62fbca19df4a6e9db711f17d77c14fb62b/mypy-0.971-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:19830b7dba7d5356d3e26e2427a2ec91c994cd92d983142cbd025ebe81d69cf3", size = 10017604 }, + { url = "https://files.pythonhosted.org/packages/f0/b7/d39405fb53e0ae99c26cba3c8ab50717eafb7aeb64beea6efbd42a17ef82/mypy-0.971-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:02ef476f6dcb86e6f502ae39a16b93285fef97e7f1ff22932b657d1ef1f28655", size = 17645516 }, + { url = "https://files.pythonhosted.org/packages/9e/01/a81de921bc3efde879f6eab5ff4d4bb33b037581f78eccfa28a47105d0b3/mypy-0.971-cp310-cp310-win_amd64.whl", hash = "sha256:25c5750ba5609a0c7550b73a33deb314ecfb559c350bb050b655505e8aed4103", size = 8739572 }, + { url = "https://files.pythonhosted.org/packages/eb/a3/cb03e2131fe4253a6d942de668c689a1e6b61a237075b4b1c2527d92842c/mypy-0.971-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d3348e7eb2eea2472db611486846742d5d52d1290576de99d59edeb7cd4a42ca", size = 10518724 }, + { url = "https://files.pythonhosted.org/packages/94/3a/6279b09780ad0190c93f710ab0c5509c977e9f25b27bdea33758445e8e3f/mypy-0.971-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3fa7a477b9900be9b7dd4bab30a12759e5abe9586574ceb944bc29cddf8f0417", size = 14864567 }, + { url = "https://files.pythonhosted.org/packages/a3/1c/97a81d851751b3393989078a9a833149a26c341aa799c6d9419871aec1af/mypy-0.971-cp36-cp36m-win_amd64.whl", hash = "sha256:2ad53cf9c3adc43cf3bea0a7d01a2f2e86db9fe7596dfecb4496a5dda63cbb09", size = 8425014 }, + { url = "https://files.pythonhosted.org/packages/b4/04/ea16449bb496794508a1834ce69fa1630e96291360bbacaf5439af370573/mypy-0.971-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:855048b6feb6dfe09d3353466004490b1872887150c5bb5caad7838b57328cc8", size = 10679238 }, + { url = "https://files.pythonhosted.org/packages/2f/bb/636978d06c59d632a79684583b3afad5998b221c0f907b9c458398d10710/mypy-0.971-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:23488a14a83bca6e54402c2e6435467a4138785df93ec85aeff64c6170077fb0", size = 15002505 }, + { url = "https://files.pythonhosted.org/packages/28/2f/30dcdd46de1d19340da8b9e8fb41d907001d8f9b8c3d443d5128925e10b2/mypy-0.971-cp37-cp37m-win_amd64.whl", hash = "sha256:4b21e5b1a70dfb972490035128f305c39bc4bc253f34e96a4adf9127cf943eb2", size = 8442000 }, + { url = "https://files.pythonhosted.org/packages/77/a8/adecd715710c9338586af6a6fe66055a6b4c6799364fbe24505154ca8fd4/mypy-0.971-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9796a2ba7b4b538649caa5cecd398d873f4022ed2333ffde58eaf604c4d2cb27", size = 18389736 }, + { url = "https://files.pythonhosted.org/packages/4c/7f/c20f9283d6659c6ebf790cbc4c12183ceaa4adf8c03df15369c220b2c03a/mypy-0.971-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5a361d92635ad4ada1b1b2d3630fc2f53f2127d51cf2def9db83cba32e47c856", size = 10999183 }, + { url = "https://files.pythonhosted.org/packages/c7/13/8202db537028ac473c05f43c046bf547a0c4be0454d9ddab0c7a68525ee9/mypy-0.971-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b793b899f7cf563b1e7044a5c97361196b938e92f0a4343a5d27966a53d2ec71", size = 9974998 }, + { url = "https://files.pythonhosted.org/packages/34/6f/232461e55913d1320f33fc7e8fa8119af9db6182876093e9de189df9dbbe/mypy-0.971-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d1ea5d12c8e2d266b5fb8c7a5d2e9c0219fedfeb493b7ed60cd350322384ac27", size = 17346581 }, + { url = "https://files.pythonhosted.org/packages/74/e5/e65b82813bdea739266e590e5fda72ec991c3e1223135342724a997fb3ff/mypy-0.971-cp38-cp38-win_amd64.whl", hash = "sha256:23c7ff43fff4b0df93a186581885c8512bc50fc4d4910e0f838e35d6bb6b5e58", size = 8719594 }, + { url = "https://files.pythonhosted.org/packages/01/2d/7aab0a38e05dcaf14bf1f3e238f1c1a6f7bc16065eb1db8ffed62f860d27/mypy-0.971-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1f7656b69974a6933e987ee8ffb951d836272d6c0f81d727f1d0e2696074d9e6", size = 18486774 }, + { url = "https://files.pythonhosted.org/packages/15/61/ffc2cf8cd1507f29444a50d0a93ecd3cc9a267c019f8c705447f40b6180d/mypy-0.971-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d2022bfadb7a5c2ef410d6a7c9763188afdb7f3533f22a0a32be10d571ee4bbe", size = 11055594 }, + { url = "https://files.pythonhosted.org/packages/0b/02/644d6c498e9379f76ce5128a15f92281621770510f0fb9e321b530e1cd2c/mypy-0.971-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef943c72a786b0f8d90fd76e9b39ce81fb7171172daf84bf43eaf937e9f220a9", size = 10010336 }, + { url = "https://files.pythonhosted.org/packages/9a/f6/51c1fe6dcd657fbecb130bd78ea665a26e0c44e637373de6ac141a54f83c/mypy-0.971-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d744f72eb39f69312bc6c2abf8ff6656973120e2eb3f3ec4f758ed47e414a4bf", size = 17609774 }, + { url = "https://files.pythonhosted.org/packages/77/91/53304c05871cc38e95e7e5c3e2d0f84c1822d61a731c02434a20b12ea118/mypy-0.971-cp39-cp39-win_amd64.whl", hash = "sha256:77a514ea15d3007d33a9e2157b0ba9c267496acf12a7f2b9b9f8446337aac5b0", size = 8738328 }, + { url = "https://files.pythonhosted.org/packages/6c/c6/20dd5b70962af557101b2d3a7052f8298a3e94708b62bc5ad7ca713b59bb/mypy-0.971-py3-none-any.whl", hash = "sha256:0d054ef16b071149917085f51f89555a576e2618d5d9dd70bd6eea6410af3ac9", size = 2550912 }, +] + +[[package]] +name = "mypy" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.7.*'", +] +dependencies = [ + { name = "mypy-extensions", marker = "python_full_version == '3.7.*'" }, + { name = "tomli", version = "2.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" }, + { name = "typed-ast", marker = "python_full_version == '3.7.*'" }, + { name = "typing-extensions", version = "4.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/28/d8a8233ff167d06108e53b7aefb4a8d7350adbbf9d7abd980f17fdb7a3a6/mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b", size = 2855162 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/3b/1c7363863b56c059f60a1dfdca9ac774a22ba64b7a4da0ee58ee53e5243f/mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8", size = 10451043 }, + { url = "https://files.pythonhosted.org/packages/a7/24/6f0df1874118839db1155fed62a4bd7e80c181367ff8ea07d40fbaffcfb4/mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878", size = 9542079 }, + { url = "https://files.pythonhosted.org/packages/04/5c/deeac94fcccd11aa621e6b350df333e1b809b11443774ea67582cc0205da/mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd", size = 11974913 }, + { url = "https://files.pythonhosted.org/packages/e5/2f/de3c455c54e8cf5e37ea38705c1920f2df470389f8fc051084d2dd8c9c59/mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc", size = 12044492 }, + { url = "https://files.pythonhosted.org/packages/e7/d3/6f65357dcb68109946de70cd55bd2e60f10114f387471302f48d54ff5dae/mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1", size = 8831655 }, + { url = "https://files.pythonhosted.org/packages/94/01/e34e37a044325af4d4af9825c15e8a0d26d89b5a9624b4d0908449d3411b/mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462", size = 10338636 }, + { url = "https://files.pythonhosted.org/packages/92/58/ccc0b714ecbd1a64b34d8ce1c38763ff6431de1d82551904ecc3711fbe05/mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258", size = 9444172 }, + { url = "https://files.pythonhosted.org/packages/73/72/dfc0b46e6905eafd598e7c48c0c4f2e232647e4e36547425c64e6c850495/mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2", size = 11855450 }, + { url = "https://files.pythonhosted.org/packages/66/f4/60739a2d336f3adf5628e7c9b920d16e8af6dc078550d615e4ba2a1d7759/mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7", size = 11928679 }, + { url = "https://files.pythonhosted.org/packages/8c/26/6ff2b55bf8b605a4cc898883654c2ca4dd4feedf0bb04ecaacf60d165cde/mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01", size = 8831134 }, + { url = "https://files.pythonhosted.org/packages/95/47/fb69dad9634af9f1dab69f8b4031d674592384b59c7171852b1fbed6de15/mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b", size = 10101278 }, + { url = "https://files.pythonhosted.org/packages/65/f7/77339904a3415cadca5551f2ea0c74feefc9b7187636a292690788f4d4b3/mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b", size = 11643877 }, + { url = "https://files.pythonhosted.org/packages/f5/93/ae39163ae84266d24d1fcf8ee1e2db1e0346e09de97570dd101a07ccf876/mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7", size = 11702718 }, + { url = "https://files.pythonhosted.org/packages/13/3b/3b7de921626547b36c34b91c74cfbda260210df7c49bd3d315015cfd6005/mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9", size = 8551181 }, + { url = "https://files.pythonhosted.org/packages/49/7d/63bab763e4d44e1a7c341fb64496ddf20970780935596ffed9ed2d85eae7/mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042", size = 10390236 }, + { url = "https://files.pythonhosted.org/packages/23/3f/54a87d933440416a1efd7a42b45f8cf22e353efe889eb3903cc34177ab44/mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3", size = 9496760 }, + { url = "https://files.pythonhosted.org/packages/4e/89/26230b46e27724bd54f76cd73a2759eaaf35292b32ba64f36c7c47836d4b/mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6", size = 11927489 }, + { url = "https://files.pythonhosted.org/packages/64/7d/156e721376951c449554942eedf4d53e9ca2a57e94bf0833ad2821d59bfa/mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f", size = 11990009 }, + { url = "https://files.pythonhosted.org/packages/27/ab/21230851e8137c9ef9a095cc8cb70d8ff8cac21014e4b249ac7a9eae7df9/mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc", size = 8816535 }, + { url = "https://files.pythonhosted.org/packages/1d/1b/9050b5c444ef82c3d59bdbf21f91b259cf20b2ac1df37d55bc6b91d609a1/mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828", size = 10447897 }, + { url = "https://files.pythonhosted.org/packages/da/00/ac2b58b321d85cac25be0dcd1bc2427dfc6cf403283fc205a0031576f14b/mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3", size = 9534091 }, + { url = "https://files.pythonhosted.org/packages/c4/10/26240f14e854a95af87d577b288d607ebe0ccb75cb37052f6386402f022d/mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816", size = 11970165 }, + { url = "https://files.pythonhosted.org/packages/b7/34/a3edaec8762181bfe97439c7e094f4c2f411ed9b79ac8f4d72156e88d5ce/mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c", size = 12040792 }, + { url = "https://files.pythonhosted.org/packages/d1/f3/0d0622d5a83859a992b01741a7b97949d6fb9efc9f05f20a09f0df10dc1e/mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f", size = 8831367 }, + { url = "https://files.pythonhosted.org/packages/3d/9a/e13addb8d652cb068f835ac2746d9d42f85b730092f581bb17e2059c28f1/mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4", size = 2451741 }, +] + +[[package]] +name = "mypy" +version = "1.14.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +dependencies = [ + { name = "mypy-extensions", marker = "python_full_version == '3.8.*'" }, + { name = "tomli", version = "2.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "typing-extensions", version = "4.12.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/eb/2c92d8ea1e684440f54fa49ac5d9a5f19967b7b472a281f419e69a8d228e/mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6", size = 3216051 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/7a/87ae2adb31d68402da6da1e5f30c07ea6063e9f09b5e7cfc9dfa44075e74/mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb", size = 11211002 }, + { url = "https://files.pythonhosted.org/packages/e1/23/eada4c38608b444618a132be0d199b280049ded278b24cbb9d3fc59658e4/mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0", size = 10358400 }, + { url = "https://files.pythonhosted.org/packages/43/c9/d6785c6f66241c62fd2992b05057f404237deaad1566545e9f144ced07f5/mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d", size = 12095172 }, + { url = "https://files.pythonhosted.org/packages/c3/62/daa7e787770c83c52ce2aaf1a111eae5893de9e004743f51bfcad9e487ec/mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b", size = 12828732 }, + { url = "https://files.pythonhosted.org/packages/1b/a2/5fb18318a3637f29f16f4e41340b795da14f4751ef4f51c99ff39ab62e52/mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427", size = 13012197 }, + { url = "https://files.pythonhosted.org/packages/28/99/e153ce39105d164b5f02c06c35c7ba958aaff50a2babba7d080988b03fe7/mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f", size = 9780836 }, + { url = "https://files.pythonhosted.org/packages/da/11/a9422850fd506edbcdc7f6090682ecceaf1f87b9dd847f9df79942da8506/mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c", size = 11120432 }, + { url = "https://files.pythonhosted.org/packages/b6/9e/47e450fd39078d9c02d620545b2cb37993a8a8bdf7db3652ace2f80521ca/mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1", size = 10279515 }, + { url = "https://files.pythonhosted.org/packages/01/b5/6c8d33bd0f851a7692a8bfe4ee75eb82b6983a3cf39e5e32a5d2a723f0c1/mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8", size = 12025791 }, + { url = "https://files.pythonhosted.org/packages/f0/4c/e10e2c46ea37cab5c471d0ddaaa9a434dc1d28650078ac1b56c2d7b9b2e4/mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f", size = 12749203 }, + { url = "https://files.pythonhosted.org/packages/88/55/beacb0c69beab2153a0f57671ec07861d27d735a0faff135a494cd4f5020/mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1", size = 12885900 }, + { url = "https://files.pythonhosted.org/packages/a2/75/8c93ff7f315c4d086a2dfcde02f713004357d70a163eddb6c56a6a5eff40/mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae", size = 9777869 }, + { url = "https://files.pythonhosted.org/packages/43/1b/b38c079609bb4627905b74fc6a49849835acf68547ac33d8ceb707de5f52/mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14", size = 11266668 }, + { url = "https://files.pythonhosted.org/packages/6b/75/2ed0d2964c1ffc9971c729f7a544e9cd34b2cdabbe2d11afd148d7838aa2/mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9", size = 10254060 }, + { url = "https://files.pythonhosted.org/packages/a1/5f/7b8051552d4da3c51bbe8fcafffd76a6823779101a2b198d80886cd8f08e/mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11", size = 11933167 }, + { url = "https://files.pythonhosted.org/packages/04/90/f53971d3ac39d8b68bbaab9a4c6c58c8caa4d5fd3d587d16f5927eeeabe1/mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e", size = 12864341 }, + { url = "https://files.pythonhosted.org/packages/03/d2/8bc0aeaaf2e88c977db41583559319f1821c069e943ada2701e86d0430b7/mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89", size = 12972991 }, + { url = "https://files.pythonhosted.org/packages/6f/17/07815114b903b49b0f2cf7499f1c130e5aa459411596668267535fe9243c/mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b", size = 9879016 }, + { url = "https://files.pythonhosted.org/packages/9e/15/bb6a686901f59222275ab228453de741185f9d54fecbaacec041679496c6/mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255", size = 11252097 }, + { url = "https://files.pythonhosted.org/packages/f8/b3/8b0f74dfd072c802b7fa368829defdf3ee1566ba74c32a2cb2403f68024c/mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34", size = 10239728 }, + { url = "https://files.pythonhosted.org/packages/c5/9b/4fd95ab20c52bb5b8c03cc49169be5905d931de17edfe4d9d2986800b52e/mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a", size = 11924965 }, + { url = "https://files.pythonhosted.org/packages/56/9d/4a236b9c57f5d8f08ed346914b3f091a62dd7e19336b2b2a0d85485f82ff/mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9", size = 12867660 }, + { url = "https://files.pythonhosted.org/packages/40/88/a61a5497e2f68d9027de2bb139c7bb9abaeb1be1584649fa9d807f80a338/mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd", size = 12969198 }, + { url = "https://files.pythonhosted.org/packages/54/da/3d6fc5d92d324701b0c23fb413c853892bfe0e1dbe06c9138037d459756b/mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107", size = 9885276 }, + { url = "https://files.pythonhosted.org/packages/39/02/1817328c1372be57c16148ce7d2bfcfa4a796bedaed897381b1aad9b267c/mypy-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7084fb8f1128c76cd9cf68fe5971b37072598e7c31b2f9f95586b65c741a9d31", size = 11143050 }, + { url = "https://files.pythonhosted.org/packages/b9/07/99db9a95ece5e58eee1dd87ca456a7e7b5ced6798fd78182c59c35a7587b/mypy-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f845a00b4f420f693f870eaee5f3e2692fa84cc8514496114649cfa8fd5e2c6", size = 10321087 }, + { url = "https://files.pythonhosted.org/packages/9a/eb/85ea6086227b84bce79b3baf7f465b4732e0785830726ce4a51528173b71/mypy-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44bf464499f0e3a2d14d58b54674dee25c031703b2ffc35064bd0df2e0fac319", size = 12066766 }, + { url = "https://files.pythonhosted.org/packages/4b/bb/f01bebf76811475d66359c259eabe40766d2f8ac8b8250d4e224bb6df379/mypy-1.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99f27732c0b7dc847adb21c9d47ce57eb48fa33a17bc6d7d5c5e9f9e7ae5bac", size = 12787111 }, + { url = "https://files.pythonhosted.org/packages/2f/c9/84837ff891edcb6dcc3c27d85ea52aab0c4a34740ff5f0ccc0eb87c56139/mypy-1.14.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bce23c7377b43602baa0bd22ea3265c49b9ff0b76eb315d6c34721af4cdf1d9b", size = 12974331 }, + { url = "https://files.pythonhosted.org/packages/84/5f/901e18464e6a13f8949b4909535be3fa7f823291b8ab4e4b36cfe57d6769/mypy-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:8edc07eeade7ebc771ff9cf6b211b9a7d93687ff892150cb5692e4f4272b0837", size = 9763210 }, + { url = "https://files.pythonhosted.org/packages/ca/1f/186d133ae2514633f8558e78cd658070ba686c0e9275c5a5c24a1e1f0d67/mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35", size = 11200493 }, + { url = "https://files.pythonhosted.org/packages/af/fc/4842485d034e38a4646cccd1369f6b1ccd7bc86989c52770d75d719a9941/mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc", size = 10357702 }, + { url = "https://files.pythonhosted.org/packages/b4/e6/457b83f2d701e23869cfec013a48a12638f75b9d37612a9ddf99072c1051/mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9", size = 12091104 }, + { url = "https://files.pythonhosted.org/packages/f1/bf/76a569158db678fee59f4fd30b8e7a0d75bcbaeef49edd882a0d63af6d66/mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb", size = 12830167 }, + { url = "https://files.pythonhosted.org/packages/43/bc/0bc6b694b3103de9fed61867f1c8bd33336b913d16831431e7cb48ef1c92/mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60", size = 13013834 }, + { url = "https://files.pythonhosted.org/packages/b0/79/5f5ec47849b6df1e6943d5fd8e6632fbfc04b4fd4acfa5a5a9535d11b4e2/mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c", size = 9781231 }, + { url = "https://files.pythonhosted.org/packages/a0/b5/32dd67b69a16d088e533962e5044e51004176a9952419de0370cdaead0f8/mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1", size = 2752905 }, +] + +[[package]] +name = "mypy" +version = "1.15.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "mypy-extensions", marker = "python_full_version >= '3.9'" }, + { name = "tomli", version = "2.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, + { name = "typing-extensions", version = "4.12.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/43/d5e49a86afa64bd3839ea0d5b9c7103487007d728e1293f52525d6d5486a/mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43", size = 3239717 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/f8/65a7ce8d0e09b6329ad0c8d40330d100ea343bd4dd04c4f8ae26462d0a17/mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13", size = 10738433 }, + { url = "https://files.pythonhosted.org/packages/b4/95/9c0ecb8eacfe048583706249439ff52105b3f552ea9c4024166c03224270/mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559", size = 9861472 }, + { url = "https://files.pythonhosted.org/packages/84/09/9ec95e982e282e20c0d5407bc65031dfd0f0f8ecc66b69538296e06fcbee/mypy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b", size = 11611424 }, + { url = "https://files.pythonhosted.org/packages/78/13/f7d14e55865036a1e6a0a69580c240f43bc1f37407fe9235c0d4ef25ffb0/mypy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3", size = 12365450 }, + { url = "https://files.pythonhosted.org/packages/48/e1/301a73852d40c241e915ac6d7bcd7fedd47d519246db2d7b86b9d7e7a0cb/mypy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b", size = 12551765 }, + { url = "https://files.pythonhosted.org/packages/77/ba/c37bc323ae5fe7f3f15a28e06ab012cd0b7552886118943e90b15af31195/mypy-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828", size = 9274701 }, + { url = "https://files.pythonhosted.org/packages/03/bc/f6339726c627bd7ca1ce0fa56c9ae2d0144604a319e0e339bdadafbbb599/mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f", size = 10662338 }, + { url = "https://files.pythonhosted.org/packages/e2/90/8dcf506ca1a09b0d17555cc00cd69aee402c203911410136cd716559efe7/mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5", size = 9787540 }, + { url = "https://files.pythonhosted.org/packages/05/05/a10f9479681e5da09ef2f9426f650d7b550d4bafbef683b69aad1ba87457/mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e", size = 11538051 }, + { url = "https://files.pythonhosted.org/packages/e9/9a/1f7d18b30edd57441a6411fcbc0c6869448d1a4bacbaee60656ac0fc29c8/mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c", size = 12286751 }, + { url = "https://files.pythonhosted.org/packages/72/af/19ff499b6f1dafcaf56f9881f7a965ac2f474f69f6f618b5175b044299f5/mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f", size = 12421783 }, + { url = "https://files.pythonhosted.org/packages/96/39/11b57431a1f686c1aed54bf794870efe0f6aeca11aca281a0bd87a5ad42c/mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f", size = 9265618 }, + { url = "https://files.pythonhosted.org/packages/98/3a/03c74331c5eb8bd025734e04c9840532226775c47a2c39b56a0c8d4f128d/mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd", size = 10793981 }, + { url = "https://files.pythonhosted.org/packages/f0/1a/41759b18f2cfd568848a37c89030aeb03534411eef981df621d8fad08a1d/mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f", size = 9749175 }, + { url = "https://files.pythonhosted.org/packages/12/7e/873481abf1ef112c582db832740f4c11b2bfa510e829d6da29b0ab8c3f9c/mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464", size = 11455675 }, + { url = "https://files.pythonhosted.org/packages/b3/d0/92ae4cde706923a2d3f2d6c39629134063ff64b9dedca9c1388363da072d/mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee", size = 12410020 }, + { url = "https://files.pythonhosted.org/packages/46/8b/df49974b337cce35f828ba6fda228152d6db45fed4c86ba56ffe442434fd/mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e", size = 12498582 }, + { url = "https://files.pythonhosted.org/packages/13/50/da5203fcf6c53044a0b699939f31075c45ae8a4cadf538a9069b165c1050/mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22", size = 9366614 }, + { url = "https://files.pythonhosted.org/packages/6a/9b/fd2e05d6ffff24d912f150b87db9e364fa8282045c875654ce7e32fffa66/mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445", size = 10788592 }, + { url = "https://files.pythonhosted.org/packages/74/37/b246d711c28a03ead1fd906bbc7106659aed7c089d55fe40dd58db812628/mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d", size = 9753611 }, + { url = "https://files.pythonhosted.org/packages/a6/ac/395808a92e10cfdac8003c3de9a2ab6dc7cde6c0d2a4df3df1b815ffd067/mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5", size = 11438443 }, + { url = "https://files.pythonhosted.org/packages/d2/8b/801aa06445d2de3895f59e476f38f3f8d610ef5d6908245f07d002676cbf/mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036", size = 12402541 }, + { url = "https://files.pythonhosted.org/packages/c7/67/5a4268782eb77344cc613a4cf23540928e41f018a9a1ec4c6882baf20ab8/mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357", size = 12494348 }, + { url = "https://files.pythonhosted.org/packages/83/3e/57bb447f7bbbfaabf1712d96f9df142624a386d98fb026a761532526057e/mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf", size = 9373648 }, + { url = "https://files.pythonhosted.org/packages/5a/fa/79cf41a55b682794abe71372151dbbf856e3008f6767057229e6649d294a/mypy-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e601a7fa172c2131bff456bb3ee08a88360760d0d2f8cbd7a75a65497e2df078", size = 10737129 }, + { url = "https://files.pythonhosted.org/packages/d3/33/dd8feb2597d648de29e3da0a8bf4e1afbda472964d2a4a0052203a6f3594/mypy-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:712e962a6357634fef20412699a3655c610110e01cdaa6180acec7fc9f8513ba", size = 9856335 }, + { url = "https://files.pythonhosted.org/packages/e4/b5/74508959c1b06b96674b364ffeb7ae5802646b32929b7701fc6b18447592/mypy-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95579473af29ab73a10bada2f9722856792a36ec5af5399b653aa28360290a5", size = 11611935 }, + { url = "https://files.pythonhosted.org/packages/6c/53/da61b9d9973efcd6507183fdad96606996191657fe79701b2c818714d573/mypy-1.15.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f8722560a14cde92fdb1e31597760dc35f9f5524cce17836c0d22841830fd5b", size = 12365827 }, + { url = "https://files.pythonhosted.org/packages/c1/72/965bd9ee89540c79a25778cc080c7e6ef40aa1eeac4d52cec7eae6eb5228/mypy-1.15.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fbb8da62dc352133d7d7ca90ed2fb0e9d42bb1a32724c287d3c76c58cbaa9c2", size = 12541924 }, + { url = "https://files.pythonhosted.org/packages/46/d0/f41645c2eb263e6c77ada7d76f894c580c9ddb20d77f0c24d34273a4dab2/mypy-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:d10d994b41fb3497719bbf866f227b3489048ea4bbbb5015357db306249f7980", size = 9271176 }, + { url = "https://files.pythonhosted.org/packages/09/4e/a7d65c7322c510de2c409ff3828b03354a7c43f5a8ed458a7a131b41c7b9/mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e", size = 2221777 }, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, +] + +[[package]] +name = "numpy" +version = "1.19.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.6.8' and python_full_version < '3.7'", + "python_full_version < '3.6.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/51/60/3f0fe5b7675a461d96b9d6729beecd3532565743278a9c3fe6dd09697fa7/numpy-1.19.5.zip", hash = "sha256:a76f502430dd98d7546e1ea2250a7360c065a5fdea52b2dffe8ae7180909b6f4", size = 7318340 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/9d/984f87a8d5b28b1d4afc042d8f436a76d6210fb582214f35a0ea1db3be66/numpy-1.19.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc6bd4fd593cb261332568485e20a0712883cf631f6f5e8e86a52caa8b2b50ff", size = 15599590 }, + { url = "https://files.pythonhosted.org/packages/9e/3f/693399e09bfc75737e0622803ba13d73e9a4da1d9fcc8614723372c5b00d/numpy-1.19.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:aeb9ed923be74e659984e321f609b9ba54a48354bfd168d21a2b072ed1e833ea", size = 11484645 }, + { url = "https://files.pythonhosted.org/packages/45/b2/6c7545bb7a38754d63048c7696804a0d947328125d81bf12beaa692c3ae3/numpy-1.19.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8b5e972b43c8fc27d56550b4120fe6257fdc15f9301914380b27f74856299fea", size = 13391355 }, + { url = "https://files.pythonhosted.org/packages/ab/81/244258ba61e03ccbfff2b9d3918b3665184380c5591050eb84ac3e88d0b0/numpy-1.19.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:43d4c81d5ffdff6bae58d66a3cd7f54a7acd9a0e7b18d97abb255defc09e3140", size = 12564437 }, + { url = "https://files.pythonhosted.org/packages/14/32/d3fa649ad7ec0b82737b92fefd3c4dd376b0bb23730715124569f38f3a08/numpy-1.19.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:a4646724fba402aa7504cd48b4b50e783296b5e10a524c7a6da62e4a8ac9698d", size = 14835495 }, + { url = "https://files.pythonhosted.org/packages/ab/f6/6cb12866511a564dde8208111b551f9469ba1c3963a63077afbb8a070a4a/numpy-1.19.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:2e55195bc1c6b705bfd8ad6f288b38b11b1af32f3c8289d6c50d47f950c12e76", size = 12376775 }, + { url = "https://files.pythonhosted.org/packages/b2/4f/5b2cdd441e92555377003b06c0c2c4ba0282a2bfe809f89a009e853fb26b/numpy-1.19.5-cp36-cp36m-win32.whl", hash = "sha256:39b70c19ec771805081578cc936bbe95336798b7edf4732ed102e7a43ec5c07a", size = 11008246 }, + { url = "https://files.pythonhosted.org/packages/ea/bc/da526221bc111857c7ef39c3af670bbcf5e69c247b0d22e51986f6d0c5c2/numpy-1.19.5-cp36-cp36m-win_amd64.whl", hash = "sha256:dbd18bcf4889b720ba13a27ec2f2aac1981bd41203b3a3b27ba7a33f88ae4827", size = 13227547 }, + { url = "https://files.pythonhosted.org/packages/39/ba/f3db2b5bb36fee2478b1cf47e7f9664b9c27241be80acd31c844404da297/numpy-1.19.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:603aa0706be710eea8884af807b1b3bc9fb2e49b9f4da439e76000f3b3c6ff0f", size = 15558406 }, + { url = "https://files.pythonhosted.org/packages/ca/8d/cfeab2119d6c94e1c6ad7753853773a142109a5f8f56e1d8c940886814bc/numpy-1.19.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:cae865b1cae1ec2663d8ea56ef6ff185bad091a5e33ebbadd98de2cfa3fa668f", size = 11483612 }, + { url = "https://files.pythonhosted.org/packages/b1/e1/8c4c5632adaffc18dba4e03e97458dc1cb00583811e6982fc620b9d88515/numpy-1.19.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:36674959eed6957e61f11c912f71e78857a8d0604171dfd9ce9ad5cbf41c511c", size = 13385808 }, + { url = "https://files.pythonhosted.org/packages/0b/2c/b20e49628109f1f6aa56c5a283dc6001276b0b8853712dc66f2243abddfe/numpy-1.19.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:06fab248a088e439402141ea04f0fffb203723148f6ee791e9c75b3e9e82f080", size = 12570233 }, + { url = "https://files.pythonhosted.org/packages/08/d6/a6aaa29fea945bc6c61d11f6e0697b325ff7446de5ffd62c2fa02f627048/numpy-1.19.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6149a185cece5ee78d1d196938b2a8f9d09f5a5ebfbba66969302a778d5ddd1d", size = 14835904 }, + { url = "https://files.pythonhosted.org/packages/fe/93/b0b203c693d621d6ee9577fc650bab49434c6d23c809dc4eb49db6a339af/numpy-1.19.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:50a4a0ad0111cc1b71fa32dedd05fa239f7fb5a43a40663269bb5dc7877cfd28", size = 12383550 }, + { url = "https://files.pythonhosted.org/packages/c4/4c/2be94fa407a0df1ac54b6ee2e42d002b1ea957748fda1b7775e224db12ce/numpy-1.19.5-cp37-cp37m-win32.whl", hash = "sha256:d051ec1c64b85ecc69531e1137bb9751c6830772ee5c1c426dbcfe98ef5788d7", size = 11008115 }, + { url = "https://files.pythonhosted.org/packages/ff/18/60ac053857fb924b0324c81200b59c00317ebaa3c14b7478266b50ffed19/numpy-1.19.5-cp37-cp37m-win_amd64.whl", hash = "sha256:a12ff4c8ddfee61f90a1633a4c4afd3f7bcb32b11c52026c92a12e1325922d0d", size = 13228065 }, + { url = "https://files.pythonhosted.org/packages/a6/b7/c0594c698c7149bfe738724ab9ab3722dca3a4a43823468fe9481abe4016/numpy-1.19.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cf2402002d3d9f91c8b01e66fbb436a4ed01c6498fffed0e4c7566da1d40ee1e", size = 15585489 }, + { url = "https://files.pythonhosted.org/packages/57/c6/bb70999019bc1d11ab27d309eba2f4b7c9c241ca64912ebaa8ec82593df6/numpy-1.19.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1ded4fce9cfaaf24e7a0ab51b7a87be9038ea1ace7f34b841fe3b6894c721d1c", size = 11475576 }, + { url = "https://files.pythonhosted.org/packages/21/da/4a59e01f8fff4281a068e90868edd62253c1431a1b7315fe6789f8a0d9c0/numpy-1.19.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:012426a41bc9ab63bb158635aecccc7610e3eff5d31d1eb43bc099debc979d94", size = 13370326 }, + { url = "https://files.pythonhosted.org/packages/47/73/b9435c96228003915c9128c0b49af96aed359a03aa8a86f562add743a99f/numpy-1.19.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:759e4095edc3c1b3ac031f34d9459fa781777a93ccc633a472a5468587a190ff", size = 12610298 }, + { url = "https://files.pythonhosted.org/packages/66/d7/3b133b17e185f14137bc8afe7a41daf1f31556900f10238312a5ae9c7345/numpy-1.19.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:a9d17f2be3b427fbb2bce61e596cf555d6f8a56c222bd2ca148baeeb5e5c783c", size = 14887086 }, + { url = "https://files.pythonhosted.org/packages/97/b6/9ad5980f6657b2d5a7d1d1175f1f48881d5d73c5140c48dbbf08132b5938/numpy-1.19.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:99abf4f353c3d1a0c7a5f27699482c987cf663b1eac20db59b8c7b061eabd7fc", size = 12415699 }, + { url = "https://files.pythonhosted.org/packages/fd/25/273019ca5ffcabd8a8fff7dd44460e4f9a2ccbc59a647ce511cb721ff757/numpy-1.19.5-cp38-cp38-win32.whl", hash = "sha256:384ec0463d1c2671170901994aeb6dce126de0a95ccc3976c43b0038a37329c2", size = 11034907 }, + { url = "https://files.pythonhosted.org/packages/5a/13/25a83b9aae5fe9460b1997f5ba48814783d7f460bbbd8cadd96e1481ddf0/numpy-1.19.5-cp38-cp38-win_amd64.whl", hash = "sha256:811daee36a58dc79cf3d8bdd4a490e4277d0e4b7d103a001a4e73ddb48e7e6aa", size = 13269033 }, + { url = "https://files.pythonhosted.org/packages/e1/c5/65b2f257a154c7fabc1895b435e7863a1f0bb1769d3c28f1500976e090ee/numpy-1.19.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c843b3f50d1ab7361ca4f0b3639bf691569493a56808a0b0c54a051d260b7dbd", size = 15627844 }, + { url = "https://files.pythonhosted.org/packages/a7/67/06b4c4af90b22ce97270eb75927e42b31b814be44a28e12ec2d9ff8995e7/numpy-1.19.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d6631f2e867676b13026e2846180e2c13c1e11289d67da08d71cacb2cd93d4aa", size = 11495111 }, + { url = "https://files.pythonhosted.org/packages/89/62/189b502767f70a310a010c397fcd52169fdf75f6876e52beb54e98f1ce93/numpy-1.19.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7fb43004bce0ca31d8f13a6eb5e943fa73371381e53f7074ed21a4cb786c32f8", size = 13392564 }, + { url = "https://files.pythonhosted.org/packages/60/f1/9afcfd695ffa3d8295aa2180ae9499f3ab03ddde7ac559c320945f7c4725/numpy-1.19.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2ea52bd92ab9f768cc64a4c3ef8f4b2580a17af0a5436f6126b08efbd1838371", size = 12602683 }, + { url = "https://files.pythonhosted.org/packages/f7/50/02c0fb5d913901ad643934713a31567df9b5065c28efc9672b707f80dfb1/numpy-1.19.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:400580cbd3cff6ffa6293df2278c75aef2d58d8d93d3c5614cd67981dae68ceb", size = 14886464 }, + { url = "https://files.pythonhosted.org/packages/91/11/059ef2ef98f9eea49ece6d6046bc537c3050c575108a51a624a179c8e7e3/numpy-1.19.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:df609c82f18c5b9f6cb97271f03315ff0dbe481a2a02e56aeb1b1a985ce38e60", size = 12411067 }, + { url = "https://files.pythonhosted.org/packages/77/fb/f0b570d098e23a899c8e64b3ceaa51bcba617789a45d8b558ad255aa867a/numpy-1.19.5-cp39-cp39-win32.whl", hash = "sha256:ab83f24d5c52d60dbc8cd0528759532736b56db58adaa7b5f1f76ad551416a1e", size = 11029423 }, + { url = "https://files.pythonhosted.org/packages/bc/40/d6f7ba9ce5406b578e538325828ea43849a3dfd8db63d1147a257d19c8d1/numpy-1.19.5-cp39-cp39-win_amd64.whl", hash = "sha256:0eef32ca3132a48e43f6a0f5a82cb508f22ce5a3d6f67a8329c81c8e226d3f6e", size = 13267873 }, + { url = "https://files.pythonhosted.org/packages/3c/1b/c8bb1bfe13a697f76a2c970df8fc7f287352ebafa9708a5b3f52692efd43/numpy-1.19.5-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:a0d53e51a6cb6f0d9082decb7a4cb6dfb33055308c4c44f53103c073f649af73", size = 14259809 }, +] + +[[package]] +name = "numpy" +version = "1.21.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.7.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/45/b7/de7b8e67f2232c26af57c205aaad29fe17754f793404f59c8a730c7a191a/numpy-1.21.6.zip", hash = "sha256:ecb55251139706669fdec2ff073c98ef8e9a84473e51e716211b41aa0f18e656", size = 10274544 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/c6/05ae3c7f75b596e1bb3d78131c331eada9376a03d1af9801bd40e4675023/numpy-1.21.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8737609c3bbdd48e380d463134a35ffad3b22dc56295eff6f79fd85bd0eeeb25", size = 27203123 }, + { url = "https://files.pythonhosted.org/packages/4a/72/a3379f83172f1431d7949138373e3a24beed68184c9362dab1b4d465be26/numpy-1.21.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fdffbfb6832cd0b300995a2b08b8f6fa9f6e856d562800fea9182316d99c4e8e", size = 16965603 }, + { url = "https://files.pythonhosted.org/packages/26/e7/4a6f579af8186372b03e8480e47df309520d91cfead8759b64dd5ac62688/numpy-1.21.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3820724272f9913b597ccd13a467cc492a0da6b05df26ea09e78b171a0bb9da6", size = 12364595 }, + { url = "https://files.pythonhosted.org/packages/57/ba/d8cbdfd507b541bb247beff24d9d7304ac8ffc379cf585701187d45d4512/numpy-1.21.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f17e562de9edf691a42ddb1eb4a5541c20dd3f9e65b09ded2beb0799c0cf29bb", size = 13019278 }, + { url = "https://files.pythonhosted.org/packages/6f/7b/036000a55680e6c7eb81502b0aa27ce0ed65d4d8805613909967d9f8baf6/numpy-1.21.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f30427731561ce75d7048ac254dbe47a2ba576229250fb60f0fb74db96501a1", size = 15906004 }, + { url = "https://files.pythonhosted.org/packages/b0/77/ff8bbe56ff6cbbdbdb8a641c67cee61e29b2e8bfbb18732c2e1d2961fe4d/numpy-1.21.6-cp310-cp310-win32.whl", hash = "sha256:d4bf4d43077db55589ffc9009c0ba0a94fa4908b9586d6ccce2e0b164c86303c", size = 11706182 }, + { url = "https://files.pythonhosted.org/packages/ec/03/93702ca9c4bd61791e46c80ff1f24943febb2317484cf7e8207688bbbd95/numpy-1.21.6-cp310-cp310-win_amd64.whl", hash = "sha256:d136337ae3cc69aa5e447e78d8e1514be8c3ec9b54264e680cf0b4bd9011574f", size = 14008913 }, + { url = "https://files.pythonhosted.org/packages/32/dd/43d8b2b2ebf424f6555271a4c9f5b50dc3cc0aafa66c72b4d36863f71358/numpy-1.21.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6aaf96c7f8cebc220cdfc03f1d5a31952f027dda050e5a703a0d1c396075e3e7", size = 16894122 }, + { url = "https://files.pythonhosted.org/packages/06/78/b184f13f5461812a17a90b380d70a93fa3532460f0af9d72b0d93d8bc4ff/numpy-1.21.6-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:67c261d6c0a9981820c3a149d255a76918278a6b03b6a036800359aba1256d46", size = 13670697 }, + { url = "https://files.pythonhosted.org/packages/6d/ad/ff3b21ebfe79a4d25b4a4f8e5cf9fd44a204adb6b33c09010f566f51027a/numpy-1.21.6-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a6be4cb0ef3b8c9250c19cc122267263093eee7edd4e3fa75395dfda8c17a8e2", size = 15702369 }, + { url = "https://files.pythonhosted.org/packages/b7/0d/86662f93102e42545cdf031da4fddf0ace9030ec67478932a628afc5973b/numpy-1.21.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c4068a8c44014b2d55f3c3f574c376b2494ca9cc73d2f1bd692382b6dffe3db", size = 12974170 }, + { url = "https://files.pythonhosted.org/packages/cd/eb/f6f3258e7b0e0cc5c327778312bf4ee4978c8514aa28e97119ee206f6e60/numpy-1.21.6-cp37-cp37m-win32.whl", hash = "sha256:7c7e5fa88d9ff656e067876e4736379cc962d185d5cd808014a8a928d529ef4e", size = 11680505 }, + { url = "https://files.pythonhosted.org/packages/97/9f/da37cc4a188a1d5d203d65ab28d6504e17594b5342e0c1dc5610ee6f4535/numpy-1.21.6-cp37-cp37m-win_amd64.whl", hash = "sha256:bcb238c9c96c00d3085b264e5c1a1207672577b93fa666c3b14a45240b14123a", size = 13969236 }, + { url = "https://files.pythonhosted.org/packages/b5/e2/b2df1f664d644e690b40179fc0a07c163c6decf986c7adee8a85a094e8ce/numpy-1.21.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:82691fda7c3f77c90e62da69ae60b5ac08e87e775b09813559f8901a88266552", size = 27127297 }, + { url = "https://files.pythonhosted.org/packages/5b/d4/be63d2bed7d10f443dee42469623326b6bc51c9e5cd096ebb7227bca456f/numpy-1.21.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:643843bcc1c50526b3a71cd2ee561cf0d8773f062c8cbaf9ffac9fdf573f83ab", size = 16926225 }, + { url = "https://files.pythonhosted.org/packages/0d/21/036363516c06737135ee58741e9c0af4899348ce3c5f5e04379240edd090/numpy-1.21.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:357768c2e4451ac241465157a3e929b265dfac85d9214074985b1786244f2ef3", size = 12329630 }, + { url = "https://files.pythonhosted.org/packages/6a/52/a1dcf14b8e81d49c14112663290ee2ed545bd04988170138284a613bd926/numpy-1.21.6-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9f411b2c3f3d76bba0865b35a425157c5dcf54937f82bbeb3d3c180789dd66a6", size = 13719672 }, + { url = "https://files.pythonhosted.org/packages/d5/43/e88bb1fb7d040ae8e0e06e749341b13f57701aab11fe9d71c99af6202c5c/numpy-1.21.6-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4aa48afdce4660b0076a00d80afa54e8a97cd49f457d68a4342d188a09451c1a", size = 15749422 }, + { url = "https://files.pythonhosted.org/packages/86/c7/3f68d0a8dcc9458879c614707e6ffaf64a108664cfbba9702d3ba7ca4c82/numpy-1.21.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a96eef20f639e6a97d23e57dd0c1b1069a7b4fd7027482a4c5c451cd7732f4", size = 13026205 }, + { url = "https://files.pythonhosted.org/packages/6f/47/453023bd298f8b0be092d8a8bdd4b21f87a8c639ecb724a94cd75e23d216/numpy-1.21.6-cp38-cp38-win32.whl", hash = "sha256:5c3c8def4230e1b959671eb959083661b4a0d2e9af93ee339c7dada6759a9470", size = 11707551 }, + { url = "https://files.pythonhosted.org/packages/48/5f/db4550e1c68206814a577ebd92c0dd082f3628fd7fc96725d44a521b0c92/numpy-1.21.6-cp38-cp38-win_amd64.whl", hash = "sha256:bf2ec4b75d0e9356edea834d1de42b31fe11f726a81dfb2c2112bc1eaa508fcf", size = 14009785 }, + { url = "https://files.pythonhosted.org/packages/83/eb/a6a0d7fc8e718776c5c710692ea027607104710cba813c4b869182179334/numpy-1.21.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4391bd07606be175aafd267ef9bea87cf1b8210c787666ce82073b05f202add1", size = 27202394 }, + { url = "https://files.pythonhosted.org/packages/4c/62/07402945bd5d5cf515a5f0cbc7263abf02ec0ddf3b19fbdc4af7537cd4d0/numpy-1.21.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:67f21981ba2f9d7ba9ade60c9e8cbaa8cf8e9ae51673934480e45cf55e953673", size = 16965163 }, + { url = "https://files.pythonhosted.org/packages/44/56/041e886b4a8da813b7ec297c270fb3582d2ae8b7f33e106eb5c7a5e9184c/numpy-1.21.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee5ec40fdd06d62fe5d4084bef4fd50fd4bb6bfd2bf519365f569dc470163ab0", size = 12364236 }, + { url = "https://files.pythonhosted.org/packages/61/f4/f01a8989e53a437ad660ab86c91514bec3d5067393e4a844b259f5a103de/numpy-1.21.6-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1dbe1c91269f880e364526649a52eff93ac30035507ae980d2fed33aaee633ac", size = 13721583 }, + { url = "https://files.pythonhosted.org/packages/e7/f2/0bdcf2c40ef144cbbc9e0947eea831a145a98b0e5f8438fc09cf7fda0b35/numpy-1.21.6-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d9caa9d5e682102453d96a0ee10c7241b72859b01a941a397fd965f23b3e016b", size = 15734025 }, + { url = "https://files.pythonhosted.org/packages/76/7f/830cf169eede1b855538f962e3a70c31755db6423652695b813ed04ff54e/numpy-1.21.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58459d3bad03343ac4b1b42ed14d571b8743dc80ccbf27444f266729df1d6f5b", size = 13020515 }, + { url = "https://files.pythonhosted.org/packages/1b/b5/7178d5a22427a9195ac69d6ec150415734f7a7a19d1142f82b89ead1dac4/numpy-1.21.6-cp39-cp39-win32.whl", hash = "sha256:7f5ae4f304257569ef3b948810816bc87c9146e8c446053539947eedeaa32786", size = 11706846 }, + { url = "https://files.pythonhosted.org/packages/4d/04/bcd62448f2e772bc90a73ba21bacaa19817ae9905ae639969462862bd071/numpy-1.21.6-cp39-cp39-win_amd64.whl", hash = "sha256:e31f0bb5928b793169b87e3d1e070f2342b22d5245c755e2b81caa29756246c3", size = 14008965 }, + { url = "https://files.pythonhosted.org/packages/2e/5a/6f3e280a10de48395053a559bfcb3b2221b74b57d062c1d6307fc965f549/numpy-1.21.6-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dd1c8f6bd65d07d3810b90d02eba7997e32abbdf1277a481d698969e921a3be0", size = 15159527 }, +] + +[[package]] +name = "numpy" +version = "1.24.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/a4/9b/027bec52c633f6556dba6b722d9a0befb40498b9ceddd29cbe67a45a127c/numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463", size = 10911229 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/80/6cdfb3e275d95155a34659163b83c09e3a3ff9f1456880bec6cc63d71083/numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64", size = 19789140 }, + { url = "https://files.pythonhosted.org/packages/64/5f/3f01d753e2175cfade1013eea08db99ba1ee4bdb147ebcf3623b75d12aa7/numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1", size = 13854297 }, + { url = "https://files.pythonhosted.org/packages/5a/b3/2f9c21d799fa07053ffa151faccdceeb69beec5a010576b8991f614021f7/numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4", size = 13995611 }, + { url = "https://files.pythonhosted.org/packages/10/be/ae5bf4737cb79ba437879915791f6f26d92583c738d7d960ad94e5c36adf/numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6", size = 17282357 }, + { url = "https://files.pythonhosted.org/packages/c0/64/908c1087be6285f40e4b3e79454552a701664a079321cff519d8c7051d06/numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc", size = 12429222 }, + { url = "https://files.pythonhosted.org/packages/22/55/3d5a7c1142e0d9329ad27cece17933b0e2ab4e54ddc5c1861fbfeb3f7693/numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e", size = 14841514 }, + { url = "https://files.pythonhosted.org/packages/a9/cc/5ed2280a27e5dab12994c884f1f4d8c3bd4d885d02ae9e52a9d213a6a5e2/numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810", size = 19775508 }, + { url = "https://files.pythonhosted.org/packages/c0/bc/77635c657a3668cf652806210b8662e1aff84b818a55ba88257abf6637a8/numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254", size = 13840033 }, + { url = "https://files.pythonhosted.org/packages/a7/4c/96cdaa34f54c05e97c1c50f39f98d608f96f0677a6589e64e53104e22904/numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7", size = 13991951 }, + { url = "https://files.pythonhosted.org/packages/22/97/dfb1a31bb46686f09e68ea6ac5c63fdee0d22d7b23b8f3f7ea07712869ef/numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5", size = 17278923 }, + { url = "https://files.pythonhosted.org/packages/35/e2/76a11e54139654a324d107da1d98f99e7aa2a7ef97cfd7c631fba7dbde71/numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d", size = 12422446 }, + { url = "https://files.pythonhosted.org/packages/d8/ec/ebef2f7d7c28503f958f0f8b992e7ce606fb74f9e891199329d5f5f87404/numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694", size = 14834466 }, + { url = "https://files.pythonhosted.org/packages/11/10/943cfb579f1a02909ff96464c69893b1d25be3731b5d3652c2e0cf1281ea/numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61", size = 19780722 }, + { url = "https://files.pythonhosted.org/packages/a7/ae/f53b7b265fdc701e663fbb322a8e9d4b14d9cb7b2385f45ddfabfc4327e4/numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f", size = 13843102 }, + { url = "https://files.pythonhosted.org/packages/25/6f/2586a50ad72e8dbb1d8381f837008a0321a3516dfd7cb57fc8cf7e4bb06b/numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e", size = 14039616 }, + { url = "https://files.pythonhosted.org/packages/98/5d/5738903efe0ecb73e51eb44feafba32bdba2081263d40c5043568ff60faf/numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc", size = 17316263 }, + { url = "https://files.pythonhosted.org/packages/d1/57/8d328f0b91c733aa9aa7ee540dbc49b58796c862b4fbcb1146c701e888da/numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2", size = 12455660 }, + { url = "https://files.pythonhosted.org/packages/69/65/0d47953afa0ad569d12de5f65d964321c208492064c38fe3b0b9744f8d44/numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706", size = 14868112 }, + { url = "https://files.pythonhosted.org/packages/9a/cd/d5b0402b801c8a8b56b04c1e85c6165efab298d2f0ab741c2406516ede3a/numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400", size = 19816549 }, + { url = "https://files.pythonhosted.org/packages/14/27/638aaa446f39113a3ed38b37a66243e21b38110d021bfcb940c383e120f2/numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f", size = 13879950 }, + { url = "https://files.pythonhosted.org/packages/8f/27/91894916e50627476cff1a4e4363ab6179d01077d71b9afed41d9e1f18bf/numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9", size = 14030228 }, + { url = "https://files.pythonhosted.org/packages/7a/7c/d7b2a0417af6428440c0ad7cb9799073e507b1a465f827d058b826236964/numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d", size = 17311170 }, + { url = "https://files.pythonhosted.org/packages/18/9d/e02ace5d7dfccee796c37b995c63322674daf88ae2f4a4724c5dd0afcc91/numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835", size = 12454918 }, + { url = "https://files.pythonhosted.org/packages/63/38/6cc19d6b8bfa1d1a459daf2b3fe325453153ca7019976274b6f33d8b5663/numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8", size = 14867441 }, + { url = "https://files.pythonhosted.org/packages/a4/fd/8dff40e25e937c94257455c237b9b6bf5a30d42dd1cc11555533be099492/numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef", size = 19156590 }, + { url = "https://files.pythonhosted.org/packages/42/e7/4bf953c6e05df90c6d351af69966384fed8e988d0e8c54dad7103b59f3ba/numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a", size = 16705744 }, + { url = "https://files.pythonhosted.org/packages/fc/dd/9106005eb477d022b60b3817ed5937a43dad8fd1f20b0610ea8a32fcb407/numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2", size = 14734290 }, +] + +[[package]] +name = "numpy" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/75/10dd1f8116a8b796cb2c737b674e02d02e80454bda953fa7e65d8c12b016/numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78", size = 18902015 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/91/3495b3237510f79f5d81f2508f9f13fea78ebfdf07538fc7444badda173d/numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece", size = 21165245 }, + { url = "https://files.pythonhosted.org/packages/05/33/26178c7d437a87082d11019292dce6d3fe6f0e9026b7b2309cbf3e489b1d/numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04", size = 13738540 }, + { url = "https://files.pythonhosted.org/packages/ec/31/cc46e13bf07644efc7a4bf68df2df5fb2a1a88d0cd0da9ddc84dc0033e51/numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66", size = 5300623 }, + { url = "https://files.pythonhosted.org/packages/6e/16/7bfcebf27bb4f9d7ec67332ffebee4d1bf085c84246552d52dbb548600e7/numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b", size = 6901774 }, + { url = "https://files.pythonhosted.org/packages/f9/a3/561c531c0e8bf082c5bef509d00d56f82e0ea7e1e3e3a7fc8fa78742a6e5/numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd", size = 13907081 }, + { url = "https://files.pythonhosted.org/packages/fa/66/f7177ab331876200ac7563a580140643d1179c8b4b6a6b0fc9838de2a9b8/numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318", size = 19523451 }, + { url = "https://files.pythonhosted.org/packages/25/7f/0b209498009ad6453e4efc2c65bcdf0ae08a182b2b7877d7ab38a92dc542/numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8", size = 19927572 }, + { url = "https://files.pythonhosted.org/packages/3e/df/2619393b1e1b565cd2d4c4403bdd979621e2c4dea1f8532754b2598ed63b/numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326", size = 14400722 }, + { url = "https://files.pythonhosted.org/packages/22/ad/77e921b9f256d5da36424ffb711ae79ca3f451ff8489eeca544d0701d74a/numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97", size = 6472170 }, + { url = "https://files.pythonhosted.org/packages/10/05/3442317535028bc29cf0c0dd4c191a4481e8376e9f0db6bcf29703cadae6/numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131", size = 15905558 }, + { url = "https://files.pythonhosted.org/packages/8b/cf/034500fb83041aa0286e0fb16e7c76e5c8b67c0711bb6e9e9737a717d5fe/numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448", size = 21169137 }, + { url = "https://files.pythonhosted.org/packages/4a/d9/32de45561811a4b87fbdee23b5797394e3d1504b4a7cf40c10199848893e/numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195", size = 13703552 }, + { url = "https://files.pythonhosted.org/packages/c1/ca/2f384720020c7b244d22508cb7ab23d95f179fcfff33c31a6eeba8d6c512/numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57", size = 5298957 }, + { url = "https://files.pythonhosted.org/packages/0e/78/a3e4f9fb6aa4e6fdca0c5428e8ba039408514388cf62d89651aade838269/numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a", size = 6905573 }, + { url = "https://files.pythonhosted.org/packages/a0/72/cfc3a1beb2caf4efc9d0b38a15fe34025230da27e1c08cc2eb9bfb1c7231/numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669", size = 13914330 }, + { url = "https://files.pythonhosted.org/packages/ba/a8/c17acf65a931ce551fee11b72e8de63bf7e8a6f0e21add4c937c83563538/numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951", size = 19534895 }, + { url = "https://files.pythonhosted.org/packages/ba/86/8767f3d54f6ae0165749f84648da9dcc8cd78ab65d415494962c86fac80f/numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9", size = 19937253 }, + { url = "https://files.pythonhosted.org/packages/df/87/f76450e6e1c14e5bb1eae6836478b1028e096fd02e85c1c37674606ab752/numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15", size = 14414074 }, + { url = "https://files.pythonhosted.org/packages/5c/ca/0f0f328e1e59f73754f06e1adfb909de43726d4f24c6a3f8805f34f2b0fa/numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4", size = 6470640 }, + { url = "https://files.pythonhosted.org/packages/eb/57/3a3f14d3a759dcf9bf6e9eda905794726b758819df4663f217d658a58695/numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc", size = 15910230 }, + { url = "https://files.pythonhosted.org/packages/45/40/2e117be60ec50d98fa08c2f8c48e09b3edea93cfcabd5a9ff6925d54b1c2/numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b", size = 20895803 }, + { url = "https://files.pythonhosted.org/packages/46/92/1b8b8dee833f53cef3e0a3f69b2374467789e0bb7399689582314df02651/numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e", size = 13471835 }, + { url = "https://files.pythonhosted.org/packages/7f/19/e2793bde475f1edaea6945be141aef6c8b4c669b90c90a300a8954d08f0a/numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c", size = 5038499 }, + { url = "https://files.pythonhosted.org/packages/e3/ff/ddf6dac2ff0dd50a7327bcdba45cb0264d0e96bb44d33324853f781a8f3c/numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c", size = 6633497 }, + { url = "https://files.pythonhosted.org/packages/72/21/67f36eac8e2d2cd652a2e69595a54128297cdcb1ff3931cfc87838874bd4/numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692", size = 13621158 }, + { url = "https://files.pythonhosted.org/packages/39/68/e9f1126d757653496dbc096cb429014347a36b228f5a991dae2c6b6cfd40/numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a", size = 19236173 }, + { url = "https://files.pythonhosted.org/packages/d1/e9/1f5333281e4ebf483ba1c888b1d61ba7e78d7e910fdd8e6499667041cc35/numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c", size = 19634174 }, + { url = "https://files.pythonhosted.org/packages/71/af/a469674070c8d8408384e3012e064299f7a2de540738a8e414dcfd639996/numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded", size = 14099701 }, + { url = "https://files.pythonhosted.org/packages/d0/3d/08ea9f239d0e0e939b6ca52ad403c84a2bce1bde301a8eb4888c1c1543f1/numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5", size = 6174313 }, + { url = "https://files.pythonhosted.org/packages/b2/b5/4ac39baebf1fdb2e72585c8352c56d063b6126be9fc95bd2bb5ef5770c20/numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a", size = 15606179 }, + { url = "https://files.pythonhosted.org/packages/43/c1/41c8f6df3162b0c6ffd4437d729115704bd43363de0090c7f913cfbc2d89/numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c", size = 21169942 }, + { url = "https://files.pythonhosted.org/packages/39/bc/fd298f308dcd232b56a4031fd6ddf11c43f9917fbc937e53762f7b5a3bb1/numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd", size = 13711512 }, + { url = "https://files.pythonhosted.org/packages/96/ff/06d1aa3eeb1c614eda245c1ba4fb88c483bee6520d361641331872ac4b82/numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b", size = 5306976 }, + { url = "https://files.pythonhosted.org/packages/2d/98/121996dcfb10a6087a05e54453e28e58694a7db62c5a5a29cee14c6e047b/numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729", size = 6906494 }, + { url = "https://files.pythonhosted.org/packages/15/31/9dffc70da6b9bbf7968f6551967fc21156207366272c2a40b4ed6008dc9b/numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1", size = 13912596 }, + { url = "https://files.pythonhosted.org/packages/b9/14/78635daab4b07c0930c919d451b8bf8c164774e6a3413aed04a6d95758ce/numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd", size = 19526099 }, + { url = "https://files.pythonhosted.org/packages/26/4c/0eeca4614003077f68bfe7aac8b7496f04221865b3a5e7cb230c9d055afd/numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d", size = 19932823 }, + { url = "https://files.pythonhosted.org/packages/f1/46/ea25b98b13dccaebddf1a803f8c748680d972e00507cd9bc6dcdb5aa2ac1/numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d", size = 14404424 }, + { url = "https://files.pythonhosted.org/packages/c8/a6/177dd88d95ecf07e722d21008b1b40e681a929eb9e329684d449c36586b2/numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa", size = 6476809 }, + { url = "https://files.pythonhosted.org/packages/ea/2b/7fc9f4e7ae5b507c1a3a21f0f15ed03e794c1242ea8a242ac158beb56034/numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73", size = 15911314 }, + { url = "https://files.pythonhosted.org/packages/8f/3b/df5a870ac6a3be3a86856ce195ef42eec7ae50d2a202be1f5a4b3b340e14/numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8", size = 21025288 }, + { url = "https://files.pythonhosted.org/packages/2c/97/51af92f18d6f6f2d9ad8b482a99fb74e142d71372da5d834b3a2747a446e/numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4", size = 6762793 }, + { url = "https://files.pythonhosted.org/packages/12/46/de1fbd0c1b5ccaa7f9a005b66761533e2f6a3e560096682683a223631fe9/numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c", size = 19334885 }, + { url = "https://files.pythonhosted.org/packages/cc/dc/d330a6faefd92b446ec0f0dfea4c3207bb1fef3c4771d19cf4543efd2c78/numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385", size = 15828784 }, +] + +[[package]] +name = "numpy" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/90/8956572f5c4ae52201fdec7ba2044b2c882832dcec7d5d0922c9e9acf2de/numpy-2.2.3.tar.gz", hash = "sha256:dbdc15f0c81611925f382dfa97b3bd0bc2c1ce19d4fe50482cb0ddc12ba30020", size = 20262700 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/e1/1816d5d527fa870b260a1c2c5904d060caad7515637bd54f495a5ce13ccd/numpy-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cbc6472e01952d3d1b2772b720428f8b90e2deea8344e854df22b0618e9cce71", size = 21232911 }, + { url = "https://files.pythonhosted.org/packages/29/46/9f25dc19b359f10c0e52b6bac25d3181eb1f4b4d04c9846a32cf5ea52762/numpy-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdfe0c22692a30cd830c0755746473ae66c4a8f2e7bd508b35fb3b6a0813d787", size = 14371955 }, + { url = "https://files.pythonhosted.org/packages/72/d7/de941296e6b09a5c81d3664ad912f1496a0ecdd2f403318e5e35604ff70f/numpy-2.2.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:e37242f5324ffd9f7ba5acf96d774f9276aa62a966c0bad8dae692deebec7716", size = 5410476 }, + { url = "https://files.pythonhosted.org/packages/36/ce/55f685995110f8a268fdca0f198c9a84fa87b39512830965cc1087af6391/numpy-2.2.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:95172a21038c9b423e68be78fd0be6e1b97674cde269b76fe269a5dfa6fadf0b", size = 6945730 }, + { url = "https://files.pythonhosted.org/packages/4f/84/abdb9f6e22576d89c259401c3234d4755b322539491bbcffadc8bcb120d3/numpy-2.2.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5b47c440210c5d1d67e1cf434124e0b5c395eee1f5806fdd89b553ed1acd0a3", size = 14350752 }, + { url = "https://files.pythonhosted.org/packages/e9/88/3870cfa9bef4dffb3a326507f430e6007eeac258ebeef6b76fc542aef66d/numpy-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0391ea3622f5c51a2e29708877d56e3d276827ac5447d7f45e9bc4ade8923c52", size = 16399386 }, + { url = "https://files.pythonhosted.org/packages/02/10/3f629682dd0b457525c131945329c4e81e2dadeb11256e6ce4c9a1a6fb41/numpy-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f6b3dfc7661f8842babd8ea07e9897fe3d9b69a1d7e5fbb743e4160f9387833b", size = 15561826 }, + { url = "https://files.pythonhosted.org/packages/da/18/fd35673ba9751eba449d4ce5d24d94e3b612cdbfba79348da71488c0b7ac/numpy-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1ad78ce7f18ce4e7df1b2ea4019b5817a2f6a8a16e34ff2775f646adce0a5027", size = 18188593 }, + { url = "https://files.pythonhosted.org/packages/ce/4c/c0f897b580ea59484b4cc96a441fea50333b26675a60a1421bc912268b5f/numpy-2.2.3-cp310-cp310-win32.whl", hash = "sha256:5ebeb7ef54a7be11044c33a17b2624abe4307a75893c001a4800857956b41094", size = 6590421 }, + { url = "https://files.pythonhosted.org/packages/e5/5b/aaabbfc7060c5c8f0124c5deb5e114a3b413a548bbc64e372c5b5db36165/numpy-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:596140185c7fa113563c67c2e894eabe0daea18cf8e33851738c19f70ce86aeb", size = 12925667 }, + { url = "https://files.pythonhosted.org/packages/96/86/453aa3949eab6ff54e2405f9cb0c01f756f031c3dc2a6d60a1d40cba5488/numpy-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:16372619ee728ed67a2a606a614f56d3eabc5b86f8b615c79d01957062826ca8", size = 21237256 }, + { url = "https://files.pythonhosted.org/packages/20/c3/93ecceadf3e155d6a9e4464dd2392d8d80cf436084c714dc8535121c83e8/numpy-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5521a06a3148686d9269c53b09f7d399a5725c47bbb5b35747e1cb76326b714b", size = 14408049 }, + { url = "https://files.pythonhosted.org/packages/8d/29/076999b69bd9264b8df5e56f2be18da2de6b2a2d0e10737e5307592e01de/numpy-2.2.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:7c8dde0ca2f77828815fd1aedfdf52e59071a5bae30dac3b4da2a335c672149a", size = 5408655 }, + { url = "https://files.pythonhosted.org/packages/e2/a7/b14f0a73eb0fe77cb9bd5b44534c183b23d4229c099e339c522724b02678/numpy-2.2.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:77974aba6c1bc26e3c205c2214f0d5b4305bdc719268b93e768ddb17e3fdd636", size = 6949996 }, + { url = "https://files.pythonhosted.org/packages/72/2f/8063da0616bb0f414b66dccead503bd96e33e43685c820e78a61a214c098/numpy-2.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d42f9c36d06440e34226e8bd65ff065ca0963aeecada587b937011efa02cdc9d", size = 14355789 }, + { url = "https://files.pythonhosted.org/packages/e6/d7/3cd47b00b8ea95ab358c376cf5602ad21871410950bc754cf3284771f8b6/numpy-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2712c5179f40af9ddc8f6727f2bd910ea0eb50206daea75f58ddd9fa3f715bb", size = 16411356 }, + { url = "https://files.pythonhosted.org/packages/27/c0/a2379e202acbb70b85b41483a422c1e697ff7eee74db642ca478de4ba89f/numpy-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c8b0451d2ec95010d1db8ca733afc41f659f425b7f608af569711097fd6014e2", size = 15576770 }, + { url = "https://files.pythonhosted.org/packages/bc/63/a13ee650f27b7999e5b9e1964ae942af50bb25606d088df4229283eda779/numpy-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9b4a8148c57ecac25a16b0e11798cbe88edf5237b0df99973687dd866f05e1b", size = 18200483 }, + { url = "https://files.pythonhosted.org/packages/4c/87/e71f89935e09e8161ac9c590c82f66d2321eb163893a94af749dfa8a3cf8/numpy-2.2.3-cp311-cp311-win32.whl", hash = "sha256:1f45315b2dc58d8a3e7754fe4e38b6fce132dab284a92851e41b2b344f6441c5", size = 6588415 }, + { url = "https://files.pythonhosted.org/packages/b9/c6/cd4298729826af9979c5f9ab02fcaa344b82621e7c49322cd2d210483d3f/numpy-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f48ba6f6c13e5e49f3d3efb1b51c8193215c42ac82610a04624906a9270be6f", size = 12929604 }, + { url = "https://files.pythonhosted.org/packages/43/ec/43628dcf98466e087812142eec6d1c1a6c6bdfdad30a0aa07b872dc01f6f/numpy-2.2.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12c045f43b1d2915eca6b880a7f4a256f59d62df4f044788c8ba67709412128d", size = 20929458 }, + { url = "https://files.pythonhosted.org/packages/9b/c0/2f4225073e99a5c12350954949ed19b5d4a738f541d33e6f7439e33e98e4/numpy-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:87eed225fd415bbae787f93a457af7f5990b92a334e346f72070bf569b9c9c95", size = 14115299 }, + { url = "https://files.pythonhosted.org/packages/ca/fa/d2c5575d9c734a7376cc1592fae50257ec95d061b27ee3dbdb0b3b551eb2/numpy-2.2.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:712a64103d97c404e87d4d7c47fb0c7ff9acccc625ca2002848e0d53288b90ea", size = 5145723 }, + { url = "https://files.pythonhosted.org/packages/eb/dc/023dad5b268a7895e58e791f28dc1c60eb7b6c06fcbc2af8538ad069d5f3/numpy-2.2.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a5ae282abe60a2db0fd407072aff4599c279bcd6e9a2475500fc35b00a57c532", size = 6678797 }, + { url = "https://files.pythonhosted.org/packages/3f/19/bcd641ccf19ac25abb6fb1dcd7744840c11f9d62519d7057b6ab2096eb60/numpy-2.2.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5266de33d4c3420973cf9ae3b98b54a2a6d53a559310e3236c4b2b06b9c07d4e", size = 14067362 }, + { url = "https://files.pythonhosted.org/packages/39/04/78d2e7402fb479d893953fb78fa7045f7deb635ec095b6b4f0260223091a/numpy-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b787adbf04b0db1967798dba8da1af07e387908ed1553a0d6e74c084d1ceafe", size = 16116679 }, + { url = "https://files.pythonhosted.org/packages/d0/a1/e90f7aa66512be3150cb9d27f3d9995db330ad1b2046474a13b7040dfd92/numpy-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:34c1b7e83f94f3b564b35f480f5652a47007dd91f7c839f404d03279cc8dd021", size = 15264272 }, + { url = "https://files.pythonhosted.org/packages/dc/b6/50bd027cca494de4fa1fc7bf1662983d0ba5f256fa0ece2c376b5eb9b3f0/numpy-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4d8335b5f1b6e2bce120d55fb17064b0262ff29b459e8493d1785c18ae2553b8", size = 17880549 }, + { url = "https://files.pythonhosted.org/packages/96/30/f7bf4acb5f8db10a96f73896bdeed7a63373137b131ca18bd3dab889db3b/numpy-2.2.3-cp312-cp312-win32.whl", hash = "sha256:4d9828d25fb246bedd31e04c9e75714a4087211ac348cb39c8c5f99dbb6683fe", size = 6293394 }, + { url = "https://files.pythonhosted.org/packages/42/6e/55580a538116d16ae7c9aa17d4edd56e83f42126cb1dfe7a684da7925d2c/numpy-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:83807d445817326b4bcdaaaf8e8e9f1753da04341eceec705c001ff342002e5d", size = 12626357 }, + { url = "https://files.pythonhosted.org/packages/0e/8b/88b98ed534d6a03ba8cddb316950fe80842885709b58501233c29dfa24a9/numpy-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bfdb06b395385ea9b91bf55c1adf1b297c9fdb531552845ff1d3ea6e40d5aba", size = 20916001 }, + { url = "https://files.pythonhosted.org/packages/d9/b4/def6ec32c725cc5fbd8bdf8af80f616acf075fe752d8a23e895da8c67b70/numpy-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:23c9f4edbf4c065fddb10a4f6e8b6a244342d95966a48820c614891e5059bb50", size = 14130721 }, + { url = "https://files.pythonhosted.org/packages/20/60/70af0acc86495b25b672d403e12cb25448d79a2b9658f4fc45e845c397a8/numpy-2.2.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:a0c03b6be48aaf92525cccf393265e02773be8fd9551a2f9adbe7db1fa2b60f1", size = 5130999 }, + { url = "https://files.pythonhosted.org/packages/2e/69/d96c006fb73c9a47bcb3611417cf178049aae159afae47c48bd66df9c536/numpy-2.2.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:2376e317111daa0a6739e50f7ee2a6353f768489102308b0d98fcf4a04f7f3b5", size = 6665299 }, + { url = "https://files.pythonhosted.org/packages/5a/3f/d8a877b6e48103733ac224ffa26b30887dc9944ff95dffdfa6c4ce3d7df3/numpy-2.2.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fb62fe3d206d72fe1cfe31c4a1106ad2b136fcc1606093aeab314f02930fdf2", size = 14064096 }, + { url = "https://files.pythonhosted.org/packages/e4/43/619c2c7a0665aafc80efca465ddb1f260287266bdbdce517396f2f145d49/numpy-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52659ad2534427dffcc36aac76bebdd02b67e3b7a619ac67543bc9bfe6b7cdb1", size = 16114758 }, + { url = "https://files.pythonhosted.org/packages/d9/79/ee4fe4f60967ccd3897aa71ae14cdee9e3c097e3256975cc9575d393cb42/numpy-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1b416af7d0ed3271cad0f0a0d0bee0911ed7eba23e66f8424d9f3dfcdcae1304", size = 15259880 }, + { url = "https://files.pythonhosted.org/packages/fb/c8/8b55cf05db6d85b7a7d414b3d1bd5a740706df00bfa0824a08bf041e52ee/numpy-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1402da8e0f435991983d0a9708b779f95a8c98c6b18a171b9f1be09005e64d9d", size = 17876721 }, + { url = "https://files.pythonhosted.org/packages/21/d6/b4c2f0564b7dcc413117b0ffbb818d837e4b29996b9234e38b2025ed24e7/numpy-2.2.3-cp313-cp313-win32.whl", hash = "sha256:136553f123ee2951bfcfbc264acd34a2fc2f29d7cdf610ce7daf672b6fbaa693", size = 6290195 }, + { url = "https://files.pythonhosted.org/packages/97/e7/7d55a86719d0de7a6a597949f3febefb1009435b79ba510ff32f05a8c1d7/numpy-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:5b732c8beef1d7bc2d9e476dbba20aaff6167bf205ad9aa8d30913859e82884b", size = 12619013 }, + { url = "https://files.pythonhosted.org/packages/a6/1f/0b863d5528b9048fd486a56e0b97c18bf705e88736c8cea7239012119a54/numpy-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:435e7a933b9fda8126130b046975a968cc2d833b505475e588339e09f7672890", size = 20944621 }, + { url = "https://files.pythonhosted.org/packages/aa/99/b478c384f7a0a2e0736177aafc97dc9152fc036a3fdb13f5a3ab225f1494/numpy-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7678556eeb0152cbd1522b684dcd215250885993dd00adb93679ec3c0e6e091c", size = 14142502 }, + { url = "https://files.pythonhosted.org/packages/fb/61/2d9a694a0f9cd0a839501d362de2a18de75e3004576a3008e56bdd60fcdb/numpy-2.2.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2e8da03bd561504d9b20e7a12340870dfc206c64ea59b4cfee9fceb95070ee94", size = 5176293 }, + { url = "https://files.pythonhosted.org/packages/33/35/51e94011b23e753fa33f891f601e5c1c9a3d515448659b06df9d40c0aa6e/numpy-2.2.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:c9aa4496fd0e17e3843399f533d62857cef5900facf93e735ef65aa4bbc90ef0", size = 6691874 }, + { url = "https://files.pythonhosted.org/packages/ff/cf/06e37619aad98a9d03bd8d65b8e3041c3a639be0f5f6b0a0e2da544538d4/numpy-2.2.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4ca91d61a4bf61b0f2228f24bbfa6a9facd5f8af03759fe2a655c50ae2c6610", size = 14036826 }, + { url = "https://files.pythonhosted.org/packages/0c/93/5d7d19955abd4d6099ef4a8ee006f9ce258166c38af259f9e5558a172e3e/numpy-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:deaa09cd492e24fd9b15296844c0ad1b3c976da7907e1c1ed3a0ad21dded6f76", size = 16096567 }, + { url = "https://files.pythonhosted.org/packages/af/53/d1c599acf7732d81f46a93621dab6aa8daad914b502a7a115b3f17288ab2/numpy-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:246535e2f7496b7ac85deffe932896a3577be7af8fb7eebe7146444680297e9a", size = 15242514 }, + { url = "https://files.pythonhosted.org/packages/53/43/c0f5411c7b3ea90adf341d05ace762dad8cb9819ef26093e27b15dd121ac/numpy-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:daf43a3d1ea699402c5a850e5313680ac355b4adc9770cd5cfc2940e7861f1bf", size = 17872920 }, + { url = "https://files.pythonhosted.org/packages/5b/57/6dbdd45ab277aff62021cafa1e15f9644a52f5b5fc840bc7591b4079fb58/numpy-2.2.3-cp313-cp313t-win32.whl", hash = "sha256:cf802eef1f0134afb81fef94020351be4fe1d6681aadf9c5e862af6602af64ef", size = 6346584 }, + { url = "https://files.pythonhosted.org/packages/97/9b/484f7d04b537d0a1202a5ba81c6f53f1846ae6c63c2127f8df869ed31342/numpy-2.2.3-cp313-cp313t-win_amd64.whl", hash = "sha256:aee2512827ceb6d7f517c8b85aa5d3923afe8fc7a57d028cffcd522f1c6fd082", size = 12706784 }, + { url = "https://files.pythonhosted.org/packages/0a/b5/a7839f5478be8f859cb880f13d90fcfe4b0ec7a9ebaff2bcc30d96760596/numpy-2.2.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3c2ec8a0f51d60f1e9c0c5ab116b7fc104b165ada3f6c58abf881cb2eb16044d", size = 21064244 }, + { url = "https://files.pythonhosted.org/packages/29/e8/5da32ffcaa7a72f7ecd82f90c062140a061eb823cb88e90279424e515cf4/numpy-2.2.3-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:ed2cf9ed4e8ebc3b754d398cba12f24359f018b416c380f577bbae112ca52fc9", size = 6809418 }, + { url = "https://files.pythonhosted.org/packages/a8/a9/68aa7076c7656a7308a0f73d0a2ced8c03f282c9fd98fa7ce21c12634087/numpy-2.2.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39261798d208c3095ae4f7bc8eaeb3481ea8c6e03dc48028057d3cbdbdb8937e", size = 16215461 }, + { url = "https://files.pythonhosted.org/packages/17/7f/d322a4125405920401450118dbdc52e0384026bd669939484670ce8b2ab9/numpy-2.2.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:783145835458e60fa97afac25d511d00a1eca94d4a8f3ace9fe2043003c678e4", size = 12839607 }, +] + +[[package]] +name = "packaging" +version = "21.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyparsing", version = "3.0.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.6.8'" }, + { name = "pyparsing", version = "3.1.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.6.8' and python_full_version < '3.7'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/9e/d1a7217f69310c1db8fdf8ab396229f55a699ce34a203691794c5d1cad0c/packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", size = 84848 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/8e/8de486cbd03baba4deef4142bd643a3e7bbe954a784dc1bb17142572d127/packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522", size = 40750 }, +] + +[[package]] +name = "pycparser" +version = "2.21" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.7.*'", + "python_full_version >= '3.6.8' and python_full_version < '3.7'", + "python_full_version < '3.6.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/0b/95d387f5f4433cb0f53ff7ad859bd2c6051051cebbb564f139a999ab46de/pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206", size = 170877 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/d5/5f610ebe421e85889f2e55e33b7f9a6795bd982198517d912eb1c76e1a53/pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", size = 118697 }, +] + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, +] + +[[package]] +name = "pyparsing" +version = "3.0.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.6.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/60/9bed18f43275b34198eb9720d4c1238c68b3755620d20df0afd89424d32b/pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea", size = 884709 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/c1/23fd82ad3121656b585351aba6c19761926bb0db2ebed9e4ff09a43a3fcc/pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484", size = 98049 }, +] + +[[package]] +name = "pyparsing" +version = "3.1.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.6.8' and python_full_version < '3.7'", +] +sdist = { url = "https://files.pythonhosted.org/packages/83/08/13f3bce01b2061f2bbd582c9df82723de943784cf719a35ac886c652043a/pyparsing-3.1.4.tar.gz", hash = "sha256:f86ec8d1a83f11977c9a6ea7598e8c27fc5cddfa5b07ea2241edbbde1d7bc032", size = 900231 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/0c/0e3c05b1c87bb6a1c76d281b0f35e78d2d80ac91b5f8f524cebf77f51049/pyparsing-3.1.4-py3-none-any.whl", hash = "sha256:a6a7ee4235a3f944aa1fa2249307708f893fe5717dc603503c6c7969c070fb7c", size = 104100 }, +] + +[[package]] +name = "redis" +version = "4.3.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.6.8' and python_full_version < '3.7'", + "python_full_version < '3.6.8'", +] +dependencies = [ + { name = "async-timeout", version = "4.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, + { name = "importlib-metadata", version = "4.8.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, + { name = "packaging", marker = "python_full_version < '3.7'" }, + { name = "typing-extensions", version = "4.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/52/2a4b3ceffe59483cdea5e653aaa40ebd7a90241612c40212dfc10fde9215/redis-4.3.6.tar.gz", hash = "sha256:7a462714dcbf7b1ad1acd81f2862b653cc8535cdfc879e28bf4947140797f948", size = 4577771 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/f6/19237b28c632935c7359bddf703395ba13bbd134fc5e2eb297c4c120398c/redis-4.3.6-py3-none-any.whl", hash = "sha256:1ea4018b8b5d8a13837f0f1c418959c90bfde0a605cb689e8070cff368a3b177", size = 248834 }, +] + +[[package]] +name = "redis" +version = "5.0.8" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.7.*'", +] +dependencies = [ + { name = "async-timeout", version = "4.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" }, + { name = "importlib-metadata", version = "6.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" }, + { name = "typing-extensions", version = "4.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/48/10/defc227d65ea9c2ff5244645870859865cba34da7373477c8376629746ec/redis-5.0.8.tar.gz", hash = "sha256:0c5b10d387568dfe0698c6fad6615750c24170e548ca2deac10c649d463e9870", size = 4595651 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/d1/19a9c76811757684a0f74adc25765c8a901d67f9f6472ac9d57c844a23c8/redis-5.0.8-py3-none-any.whl", hash = "sha256:56134ee08ea909106090934adc36f65c9bcbbaecea5b21ba704ba6fb561f8eb4", size = 255608 }, +] + +[[package]] +name = "redis" +version = "5.2.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", +] +dependencies = [ + { name = "async-timeout", version = "5.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8' and python_full_version < '3.11.3'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/47/da/d283a37303a995cd36f8b92db85135153dc4f7a8e4441aa827721b442cfb/redis-5.2.1.tar.gz", hash = "sha256:16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f", size = 4608355 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/5f/fa26b9b2672cbe30e07d9a5bdf39cf16e3b80b42916757c5f92bca88e4ba/redis-5.2.1-py3-none-any.whl", hash = "sha256:ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4", size = 261502 }, +] + +[[package]] +name = "requests" +version = "2.27.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.6.8' and python_full_version < '3.7'", + "python_full_version < '3.6.8'", +] +dependencies = [ + { name = "certifi", marker = "python_full_version < '3.7'" }, + { name = "charset-normalizer", version = "2.0.12", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, + { name = "idna", marker = "python_full_version < '3.7'" }, + { name = "urllib3", version = "1.26.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/f3/26ff3767f099b73e0efa138a9998da67890793bfa475d8278f84a30fec77/requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61", size = 106758 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/61/08076519c80041bc0ffa1a8af0cbd3bf3e2b62af10435d269a9d0f40564d/requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d", size = 63133 }, +] + +[[package]] +name = "requests" +version = "2.31.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.7.*'", +] +dependencies = [ + { name = "certifi", marker = "python_full_version == '3.7.*'" }, + { name = "charset-normalizer", version = "3.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" }, + { name = "idna", marker = "python_full_version == '3.7.*'" }, + { name = "urllib3", version = "2.0.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/be/10918a2eac4ae9f02f6cfe6414b7a155ccd8f7f9d4380d62fd5b955065c3/requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1", size = 110794 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", size = 62574 }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", +] +dependencies = [ + { name = "certifi", marker = "python_full_version >= '3.8'" }, + { name = "charset-normalizer", version = "3.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, + { name = "idna", marker = "python_full_version >= '3.8'" }, + { name = "urllib3", version = "2.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "urllib3", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + +[[package]] +name = "ruff" +version = "0.0.17" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.6.8' and python_full_version < '3.7'", + "python_full_version < '3.6.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/ed/7adc91572c08f346976335f6b1b22774ea555d11043a9ff013f962affab5/ruff-0.0.17.tar.gz", hash = "sha256:5815383171ccbab333d6b6d54253e91003ee6be4627738d56855cbefc393df41", size = 54259 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/3b/4a6b289ab3ca80109402c15dd0fc83ef1c77572453b346a74ebc55666db5/ruff-0.0.17-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:2fa56d385b31462e26a605477c626023b16fb5a399b619ba0966d0d2b8d88eca", size = 1665731 }, + { url = "https://files.pythonhosted.org/packages/3d/23/5e519dd38ae42a75f8e6a952a3c5ea842804e2cb8c60a1d72807131d8aba/ruff-0.0.17-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:4f5694f9876cde21b95ad9c1691d0513617d2e88c0749f400b866505217fd5a0", size = 3202527 }, + { url = "https://files.pythonhosted.org/packages/9c/05/69872574ea3044cfbac4becef1c25c0b1227499c91c2232b83bad9bb104c/ruff-0.0.17-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d1349f4e5a4d53294fce92f42ecf881a73c180d71f14121461cac7d251abafd4", size = 1543942 }, + { url = "https://files.pythonhosted.org/packages/24/16/e5c7cb9b77d1f64d94d507d0c3d7ef491a10dc75825f458cb1bb05ae41cf/ruff-0.0.17-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f4e60d690898be3c3bf24387e67ac89496a97eb8814b8c0f0670ea43b6e83ee2", size = 1788392 }, + { url = "https://files.pythonhosted.org/packages/76/a4/dad616f277880963968d8a5e5719556f4ecadc7333c0da8bfb5060c5750c/ruff-0.0.17-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:522cacc6e550a7d59ead3b0ca65623582d51bfe32f6c780770ccf5d1bc3246cb", size = 1758753 }, + { url = "https://files.pythonhosted.org/packages/bc/44/08d68e219e2e7659991b467122f41326fde1e2e959107746ed7559f5e498/ruff-0.0.17-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e608511d0349a6211a0a123744cc0960f88539dbf62a0b8a77e3ee483237a6da", size = 1677835 }, + { url = "https://files.pythonhosted.org/packages/d6/7d/4d897311a299007b8542bfcd83dff9b09db6a7130f48ad3252e8ba3740b9/ruff-0.0.17-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:78f14cf1056ded6bda77162d7483e11a2f2a29763538422adaa5412654ff1a94", size = 1700213 }, + { url = "https://files.pythonhosted.org/packages/90/af/2c5dfa97f6994cf462e2b1934662f730608eec7ccfd6b992bab1af002ce9/ruff-0.0.17-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7cd7180893a3ed789c82838c8082fda074ba1cec46f383e255e696533f634be", size = 1894523 }, + { url = "https://files.pythonhosted.org/packages/40/e8/74569b7b05ece82a9e298eed7e01b2ae18205cee88de0283d0647370d120/ruff-0.0.17-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a90917b0e9d2f851294e445f0b898fa94051c4d9edcc1ab6d40bc1129fb9bb1e", size = 2119346 }, + { url = "https://files.pythonhosted.org/packages/7c/46/2cb84ccb0944c37208a2cea880ba8c6cb2a1752759771d385d6217de46b2/ruff-0.0.17-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9bb485eb3e0ba0c19ffd14b533659448c1c5e2958171e818fc1bc42c76f3d99a", size = 1679168 }, + { url = "https://files.pythonhosted.org/packages/47/65/cfd4e305851fdb94b7c8147d9d839ed83f9a93a941cc89d71e633a094642/ruff-0.0.17-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f3a399e4d211cdbec9229a89b1b7e77345eae881e9c3682fef7e90044de6a864", size = 1715985 }, + { url = "https://files.pythonhosted.org/packages/23/7a/96a4e5c51bab9538d9ee27a0eba95bd790d120bcc76a195417abbfb2cc81/ruff-0.0.17-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:80fe80b12ea042b9f0d8e80608db400e0c8e419d74a4dcf8b3b4fea9ec03362f", size = 1782188 }, + { url = "https://files.pythonhosted.org/packages/fb/3c/92267e9b9336bb1ba9ba7f5e3b9028e57ee4d77f32f728992c23da69ab53/ruff-0.0.17-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3b067ed2bd3fd0d4be591ea9afc796c07706291d78efe5a8eda4172c4d43525c", size = 1812203 }, + { url = "https://files.pythonhosted.org/packages/ca/d9/52cef313261a61931c4f3be228a774c5dad89f45042518d0f483864902ca/ruff-0.0.17-cp310-none-win32.whl", hash = "sha256:e3aba30e3aad77f260095ea1dbcf2834ab64d75133ff8d260625bb22887e2799", size = 1626413 }, + { url = "https://files.pythonhosted.org/packages/fe/d7/741e229667d0038d004783286ea6fa4554ff7a3b52bfe9b26b0380462e56/ruff-0.0.17-cp310-none-win_amd64.whl", hash = "sha256:a068bced7aff34173319931972fde3d7e68e3894915edac4e0f8c9b7bec7a226", size = 1654976 }, + { url = "https://files.pythonhosted.org/packages/20/50/470c8688e96fecac2096205cb45438676f6277b9c713a0aa1c4e633af503/ruff-0.0.17-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:b708d650c2ba25458d9e735c51981b687bf6747a4b28403eb7f6bae1aa93cfcf", size = 1665730 }, + { url = "https://files.pythonhosted.org/packages/13/13/f68c059cafb95122214c2defac7005c8a5ce278c7f1768d6849c167df07a/ruff-0.0.17-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:91019d271c223a4c562dbc2fbf2a2a96157524999a1173a4c858816d0c1bb9e7", size = 1543941 }, + { url = "https://files.pythonhosted.org/packages/70/e9/353db015927b6336bffbb7fe16af3bf76e860ab0fbddd66bf0a6995a2bfa/ruff-0.0.17-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:edbaf1c7f6b6483a206e549026f03ef7e04e480b5204437e21370de508dcf736", size = 1788393 }, + { url = "https://files.pythonhosted.org/packages/32/e5/2c41a62d58763948fa332619226cb11ded2ec161c678d70b7991b839ecf5/ruff-0.0.17-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:52110ed886d674497531d44ead5fa2fe99be930adfc9cec4b1f39409043efb13", size = 1758755 }, + { url = "https://files.pythonhosted.org/packages/3f/a9/f0cc3416b6f2e3755c8b37271112c609dfdf862b2825d86f4fc926eee037/ruff-0.0.17-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:febfbe4fa02e680b93b3fc2dcb0ffe5d601435c9719a51e35fe51fff1d0cc2c6", size = 1677834 }, + { url = "https://files.pythonhosted.org/packages/ed/54/ad7286c9e136260eaf8d6236202384dfe71a884a7b31288e129a43c33cb7/ruff-0.0.17-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6e72d62bd47f086d14ea5796ea18d1b98089a839dad693afa471a1fcdb6ae0d", size = 1700213 }, + { url = "https://files.pythonhosted.org/packages/bd/f7/12a3cfa5b88485ecdc724d0e915a8ebe4dfebfa94422855a445850564d01/ruff-0.0.17-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:27d1bd7c71a90522e383853e411fcb402ccdcaf8778c6e0d54359153772f7870", size = 1894524 }, + { url = "https://files.pythonhosted.org/packages/e2/1a/1ab955830e84dcad9f606954d5b1094e186de144a2ad37247aa37145cbcd/ruff-0.0.17-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bed7beec36834e6fc40a03af92bc0da67599c70fbe24d8d820a1a2110b25eaf", size = 2119348 }, + { url = "https://files.pythonhosted.org/packages/3c/00/a67b2904d92034abd2f35a4d930eae08abf64106f64a90a67770395db8a7/ruff-0.0.17-cp37-none-win32.whl", hash = "sha256:c664d897e21b9aab2b20c764434653aa394e32c32d38e751fd4f381ace3a4e58", size = 1626410 }, + { url = "https://files.pythonhosted.org/packages/9c/a9/dacfff99065b0588ff9cf07411d7bbc8a167d1542d92a7e46f5825e262fa/ruff-0.0.17-cp37-none-win_amd64.whl", hash = "sha256:8c5900fd09baa2c7a4aecbdc754d3a43f2842906ee571812fa3eb28b8e7973a5", size = 1654975 }, + { url = "https://files.pythonhosted.org/packages/f3/a9/e5a048d209e246b3702a23540b1e09faa79b313b1b23f27993e33df3b01a/ruff-0.0.17-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:db04c29f114c68f447aeec23f9be6118ea11a18a2444416cb4afb0fd918e50db", size = 1665728 }, + { url = "https://files.pythonhosted.org/packages/32/3d/13114ef5793e43fd5c8ee17047fbdc86e9eacb81f708c0e01ca9d5db773f/ruff-0.0.17-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:8527c8aacdd0e911c6f7d6f1b109a17e68300ac0f255ccce73e748bb8835c722", size = 3202527 }, + { url = "https://files.pythonhosted.org/packages/09/3f/657daee09a7412bda8af1963d6dd1b4adfdbc0e2d37c7261984fc4953e19/ruff-0.0.17-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:889aa57c771140ec6b17e15af8308e88e43a07b7369b97cb0426e1393e3d10b5", size = 1543942 }, + { url = "https://files.pythonhosted.org/packages/c1/6b/15e1c6744216236a3a08a8e40e318c329f86febdee7a1d62e3c449d75009/ruff-0.0.17-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7be31e77c1f98d9d02a7f6f2d4c05e8236cd9c82d0c3356b083162a011fc4d23", size = 1788391 }, + { url = "https://files.pythonhosted.org/packages/ff/fa/0cf5c91b2d415416ef0d534a8f19bdc07d11a52fd54aface524d006be1ce/ruff-0.0.17-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fe0c047d6fc74b55fcd1ac4f18110500ba871de2014039e4838ed7444d3459ff", size = 1758753 }, + { url = "https://files.pythonhosted.org/packages/8f/8b/ac8d7a1cce151c74fa9458f0297ca6fc287f9fe1a57fe710f99de970bfdb/ruff-0.0.17-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4933e275f0c3af3a78ecf1f9a4e12cc0426cd76398c7e904786f99c1ea8a0dd", size = 1677834 }, + { url = "https://files.pythonhosted.org/packages/9f/38/e51bff666939e38155ef12930e6c1d4970f5a28c7f1ff3867e97b00a4cd0/ruff-0.0.17-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a9f2fb8b3bac67d0fa9e50411ab424a863e4bc29a3336a046cc38f06d3ec17f", size = 1700211 }, + { url = "https://files.pythonhosted.org/packages/e2/96/949c17bed22816136d8e0456a242059da202e316ca4510715560c026d0d8/ruff-0.0.17-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d45439cbe73332a064306c39528d6bbf4856abb6d377ad8244b6e74a737daaa", size = 1894522 }, + { url = "https://files.pythonhosted.org/packages/89/61/cec1cd7093eea58e083060fc66c3fcf758737785a21b752fc568f57eabab/ruff-0.0.17-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f7433f20d39a3819e322a3497dce037c6110f9588ec51ba136a938109dd31e71", size = 2119345 }, + { url = "https://files.pythonhosted.org/packages/a9/32/27bb21e91e0e7d6e76be6d5fce844c3ac52278ae49d8964eb356a024cf26/ruff-0.0.17-cp38-none-win32.whl", hash = "sha256:5f8f4f4310018807402c77d81ae020666b742d2173a73b147ce0d1e0a08f022f", size = 1626409 }, + { url = "https://files.pythonhosted.org/packages/d0/3f/cd9d27f2dbbd4975c7880d8284f893ea99cf73c646f453b9cef1b3924db5/ruff-0.0.17-cp38-none-win_amd64.whl", hash = "sha256:f0d0e8058d903b8fe899e04e1a957127ca97452553cf70ba9b4d1b277f034ad1", size = 1654979 }, + { url = "https://files.pythonhosted.org/packages/d3/4e/c854d4587c180936b33eac57344b11f52564878d2939fd6d9d842fa6e5ae/ruff-0.0.17-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:e6f24c3746d199bdb0d47149ed5353a41f0192630911396822fda0f8a6feaa0b", size = 1665735 }, + { url = "https://files.pythonhosted.org/packages/49/8b/8d2de1c9f7e2056bd4edaea393d6ad3494e75e99136cf127402afb4c496c/ruff-0.0.17-cp39-cp39-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:71cb773b19240f1be64c5f71aa2ad52b9f44fde1605c2c2f4089a5d61cb552d2", size = 3202528 }, + { url = "https://files.pythonhosted.org/packages/aa/9f/7235c23ed12dfd44823d3a127d5654cc2fbfbe3daa1aca00c43ad2ccd519/ruff-0.0.17-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bab716debcab46d9a1d7c8d00e2acf2b48ff28ec519b2b4c0eba873236782c21", size = 1543942 }, + { url = "https://files.pythonhosted.org/packages/2e/69/1d4a2819146458d478e8c8a194452da263ab60202083d8eba02307fde216/ruff-0.0.17-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ef57186452c0cfe71f09dac434bda0f1a804808f92221142adb9de28c3f422e6", size = 1788392 }, + { url = "https://files.pythonhosted.org/packages/c2/35/c3e2a3c3690e732860342b16e606795e977d87a90176ed1dae13c001ee86/ruff-0.0.17-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8b00be13abaf107c30b8bcaf2ac89dc2b3abd164728c229339910211c05e8c43", size = 1758753 }, + { url = "https://files.pythonhosted.org/packages/ad/3d/7d71456c7e1c543ed223b400a2e962a344b5467a27e196c4dd6f2d1d30c3/ruff-0.0.17-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9c1ba3383995091ce6f3618e89f1bb0ed5caa730f64bb79a1c60184682dc5c3", size = 1677834 }, + { url = "https://files.pythonhosted.org/packages/f3/73/e87e31367fe7af0a027672e49703b61ea6566912d09e34a8e3e43bce3455/ruff-0.0.17-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:865114aa655dc54e5699f18b258a33a15a36da915de4936d7a458425e7f6351d", size = 1700211 }, + { url = "https://files.pythonhosted.org/packages/2b/27/7449e2a8bca1957c0e2d57316ca8fdcdf8d83277b23d50a33bdded703aa6/ruff-0.0.17-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a390b4657cc1eebd9bb0e581da768aa557b1157f5eeed6fc8b5b920991061b04", size = 1894520 }, + { url = "https://files.pythonhosted.org/packages/80/39/db6441f33216e25e5dba811e9d908cff898df7c9930006c735ba6578dd65/ruff-0.0.17-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d3fec0e9e8f285324127b97c55b525fe61e8e16e93e1a03d34aba80e3aff9f21", size = 2119346 }, + { url = "https://files.pythonhosted.org/packages/73/cb/d7ae9d2276f23f89642df0af808c85acc632aceca5d7039ae3afe4585afb/ruff-0.0.17-cp39-none-win32.whl", hash = "sha256:3f063c889d65d71fb189d6246ccd537c23c9d0f6e483c961ac0b5e8477d6e3ca", size = 1626409 }, + { url = "https://files.pythonhosted.org/packages/d8/a7/3ccc344a2b228a15b52217ed2a2982214ad77684745c3e09ace2b1f8e9bf/ruff-0.0.17-cp39-none-win_amd64.whl", hash = "sha256:4ba403b8a5f38753ed3ba7ca16fb7c67eaee96a4e4a9e9709f3ad8cd3909012c", size = 1654973 }, +] + +[[package]] +name = "ruff" +version = "0.9.10" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", + "python_full_version == '3.7.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/20/8e/fafaa6f15c332e73425d9c44ada85360501045d5ab0b81400076aff27cf6/ruff-0.9.10.tar.gz", hash = "sha256:9bacb735d7bada9cfb0f2c227d3658fc443d90a727b47f206fb33f52f3c0eac7", size = 3759776 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/b2/af7c2cc9e438cbc19fafeec4f20bfcd72165460fe75b2b6e9a0958c8c62b/ruff-0.9.10-py3-none-linux_armv6l.whl", hash = "sha256:eb4d25532cfd9fe461acc83498361ec2e2252795b4f40b17e80692814329e42d", size = 10049494 }, + { url = "https://files.pythonhosted.org/packages/6d/12/03f6dfa1b95ddd47e6969f0225d60d9d7437c91938a310835feb27927ca0/ruff-0.9.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:188a6638dab1aa9bb6228a7302387b2c9954e455fb25d6b4470cb0641d16759d", size = 10853584 }, + { url = "https://files.pythonhosted.org/packages/02/49/1c79e0906b6ff551fb0894168763f705bf980864739572b2815ecd3c9df0/ruff-0.9.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5284dcac6b9dbc2fcb71fdfc26a217b2ca4ede6ccd57476f52a587451ebe450d", size = 10155692 }, + { url = "https://files.pythonhosted.org/packages/5b/01/85e8082e41585e0e1ceb11e41c054e9e36fed45f4b210991052d8a75089f/ruff-0.9.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47678f39fa2a3da62724851107f438c8229a3470f533894b5568a39b40029c0c", size = 10369760 }, + { url = "https://files.pythonhosted.org/packages/a1/90/0bc60bd4e5db051f12445046d0c85cc2c617095c0904f1aa81067dc64aea/ruff-0.9.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99713a6e2766b7a17147b309e8c915b32b07a25c9efd12ada79f217c9c778b3e", size = 9912196 }, + { url = "https://files.pythonhosted.org/packages/66/ea/0b7e8c42b1ec608033c4d5a02939c82097ddcb0b3e393e4238584b7054ab/ruff-0.9.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524ee184d92f7c7304aa568e2db20f50c32d1d0caa235d8ddf10497566ea1a12", size = 11434985 }, + { url = "https://files.pythonhosted.org/packages/d5/86/3171d1eff893db4f91755175a6e1163c5887be1f1e2f4f6c0c59527c2bfd/ruff-0.9.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:df92aeac30af821f9acf819fc01b4afc3dfb829d2782884f8739fb52a8119a16", size = 12155842 }, + { url = "https://files.pythonhosted.org/packages/89/9e/700ca289f172a38eb0bca752056d0a42637fa17b81649b9331786cb791d7/ruff-0.9.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de42e4edc296f520bb84954eb992a07a0ec5a02fecb834498415908469854a52", size = 11613804 }, + { url = "https://files.pythonhosted.org/packages/f2/92/648020b3b5db180f41a931a68b1c8575cca3e63cec86fd26807422a0dbad/ruff-0.9.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d257f95b65806104b6b1ffca0ea53f4ef98454036df65b1eda3693534813ecd1", size = 13823776 }, + { url = "https://files.pythonhosted.org/packages/5e/a6/cc472161cd04d30a09d5c90698696b70c169eeba2c41030344194242db45/ruff-0.9.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60dec7201c0b10d6d11be00e8f2dbb6f40ef1828ee75ed739923799513db24c", size = 11302673 }, + { url = "https://files.pythonhosted.org/packages/6c/db/d31c361c4025b1b9102b4d032c70a69adb9ee6fde093f6c3bf29f831c85c/ruff-0.9.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d838b60007da7a39c046fcdd317293d10b845001f38bcb55ba766c3875b01e43", size = 10235358 }, + { url = "https://files.pythonhosted.org/packages/d1/86/d6374e24a14d4d93ebe120f45edd82ad7dcf3ef999ffc92b197d81cdc2a5/ruff-0.9.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ccaf903108b899beb8e09a63ffae5869057ab649c1e9231c05ae354ebc62066c", size = 9886177 }, + { url = "https://files.pythonhosted.org/packages/00/62/a61691f6eaaac1e945a1f3f59f1eea9a218513139d5b6c2b8f88b43b5b8f/ruff-0.9.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f9567d135265d46e59d62dc60c0bfad10e9a6822e231f5b24032dba5a55be6b5", size = 10864747 }, + { url = "https://files.pythonhosted.org/packages/ee/94/2c7065e1d92a8a8a46d46d9c3cf07b0aa7e0a1e0153d74baa5e6620b4102/ruff-0.9.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5f202f0d93738c28a89f8ed9eaba01b7be339e5d8d642c994347eaa81c6d75b8", size = 11360441 }, + { url = "https://files.pythonhosted.org/packages/a7/8f/1f545ea6f9fcd7bf4368551fb91d2064d8f0577b3079bb3f0ae5779fb773/ruff-0.9.10-py3-none-win32.whl", hash = "sha256:bfb834e87c916521ce46b1788fbb8484966e5113c02df216680102e9eb960029", size = 10247401 }, + { url = "https://files.pythonhosted.org/packages/4f/18/fb703603ab108e5c165f52f5b86ee2aa9be43bb781703ec87c66a5f5d604/ruff-0.9.10-py3-none-win_amd64.whl", hash = "sha256:f2160eeef3031bf4b17df74e307d4c5fb689a6f3a26a2de3f7ef4044e3c484f1", size = 11366360 }, + { url = "https://files.pythonhosted.org/packages/35/85/338e603dc68e7d9994d5d84f24adbf69bae760ba5efd3e20f5ff2cec18da/ruff-0.9.10-py3-none-win_arm64.whl", hash = "sha256:5fd804c0327a5e5ea26615550e706942f348b197d5475ff34c19733aee4b2e69", size = 10436892 }, +] + +[[package]] +name = "tomli" +version = "1.2.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.6.8' and python_full_version < '3.7'", + "python_full_version < '3.6.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/2e/d0a8276b0cf9b9e34fd0660c330acc59656f53bb2209adc75af863a3582d/tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f", size = 15094 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/e4/74f9440db36734d7ba83c574c1e7024009ce849208a41f90e94a134dc6d1/tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c", size = 12122 }, +] + +[[package]] +name = "tomli" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.7.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/3f/d7af728f075fb08564c5949a9c95e44352e23dee646869fa104a3b2060a3/tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f", size = 15164 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", size = 12757 }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, +] + +[[package]] +name = "typed-ast" +version = "1.5.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/7e/a424029f350aa8078b75fd0d360a787a273ca753a678d1104c5fa4f3072a/typed_ast-1.5.5.tar.gz", hash = "sha256:94282f7a354f36ef5dbce0ef3467ebf6a258e370ab33d5b40c249fa996e590dd", size = 252841 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/07/5defe18d4fc16281cd18c4374270abc430c3d852d8ac29b5db6599d45cfe/typed_ast-1.5.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4bc1efe0ce3ffb74784e06460f01a223ac1f6ab31c6bc0376a21184bf5aabe3b", size = 223267 }, + { url = "https://files.pythonhosted.org/packages/a0/5c/e379b00028680bfcd267d845cf46b60e76d8ac6f7009fd440d6ce030cc92/typed_ast-1.5.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f7a8c46a8b333f71abd61d7ab9255440d4a588f34a21f126bbfc95f6049e686", size = 208260 }, + { url = "https://files.pythonhosted.org/packages/3b/99/5cc31ef4f3c80e1ceb03ed2690c7085571e3fbf119cbd67a111ec0b6622f/typed_ast-1.5.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597fc66b4162f959ee6a96b978c0435bd63791e31e4f410622d19f1686d5e769", size = 842272 }, + { url = "https://files.pythonhosted.org/packages/e2/ed/b9b8b794b37b55c9247b1e8d38b0361e8158795c181636d34d6c11b506e7/typed_ast-1.5.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d41b7a686ce653e06c2609075d397ebd5b969d821b9797d029fccd71fdec8e04", size = 824651 }, + { url = "https://files.pythonhosted.org/packages/ca/59/dbbbe5a0e91c15d14a0896b539a5ed01326b0d468e75c1a33274d128d2d1/typed_ast-1.5.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5fe83a9a44c4ce67c796a1b466c270c1272e176603d5e06f6afbc101a572859d", size = 854960 }, + { url = "https://files.pythonhosted.org/packages/90/f0/0956d925f87bd81f6e0f8cf119eac5e5c8f4da50ca25bb9f5904148d4611/typed_ast-1.5.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d5c0c112a74c0e5db2c75882a0adf3133adedcdbfd8cf7c9d6ed77365ab90a1d", size = 839321 }, + { url = "https://files.pythonhosted.org/packages/43/17/4bdece9795da6f3345c4da5667ac64bc25863617f19c28d81f350f515be6/typed_ast-1.5.5-cp310-cp310-win_amd64.whl", hash = "sha256:e1a976ed4cc2d71bb073e1b2a250892a6e968ff02aa14c1f40eba4f365ffec02", size = 139380 }, + { url = "https://files.pythonhosted.org/packages/75/53/b685e10da535c7b3572735f8bea0d4abb35a04722a7d44ca9c163a0cf822/typed_ast-1.5.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c631da9710271cb67b08bd3f3813b7af7f4c69c319b75475436fcab8c3d21bee", size = 223264 }, + { url = "https://files.pythonhosted.org/packages/96/fd/fc8ccf19fc16a40a23e7c7802d0abc78c1f38f1abb6e2447c474f8a076d8/typed_ast-1.5.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b445c2abfecab89a932b20bd8261488d574591173d07827c1eda32c457358b18", size = 208158 }, + { url = "https://files.pythonhosted.org/packages/bf/9a/598e47f2c3ecd19d7f1bb66854d0d3ba23ffd93c846448790a92524b0a8d/typed_ast-1.5.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc95ffaaab2be3b25eb938779e43f513e0e538a84dd14a5d844b8f2932593d88", size = 878366 }, + { url = "https://files.pythonhosted.org/packages/60/ca/765e8bf8b24d0ed7b9fc669f6826c5bc3eb7412fc765691f59b83ae195b2/typed_ast-1.5.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61443214d9b4c660dcf4b5307f15c12cb30bdfe9588ce6158f4a005baeb167b2", size = 860314 }, + { url = "https://files.pythonhosted.org/packages/d9/3c/4af750e6c673a0dd6c7b9f5b5e5ed58ec51a2e4e744081781c664d369dfa/typed_ast-1.5.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6eb936d107e4d474940469e8ec5b380c9b329b5f08b78282d46baeebd3692dc9", size = 898108 }, + { url = "https://files.pythonhosted.org/packages/03/8d/d0a4d1e060e1e8dda2408131a0cc7633fc4bc99fca5941dcb86c461dfe01/typed_ast-1.5.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e48bf27022897577d8479eaed64701ecaf0467182448bd95759883300ca818c8", size = 881971 }, + { url = "https://files.pythonhosted.org/packages/90/83/f28d2c912cd010a09b3677ac69d23181045eb17e358914ab739b7fdee530/typed_ast-1.5.5-cp311-cp311-win_amd64.whl", hash = "sha256:83509f9324011c9a39faaef0922c6f720f9623afe3fe220b6d0b15638247206b", size = 139286 }, + { url = "https://files.pythonhosted.org/packages/cd/0e/0b46ff64402abbd2ff14f573168cd73842ebe1dec531435226356267837d/typed_ast-1.5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:44f214394fc1af23ca6d4e9e744804d890045d1643dd7e8229951e0ef39429b5", size = 221825 }, + { url = "https://files.pythonhosted.org/packages/69/73/45dc2dcf4902c5afb7c0173f7638bcc9f1218dab32734b077dfdc7489d74/typed_ast-1.5.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:118c1ce46ce58fda78503eae14b7664163aa735b620b64b5b725453696f2a35c", size = 793086 }, + { url = "https://files.pythonhosted.org/packages/59/9b/3550429ac7c031a4f776f6950067d6ccf8d4f0fe8933c1d05c4cf50827b5/typed_ast-1.5.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be4919b808efa61101456e87f2d4c75b228f4e52618621c77f1ddcaae15904fa", size = 777565 }, + { url = "https://files.pythonhosted.org/packages/20/7f/1962dd7c1e3c76c566ecd71223eee4ff544da4df0ee284b402fa28910f23/typed_ast-1.5.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:fc2b8c4e1bc5cd96c1a823a885e6b158f8451cf6f5530e1829390b4d27d0807f", size = 814486 }, + { url = "https://files.pythonhosted.org/packages/c1/16/90c9b889c7fec0a572b93928c33bbda4ade4136a9f3378e1474bf959b6d5/typed_ast-1.5.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:16f7313e0a08c7de57f2998c85e2a69a642e97cb32f87eb65fbfe88381a5e44d", size = 802321 }, + { url = "https://files.pythonhosted.org/packages/39/9c/49a583623b39a755ba50eb8ff8f114b843b01aae3ef50850bce015df1439/typed_ast-1.5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:2b946ef8c04f77230489f75b4b5a4a6f24c078be4aed241cfabe9cbf4156e7e5", size = 160973 }, + { url = "https://files.pythonhosted.org/packages/d5/00/635353c31b71ed307ab020eff6baed9987da59a1b2ba489f885ecbe293b8/typed_ast-1.5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2188bc33d85951ea4ddad55d2b35598b2709d122c11c75cffd529fbc9965508e", size = 222315 }, + { url = "https://files.pythonhosted.org/packages/01/95/11be104446bb20212a741d30d40eab52a9cfc05ea34efa074ff4f7c16983/typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0635900d16ae133cab3b26c607586131269f88266954eb04ec31535c9a12ef1e", size = 793541 }, + { url = "https://files.pythonhosted.org/packages/32/f1/75bd58fb1410cb72fbc6e8adf163015720db2c38844b46a9149c5ff6bf38/typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57bfc3cf35a0f2fdf0a88a3044aafaec1d2f24d8ae8cd87c4f58d615fb5b6311", size = 778348 }, + { url = "https://files.pythonhosted.org/packages/47/97/0bb4dba688a58ff9c08e63b39653e4bcaa340ce1bb9c1d58163e5c2c66f1/typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:fe58ef6a764de7b4b36edfc8592641f56e69b7163bba9f9c8089838ee596bfb2", size = 809447 }, + { url = "https://files.pythonhosted.org/packages/a8/cd/9a867f5a96d83a9742c43914e10d3a2083d8fe894ab9bf60fd467c6c497f/typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d09d930c2d1d621f717bb217bf1fe2584616febb5138d9b3e8cdd26506c3f6d4", size = 796707 }, + { url = "https://files.pythonhosted.org/packages/eb/06/73ca55ee5303b41d08920de775f02d2a3e1e59430371f5adf7fbb1a21127/typed_ast-1.5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:d40c10326893ecab8a80a53039164a224984339b2c32a6baf55ecbd5b1df6431", size = 138403 }, + { url = "https://files.pythonhosted.org/packages/19/e3/88b65e46643006592f39e0fdef3e29454244a9fdaa52acfb047dc68cae6a/typed_ast-1.5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fd946abf3c31fb50eee07451a6aedbfff912fcd13cf357363f5b4e834cc5e71a", size = 222951 }, + { url = "https://files.pythonhosted.org/packages/15/e0/182bdd9edb6c6a1c068cecaa87f58924a817f2807a0b0d940f578b3328df/typed_ast-1.5.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ed4a1a42df8a3dfb6b40c3d2de109e935949f2f66b19703eafade03173f8f437", size = 208247 }, + { url = "https://files.pythonhosted.org/packages/8d/09/bba083f2c11746288eaf1859e512130420405033de84189375fe65d839ba/typed_ast-1.5.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:045f9930a1550d9352464e5149710d56a2aed23a2ffe78946478f7b5416f1ede", size = 861010 }, + { url = "https://files.pythonhosted.org/packages/31/f3/38839df509b04fb54205e388fc04b47627377e0ad628870112086864a441/typed_ast-1.5.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:381eed9c95484ceef5ced626355fdc0765ab51d8553fec08661dce654a935db4", size = 840026 }, + { url = "https://files.pythonhosted.org/packages/45/1e/aa5f1dae4b92bc665ae9a655787bb2fe007a881fa2866b0408ce548bb24c/typed_ast-1.5.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bfd39a41c0ef6f31684daff53befddae608f9daf6957140228a08e51f312d7e6", size = 875615 }, + { url = "https://files.pythonhosted.org/packages/94/88/71a1c249c01fbbd66f9f28648f8249e737a7fe19056c1a78e7b3b9250eb1/typed_ast-1.5.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8c524eb3024edcc04e288db9541fe1f438f82d281e591c548903d5b77ad1ddd4", size = 858320 }, + { url = "https://files.pythonhosted.org/packages/12/1e/19f53aad3984e351e6730e4265fde4b949a66c451e10828fdbc4dfb050f1/typed_ast-1.5.5-cp38-cp38-win_amd64.whl", hash = "sha256:7f58fabdde8dcbe764cef5e1a7fcb440f2463c1bbbec1cf2a86ca7bc1f95184b", size = 139414 }, + { url = "https://files.pythonhosted.org/packages/b1/88/6e7f36f5fab6fbf0586a2dd866ac337924b7d4796a4d1b2b04443a864faf/typed_ast-1.5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:042eb665ff6bf020dd2243307d11ed626306b82812aba21836096d229fdc6a10", size = 223329 }, + { url = "https://files.pythonhosted.org/packages/71/30/09d27e13824495547bcc665bd07afc593b22b9484f143b27565eae4ccaac/typed_ast-1.5.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:622e4a006472b05cf6ef7f9f2636edc51bda670b7bbffa18d26b255269d3d814", size = 208314 }, + { url = "https://files.pythonhosted.org/packages/07/3d/564308b7a432acb1f5399933cbb1b376a1a64d2544b90f6ba91894674260/typed_ast-1.5.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1efebbbf4604ad1283e963e8915daa240cb4bf5067053cf2f0baadc4d4fb51b8", size = 840900 }, + { url = "https://files.pythonhosted.org/packages/ea/f4/262512d14f777ea3666a089e2675a9b1500a85b8329a36de85d63433fb0e/typed_ast-1.5.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0aefdd66f1784c58f65b502b6cf8b121544680456d1cebbd300c2c813899274", size = 823435 }, + { url = "https://files.pythonhosted.org/packages/a1/25/b3ccb948166d309ab75296ac9863ebe2ff209fbc063f1122a2d3979e47c3/typed_ast-1.5.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:48074261a842acf825af1968cd912f6f21357316080ebaca5f19abbb11690c8a", size = 853125 }, + { url = "https://files.pythonhosted.org/packages/1c/09/012da182242f168bb5c42284297dcc08dc0a1b3668db5b3852aec467f56f/typed_ast-1.5.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:429ae404f69dc94b9361bb62291885894b7c6fb4640d561179548c849f8492ba", size = 837280 }, + { url = "https://files.pythonhosted.org/packages/30/bd/c815051404c4293265634d9d3e292f04fcf681d0502a9484c38b8f224d04/typed_ast-1.5.5-cp39-cp39-win_amd64.whl", hash = "sha256:335f22ccb244da2b5c296e6f96b06ee9bed46526db0de38d2f0e5a6597b81155", size = 139486 }, +] + +[[package]] +name = "typing-extensions" +version = "4.1.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.6.8' and python_full_version < '3.7'", + "python_full_version < '3.6.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/5a/8b5fbb891ef3f81fc923bf3cb4a578c0abf9471eb50ce0f51c74212182ab/typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42", size = 26694 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/6b/44f7f8f1e110027cf88956b59f2fad776cca7e1704396d043f89effd3a0e/typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2", size = 26844 }, +] + +[[package]] +name = "typing-extensions" +version = "4.7.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.7.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/8b/0111dd7d6c1478bf83baa1cab85c686426c7a6274119aceb2bd9d35395ad/typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2", size = 72876 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/6b/63cc3df74987c36fe26157ee12e09e8f9db4de771e0f3404263117e75b95/typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36", size = 33232 }, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +] + +[[package]] +name = "urllib3" +version = "1.26.20" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.6.8' and python_full_version < '3.7'", + "python_full_version < '3.6.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/e8/6ff5e6bc22095cfc59b6ea711b687e2b7ed4bdb373f7eeec370a97d7392f/urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32", size = 307380 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/cf/8435d5a7159e2a9c83a95896ed596f68cf798005fe107cc655b5c5c14704/urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e", size = 144225 }, +] + +[[package]] +name = "urllib3" +version = "2.0.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.7.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/af/47/b215df9f71b4fdba1025fc05a77db2ad243fa0926755a52c5e71659f4e3c/urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84", size = 282546 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/b2/b157855192a68541a91ba7b2bbcb91f1b4faa51f8bae38d8005c034be524/urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e", size = 124213 }, +] + +[[package]] +name = "urllib3" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", size = 300677 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338 }, +] + +[[package]] +name = "urllib3" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, +] + +[[package]] +name = "websockify" +version = "0.13.0" +source = { editable = "." } +dependencies = [ + { name = "jwcrypto", version = "1.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "jwcrypto", version = "1.5.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, + { name = "numpy", version = "1.19.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, + { name = "numpy", version = "1.21.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" }, + { name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "numpy", version = "2.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "redis", version = "4.3.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, + { name = "redis", version = "5.0.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" }, + { name = "redis", version = "5.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, + { name = "requests", version = "2.27.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, + { name = "requests", version = "2.31.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" }, + { name = "requests", version = "2.32.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8'" }, +] + +[package.dev-dependencies] +dev = [ + { name = "mypy", version = "0.971", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, + { name = "mypy", version = "1.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.7.*'" }, + { name = "mypy", version = "1.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "mypy", version = "1.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "ruff", version = "0.0.17", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.7'" }, + { name = "ruff", version = "0.9.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.7'" }, +] + +[package.metadata] +requires-dist = [ + { name = "jwcrypto", specifier = ">=1.5.1" }, + { name = "numpy", specifier = ">=1.19.0" }, + { name = "redis", specifier = ">=4.3.0" }, + { name = "requests", specifier = ">=2.27.0" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "mypy", specifier = ">=0.971" }, + { name = "ruff", specifier = ">=0.0.17" }, +] + +[[package]] +name = "wrapt" +version = "1.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/4c/063a912e20bcef7124e0df97282a8af3ff3e4b603ce84c481d6d7346be0a/wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d", size = 53972 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/c6/5375258add3777494671d8cec27cdf5402abd91016dee24aa2972c61fedf/wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4", size = 37315 }, + { url = "https://files.pythonhosted.org/packages/32/12/e11adfde33444986135d8881b401e4de6cbb4cced046edc6b464e6ad7547/wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020", size = 38160 }, + { url = "https://files.pythonhosted.org/packages/70/7d/3dcc4a7e96f8d3e398450ec7703db384413f79bd6c0196e0e139055ce00f/wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440", size = 80419 }, + { url = "https://files.pythonhosted.org/packages/d1/c4/8dfdc3c2f0b38be85c8d9fdf0011ebad2f54e40897f9549a356bebb63a97/wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487", size = 72669 }, + { url = "https://files.pythonhosted.org/packages/49/83/b40bc1ad04a868b5b5bcec86349f06c1ee1ea7afe51dc3e46131e4f39308/wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf", size = 80271 }, + { url = "https://files.pythonhosted.org/packages/19/d4/cd33d3a82df73a064c9b6401d14f346e1d2fb372885f0295516ec08ed2ee/wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72", size = 84748 }, + { url = "https://files.pythonhosted.org/packages/ef/58/2fde309415b5fa98fd8f5f4a11886cbf276824c4c64d45a39da342fff6fe/wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0", size = 77522 }, + { url = "https://files.pythonhosted.org/packages/07/44/359e4724a92369b88dbf09878a7cde7393cf3da885567ea898e5904049a3/wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136", size = 84780 }, + { url = "https://files.pythonhosted.org/packages/88/8f/706f2fee019360cc1da652353330350c76aa5746b4e191082e45d6838faf/wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d", size = 35335 }, + { url = "https://files.pythonhosted.org/packages/19/2b/548d23362e3002ebbfaefe649b833fa43f6ca37ac3e95472130c4b69e0b4/wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2", size = 37528 }, + { url = "https://files.pythonhosted.org/packages/fd/03/c188ac517f402775b90d6f312955a5e53b866c964b32119f2ed76315697e/wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09", size = 37313 }, + { url = "https://files.pythonhosted.org/packages/0f/16/ea627d7817394db04518f62934a5de59874b587b792300991b3c347ff5e0/wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d", size = 38164 }, + { url = "https://files.pythonhosted.org/packages/7f/a7/f1212ba098f3de0fd244e2de0f8791ad2539c03bef6c05a9fcb03e45b089/wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389", size = 80890 }, + { url = "https://files.pythonhosted.org/packages/b7/96/bb5e08b3d6db003c9ab219c487714c13a237ee7dcc572a555eaf1ce7dc82/wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060", size = 73118 }, + { url = "https://files.pythonhosted.org/packages/6e/52/2da48b35193e39ac53cfb141467d9f259851522d0e8c87153f0ba4205fb1/wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1", size = 80746 }, + { url = "https://files.pythonhosted.org/packages/11/fb/18ec40265ab81c0e82a934de04596b6ce972c27ba2592c8b53d5585e6bcd/wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3", size = 85668 }, + { url = "https://files.pythonhosted.org/packages/0f/ef/0ecb1fa23145560431b970418dce575cfaec555ab08617d82eb92afc7ccf/wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956", size = 78556 }, + { url = "https://files.pythonhosted.org/packages/25/62/cd284b2b747f175b5a96cbd8092b32e7369edab0644c45784871528eb852/wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d", size = 85712 }, + { url = "https://files.pythonhosted.org/packages/e5/a7/47b7ff74fbadf81b696872d5ba504966591a3468f1bc86bca2f407baef68/wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362", size = 35327 }, + { url = "https://files.pythonhosted.org/packages/cf/c3/0084351951d9579ae83a3d9e38c140371e4c6b038136909235079f2e6e78/wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89", size = 37523 }, + { url = "https://files.pythonhosted.org/packages/92/17/224132494c1e23521868cdd57cd1e903f3b6a7ba6996b7b8f077ff8ac7fe/wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b", size = 37614 }, + { url = "https://files.pythonhosted.org/packages/6a/d7/cfcd73e8f4858079ac59d9db1ec5a1349bc486ae8e9ba55698cc1f4a1dff/wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36", size = 38316 }, + { url = "https://files.pythonhosted.org/packages/7e/79/5ff0a5c54bda5aec75b36453d06be4f83d5cd4932cc84b7cb2b52cee23e2/wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73", size = 86322 }, + { url = "https://files.pythonhosted.org/packages/c4/81/e799bf5d419f422d8712108837c1d9bf6ebe3cb2a81ad94413449543a923/wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809", size = 79055 }, + { url = "https://files.pythonhosted.org/packages/62/62/30ca2405de6a20448ee557ab2cd61ab9c5900be7cbd18a2639db595f0b98/wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b", size = 87291 }, + { url = "https://files.pythonhosted.org/packages/49/4e/5d2f6d7b57fc9956bf06e944eb00463551f7d52fc73ca35cfc4c2cdb7aed/wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81", size = 90374 }, + { url = "https://files.pythonhosted.org/packages/a6/9b/c2c21b44ff5b9bf14a83252a8b973fb84923764ff63db3e6dfc3895cf2e0/wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9", size = 83896 }, + { url = "https://files.pythonhosted.org/packages/14/26/93a9fa02c6f257df54d7570dfe8011995138118d11939a4ecd82cb849613/wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c", size = 91738 }, + { url = "https://files.pythonhosted.org/packages/a2/5b/4660897233eb2c8c4de3dc7cefed114c61bacb3c28327e64150dc44ee2f6/wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc", size = 35568 }, + { url = "https://files.pythonhosted.org/packages/5c/cc/8297f9658506b224aa4bd71906447dea6bb0ba629861a758c28f67428b91/wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8", size = 37653 }, + { url = "https://files.pythonhosted.org/packages/34/37/e5540d14befd0e62cfed1820b76196b5c39029e63dc9c67630d9fbcf27f4/wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8", size = 36931 }, + { url = "https://files.pythonhosted.org/packages/39/af/1cc9d51588d865395b620b58c6ae18e9a0da105bbcbde719ba39b4d80f02/wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39", size = 77320 }, + { url = "https://files.pythonhosted.org/packages/33/df/6d33cd045919567de125ee86df4ea57077019619d038c1509b4bffdf8bcb/wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c", size = 69795 }, + { url = "https://files.pythonhosted.org/packages/66/50/1c5ccb23dd63f8f3d312dc2d5e0e64c681faf7c2388fa1ab2553713cef11/wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40", size = 77220 }, + { url = "https://files.pythonhosted.org/packages/c5/0f/8245c6167ef25965abfe108400710ef568f84ba53ef3c10873586b75d329/wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc", size = 81627 }, + { url = "https://files.pythonhosted.org/packages/cf/95/cd839cf67a9afd588e09ce8139d6afa8b5c81a8a7be7804744c715c7141e/wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e", size = 74577 }, + { url = "https://files.pythonhosted.org/packages/57/cf/caaec865789ec7ef277fbf65f09128237f41c17774f242023739f3c98709/wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465", size = 81703 }, + { url = "https://files.pythonhosted.org/packages/8e/36/517a47f1b286f97433cf46201745917db5d9944cbb97fb45f55cc71024d0/wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e", size = 35734 }, + { url = "https://files.pythonhosted.org/packages/36/fc/318d240d1360e6e8f975ae35ca8983d8a1d0fe2264ce4fae38db844615ed/wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966", size = 38048 }, + { url = "https://files.pythonhosted.org/packages/47/cf/c2861bc5e0d5f4f277e1cefd7b3f8904794cc58469d35eaa82032a84e1c9/wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593", size = 37069 }, + { url = "https://files.pythonhosted.org/packages/54/39/04409d9fc89f77bce37b98545b6ee7247ad11df28373206536eea078a390/wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292", size = 77587 }, + { url = "https://files.pythonhosted.org/packages/26/dd/1ea7cb367962a6132ab163e7b2d270049e0f471f0238d0e55cfd27219721/wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5", size = 69969 }, + { url = "https://files.pythonhosted.org/packages/7f/46/896369f2550d1ecb5e776f532aada5e77e5e13f821045978cf3d7f3f236b/wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf", size = 77483 }, + { url = "https://files.pythonhosted.org/packages/bf/42/1241b88440ccf8adbf78c81c8899001459102031cc52668cc4e749d9987e/wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228", size = 82832 }, + { url = "https://files.pythonhosted.org/packages/03/60/67dbc0624f1c86cce6150c0b2e13d906009fd6d33128add60a8a2d23137d/wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f", size = 75782 }, + { url = "https://files.pythonhosted.org/packages/8e/5f/574076e289c42e7c1c2abe944fd9dafb5adcb20b36577d4966ddef145539/wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c", size = 82949 }, + { url = "https://files.pythonhosted.org/packages/78/98/6307b4da5080432c5a37b69da92ae0582fd284441025014047e98a002ea1/wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c", size = 35182 }, + { url = "https://files.pythonhosted.org/packages/66/a5/50e6a2bd4cbf6671012771ec35085807a375da5e61540bc5f62de62ba955/wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00", size = 37314 }, + { url = "https://files.pythonhosted.org/packages/fe/9e/d3bc95e75670ba15c5b25ecf07fc49941843e2678d777ca59339348d1c96/wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0", size = 37320 }, + { url = "https://files.pythonhosted.org/packages/72/b5/0c9be75f826c8e8d583a4ab312552d63d9f7c0768710146a22ac59bda4a9/wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202", size = 38163 }, + { url = "https://files.pythonhosted.org/packages/69/21/b2ba809bafc9b6265e359f9c259c6d9a52a16cf6be20c72d95e76da609dd/wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0", size = 83535 }, + { url = "https://files.pythonhosted.org/packages/58/43/d72e625edb5926483c9868214d25b5e7d5858ace6a80c9dfddfbadf4d8f9/wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e", size = 75975 }, + { url = "https://files.pythonhosted.org/packages/ef/c6/56e718e2c58a4078518c14d97e531ef1e9e8a5c1ddafdc0d264a92be1a1a/wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f", size = 83363 }, + { url = "https://files.pythonhosted.org/packages/34/49/589db6fa2d5d428b71716815bca8b39196fdaeea7c247a719ed2f93b0ab4/wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267", size = 87739 }, + { url = "https://files.pythonhosted.org/packages/c5/40/3eabe06c8dc54fada7364f34e8caa562efe3bf3f769bf3258de9c785a27f/wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca", size = 80700 }, + { url = "https://files.pythonhosted.org/packages/15/4e/081f59237b620a124b035f1229f55db40841a9339fdb8ef60b4decc44df9/wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6", size = 87783 }, + { url = "https://files.pythonhosted.org/packages/3a/ad/9d26a33bc80444ff97b937f94611f3b986fd40f735823558dfdf05ef9db8/wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b", size = 35332 }, + { url = "https://files.pythonhosted.org/packages/01/db/4b29ba5f97d2a0aa97ec41eba1036b7c3eaf6e61e1f4639420cec2463a01/wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41", size = 37524 }, + { url = "https://files.pythonhosted.org/packages/70/cc/b92e1da2cad6a9f8ee481000ece07a35e3b24e041e60ff8b850c079f0ebf/wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2", size = 37314 }, + { url = "https://files.pythonhosted.org/packages/4a/cc/3402bcc897978be00fef608cd9e3e39ec8869c973feeb5e1e277670e5ad2/wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb", size = 38162 }, + { url = "https://files.pythonhosted.org/packages/28/d3/4f079f649c515727c127c987b2ec2e0816b80d95784f2d28d1a57d2a1029/wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8", size = 80235 }, + { url = "https://files.pythonhosted.org/packages/a3/1c/226c2a4932e578a2241dcb383f425995f80224b446f439c2e112eb51c3a6/wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c", size = 72553 }, + { url = "https://files.pythonhosted.org/packages/b1/e7/459a8a4f40f2fa65eb73cb3f339e6d152957932516d18d0e996c7ae2d7ae/wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a", size = 80129 }, + { url = "https://files.pythonhosted.org/packages/da/6f/6d0b3c4983f1fc764a422989dabc268ee87d937763246cd48aa92f1eed1e/wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664", size = 84550 }, + { url = "https://files.pythonhosted.org/packages/96/e8/27ef35cf61e5147c1c3abcb89cfbb8d691b2bb8364803fcc950140bc14d8/wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f", size = 77352 }, + { url = "https://files.pythonhosted.org/packages/b6/ad/7a0766341081bfd9f18a7049e4d6d45586ae5c5bb0a640f05e2f558e849c/wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537", size = 84626 }, + { url = "https://files.pythonhosted.org/packages/09/43/b26852e9c45a1aac0d14b1080b25b612fa840ba99739c5fc55db07b7ce08/wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3", size = 35327 }, + { url = "https://files.pythonhosted.org/packages/74/f2/96ed140b08743f7f68d5bda35a2a589600781366c3da96f056043d258b1a/wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35", size = 37526 }, + { url = "https://files.pythonhosted.org/packages/ff/21/abdedb4cdf6ff41ebf01a74087740a709e2edb146490e4d9beea054b0b7a/wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1", size = 23362 }, +] + +[[package]] +name = "zipp" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.6.8' and python_full_version < '3.7'", + "python_full_version < '3.6.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/02/bf/0d03dbdedb83afec081fefe86cae3a2447250ef1a81ac601a9a56e785401/zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832", size = 13047 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/df/d4a4974a3e3957fd1c1fa3082366d7fff6e428ddb55f074bf64876f8e8ad/zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc", size = 5313 }, +] + +[[package]] +name = "zipp" +version = "3.15.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.7.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/00/27/f0ac6b846684cecce1ee93d32450c45ab607f65c2e0255f0092032d91f07/zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b", size = 18454 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/fa/c9e82bbe1af6266adf08afb563905eb87cab83fde00a0a08963510621047/zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556", size = 6758 }, +] From 4fc7e5235229b8bdd499c5cb87ac26d6b3d91bcb Mon Sep 17 00:00:00 2001 From: Doctor Date: Wed, 12 Mar 2025 20:53:31 +0300 Subject: [PATCH 2/3] ci: add lint workflow --- .github/workflows/lint.yaml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/workflows/lint.yaml diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 00000000..2b1fd33e --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,31 @@ +name: Lint +on: [push, pull_request] +jobs: + lint: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: + - 3.12 + job: + - mypy . + - ruff format --check . + - ruff check . + + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + enable-cache: true + + - name: Install dependencies + run: uv sync --all-extras --all-groups + + - run: uv run ${{ matrix.job }} From 3eca8ad195299eab0cb40712f9b6872b6497c0ca Mon Sep 17 00:00:00 2001 From: Doctor Date: Wed, 12 Mar 2025 20:55:57 +0300 Subject: [PATCH 3/3] run `ruff format .` --- setup.py | 80 ++--- tests/echo.py | 45 ++- tests/echo_client.py | 31 +- tests/latency.py | 2 +- tests/load.py | 74 +++-- tests/test_auth_plugins.py | 25 +- tests/test_token_plugins.py | 283 +++++++++------- tests/test_websocket.py | 267 ++++++++++------ tests/test_websocketproxy.py | 29 +- tests/test_websocketserver.py | 3 +- tests/test_websockifyserver.py | 308 +++++++++++------- websockify.py | 2 +- websockify/__main__.py | 2 +- websockify/auth_plugins.py | 42 ++- websockify/sysloghandler.py | 62 ++-- websockify/token_plugins.py | 101 +++--- websockify/websocket.py | 203 +++++++----- websockify/websocketproxy.py | 567 ++++++++++++++++++++------------- websockify/websocketserver.py | 26 +- websockify/websockifyserver.py | 379 ++++++++++++---------- 20 files changed, 1496 insertions(+), 1035 deletions(-) diff --git a/setup.py b/setup.py index 5f8118fb..294fec2d 100644 --- a/setup.py +++ b/setup.py @@ -1,44 +1,44 @@ from setuptools import setup, find_packages -version = '0.13.0' -name = 'websockify' -long_description = open("README.md").read() + "\n" + \ - open("CHANGES.txt").read() + "\n" +version = "0.13.0" +name = "websockify" +long_description = open("README.md").read() + "\n" + open("CHANGES.txt").read() + "\n" -setup(name=name, - version=version, - description="Websockify.", - long_description=long_description, - long_description_content_type="text/markdown", - classifiers=[ - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - ], - keywords='noVNC websockify', - license='LGPLv3', - url="https://github.com/novnc/websockify", - author="Joel Martin", - author_email="github@martintribe.org", - - packages=['websockify'], - include_package_data=True, - install_requires=[ - 'numpy', 'requests', - 'jwcrypto', - 'redis', - ], - zip_safe=False, - entry_points={ - 'console_scripts': [ - 'websockify = websockify.websocketproxy:websockify_init', +setup( + name=name, + version=version, + description="Websockify.", + long_description=long_description, + long_description_content_type="text/markdown", + classifiers=[ + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + ], + keywords="noVNC websockify", + license="LGPLv3", + url="https://github.com/novnc/websockify", + author="Joel Martin", + author_email="github@martintribe.org", + packages=["websockify"], + include_package_data=True, + install_requires=[ + "numpy", + "requests", + "jwcrypto", + "redis", + ], + zip_safe=False, + entry_points={ + "console_scripts": [ + "websockify = websockify.websocketproxy:websockify_init", ] - }, - ) + }, +) diff --git a/tests/echo.py b/tests/echo.py index 780891cd..adcff5a8 100755 --- a/tests/echo.py +++ b/tests/echo.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -''' +""" A WebSocket server that echos back whatever it receives from the client. Copyright 2010 Joel Martin Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3) @@ -8,16 +8,19 @@ You can make a cert/key with openssl using: openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem as taken from http://docs.python.org/dev/library/ssl.html#certificates -''' +""" import os, sys, select, optparse, logging -sys.path.insert(0,os.path.join(os.path.dirname(__file__), "..")) + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) from websockify.websockifyserver import WebSockifyServer, WebSockifyRequestHandler + class WebSocketEcho(WebSockifyRequestHandler): """ WebSockets server that echos back whatever is received from the - client. """ + client.""" + buffer_size = 8096 def new_websocket_client(self): @@ -33,9 +36,11 @@ def new_websocket_client(self): while True: wlist = [] - if cqueue or c_pend: wlist.append(self.request) + if cqueue or c_pend: + wlist.append(self.request) ins, outs, excepts = select.select(rlist, wlist, [], 1) - if excepts: raise Exception("Socket exception") + if excepts: + raise Exception("Socket exception") if self.request in outs: # Send queued target data to the client @@ -50,20 +55,27 @@ def new_websocket_client(self): if closed: break -if __name__ == '__main__': + +if __name__ == "__main__": parser = optparse.OptionParser(usage="%prog [options] listen_port") - parser.add_option("--verbose", "-v", action="store_true", - help="verbose messages and per frame traffic") - parser.add_option("--cert", default="self.pem", - help="SSL certificate file") - parser.add_option("--key", default=None, - help="SSL key file (if separate from cert)") - parser.add_option("--ssl-only", action="store_true", - help="disallow non-encrypted connections") + parser.add_option( + "--verbose", + "-v", + action="store_true", + help="verbose messages and per frame traffic", + ) + parser.add_option("--cert", default="self.pem", help="SSL certificate file") + parser.add_option( + "--key", default=None, help="SSL key file (if separate from cert)" + ) + parser.add_option( + "--ssl-only", action="store_true", help="disallow non-encrypted connections" + ) (opts, args) = parser.parse_args() try: - if len(args) != 1: raise ValueError + if len(args) != 1: + raise ValueError opts.listen_port = int(args[0]) except ValueError: parser.error("Invalid arguments") @@ -73,4 +85,3 @@ def new_websocket_client(self): opts.web = "." server = WebSockifyServer(WebSocketEcho, **opts.__dict__) server.start_server() - diff --git a/tests/echo_client.py b/tests/echo_client.py index 4f238f6f..f5fa9626 100755 --- a/tests/echo_client.py +++ b/tests/echo_client.py @@ -5,9 +5,12 @@ import optparse import select -sys.path.insert(0,os.path.join(os.path.dirname(__file__), "..")) -from websockify.websocket import WebSocket, \ - WebSocketWantReadError, WebSocketWantWriteError +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from websockify.websocket import ( + WebSocket, + WebSocketWantReadError, + WebSocketWantWriteError, +) parser = optparse.OptionParser(usage="%prog URL") (opts, args) = parser.parse_args() @@ -22,19 +25,23 @@ sock.connect(URL) print("Connected.") + def send(msg): while True: try: sock.sendmsg(msg) break except WebSocketWantReadError: - msg = '' + msg = "" ins, outs, excepts = select.select([sock], [], []) - if excepts: raise Exception("Socket exception") + if excepts: + raise Exception("Socket exception") except WebSocketWantWriteError: - msg = '' + msg = "" ins, outs, excepts = select.select([], [sock], []) - if excepts: raise Exception("Socket exception") + if excepts: + raise Exception("Socket exception") + def read(): while True: @@ -42,10 +49,13 @@ def read(): return sock.recvmsg() except WebSocketWantReadError: ins, outs, excepts = select.select([sock], [], []) - if excepts: raise Exception("Socket exception") + if excepts: + raise Exception("Socket exception") except WebSocketWantWriteError: ins, outs, excepts = select.select([], [sock], []) - if excepts: raise Exception("Socket exception") + if excepts: + raise Exception("Socket exception") + counter = 1 while True: @@ -56,7 +66,8 @@ def read(): while True: ins, outs, excepts = select.select([sock], [], [], 1.0) - if excepts: raise Exception("Socket exception") + if excepts: + raise Exception("Socket exception") if ins == []: break diff --git a/tests/latency.py b/tests/latency.py index 3ae4d964..9372c2ae 120000 --- a/tests/latency.py +++ b/tests/latency.py @@ -1 +1 @@ -echo.py \ No newline at end of file +echo.py diff --git a/tests/load.py b/tests/load.py index 710b593b..60dad789 100755 --- a/tests/load.py +++ b/tests/load.py @@ -1,32 +1,32 @@ #!/usr/bin/env python -''' +""" WebSocket server-side load test program. Sends and receives traffic that has a random payload (length and content) that is checksummed and given a sequence number. Any errors are reported and counted. -''' +""" import sys, os, select, random, time, optparse, logging -sys.path.insert(0,os.path.join(os.path.dirname(__file__), "..")) + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) from websockify.websockifyserver import WebSockifyServer, WebSockifyRequestHandler -class WebSocketLoadServer(WebSockifyServer): +class WebSocketLoadServer(WebSockifyServer): recv_cnt = 0 send_cnt = 0 def __init__(self, *args, **kwargs): - self.delay = kwargs.pop('delay') + self.delay = kwargs.pop("delay") WebSockifyServer.__init__(self, *args, **kwargs) class WebSocketLoad(WebSockifyRequestHandler): - max_packet_size = 10000 def new_websocket_client(self): - print "Prepopulating random array" + print("Prepopulating random array") self.rand_array = [] for i in range(0, self.max_packet_size): self.rand_array.append(random.randint(0, 9)) @@ -37,7 +37,7 @@ def new_websocket_client(self): self.responder(self.request) - print "accumulated errors:", self.errors + print("accumulated errors:", self.errors) self.errors = 0 def responder(self, client): @@ -49,7 +49,8 @@ def responder(self, client): while True: ins, outs, excepts = select.select(socks, socks, socks, 1) - if excepts: raise Exception("Socket exception") + if excepts: + raise Exception("Socket exception") if client in ins: frames, closed = self.recv_frames() @@ -57,7 +58,7 @@ def responder(self, client): err = self.check(frames) if err: self.errors = self.errors + 1 - print err + print(err) if closed: break @@ -73,24 +74,22 @@ def responder(self, client): def generate(self): length = random.randint(10, self.max_packet_size) - numlist = self.rand_array[self.max_packet_size-length:] + numlist = self.rand_array[self.max_packet_size - length :] # Error in length - #numlist.append(5) + # numlist.append(5) chksum = sum(numlist) # Error in checksum - #numlist[0] = 5 - nums = "".join( [str(n) for n in numlist] ) + # numlist[0] = 5 + nums = "".join([str(n) for n in numlist]) data = "^%d:%d:%d:%s$" % (self.send_cnt, length, chksum, nums) self.send_cnt += 1 return data - def check(self, frames): - err = "" for data in frames: - if data.count('$') > 1: + if data.count("$") > 1: raise Exception("Multiple parts within single packet") if len(data) == 0: self.traffic("_") @@ -101,12 +100,12 @@ def check(self, frames): continue try: - cnt, length, chksum, nums = data[1:-1].split(':') - cnt = int(cnt) + cnt, length, chksum, nums = data[1:-1].split(":") + cnt = int(cnt) length = int(length) chksum = int(chksum) except ValueError: - print "\n" + repr(data) + "" + print("\n" + repr(data) + "") err += "Invalid data format\n" continue @@ -131,27 +130,37 @@ def check(self, frames): real_chksum += int(num) if real_chksum != chksum: - err += "Expected checksum %d but real chksum is %d\n" % (chksum, real_chksum) + err += "Expected checksum %d but real chksum is %d\n" % ( + chksum, + real_chksum, + ) return err -if __name__ == '__main__': +if __name__ == "__main__": parser = optparse.OptionParser(usage="%prog [options] listen_port") - parser.add_option("--verbose", "-v", action="store_true", - help="verbose messages and per frame traffic") - parser.add_option("--cert", default="self.pem", - help="SSL certificate file") - parser.add_option("--key", default=None, - help="SSL key file (if separate from cert)") - parser.add_option("--ssl-only", action="store_true", - help="disallow non-encrypted connections") + parser.add_option( + "--verbose", + "-v", + action="store_true", + help="verbose messages and per frame traffic", + ) + parser.add_option("--cert", default="self.pem", help="SSL certificate file") + parser.add_option( + "--key", default=None, help="SSL key file (if separate from cert)" + ) + parser.add_option( + "--ssl-only", action="store_true", help="disallow non-encrypted connections" + ) (opts, args) = parser.parse_args() try: - if len(args) != 1: raise ValueError + if len(args) != 1: + raise ValueError opts.listen_port = int(args[0]) - if len(args) not in [1,2]: raise ValueError + if len(args) not in [1, 2]: + raise ValueError opts.listen_port = int(args[0]) if len(args) == 2: opts.delay = int(args[1]) @@ -165,4 +174,3 @@ def check(self, frames): opts.web = "." server = WebSocketLoadServer(WebSocketLoad, **opts.__dict__) server.start_server() - diff --git a/tests/test_auth_plugins.py b/tests/test_auth_plugins.py index 4b3bfb5c..e3d348af 100644 --- a/tests/test_auth_plugins.py +++ b/tests/test_auth_plugins.py @@ -1,28 +1,33 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -""" Unit tests for Authentication plugins""" +"""Unit tests for Authentication plugins""" from websockify.auth_plugins import BasicHTTPAuth, AuthenticationError import unittest class BasicHTTPAuthTestCase(unittest.TestCase): - def setUp(self): - self.plugin = BasicHTTPAuth('Aladdin:open sesame') + self.plugin = BasicHTTPAuth("Aladdin:open sesame") def test_no_auth(self): headers = {} - self.assertRaises(AuthenticationError, self.plugin.authenticate, headers, 'localhost', '1234') + self.assertRaises( + AuthenticationError, self.plugin.authenticate, headers, "localhost", "1234" + ) def test_invalid_password(self): - headers = {'Authorization': 'Basic QWxhZGRpbjpzZXNhbWUgc3RyZWV0'} - self.assertRaises(AuthenticationError, self.plugin.authenticate, headers, 'localhost', '1234') + headers = {"Authorization": "Basic QWxhZGRpbjpzZXNhbWUgc3RyZWV0"} + self.assertRaises( + AuthenticationError, self.plugin.authenticate, headers, "localhost", "1234" + ) def test_valid_password(self): - headers = {'Authorization': 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='} - self.plugin.authenticate(headers, 'localhost', '1234') + headers = {"Authorization": "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="} + self.plugin.authenticate(headers, "localhost", "1234") def test_garbage_auth(self): - headers = {'Authorization': 'Basic xxxxxxxxxxxxxxxxxxxxxxxxxxxx'} - self.assertRaises(AuthenticationError, self.plugin.authenticate, headers, 'localhost', '1234') + headers = {"Authorization": "Basic xxxxxxxxxxxxxxxxxxxxxxxxxxxx"} + self.assertRaises( + AuthenticationError, self.plugin.authenticate, headers, "localhost", "1234" + ) diff --git a/tests/test_token_plugins.py b/tests/test_token_plugins.py index a9fd256d..fae783bb 100644 --- a/tests/test_token_plugins.py +++ b/tests/test_token_plugins.py @@ -1,80 +1,93 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -""" Unit tests for Token plugins""" +"""Unit tests for Token plugins""" import sys import unittest from unittest.mock import patch, mock_open, MagicMock from jwcrypto import jwt, jwk -from websockify.token_plugins import parse_source_args, ReadOnlyTokenFile, JWTTokenApi, TokenRedis +from websockify.token_plugins import ( + parse_source_args, + ReadOnlyTokenFile, + JWTTokenApi, + TokenRedis, +) + class ParseSourceArgumentsTestCase(unittest.TestCase): def test_parameterized(self): params = [ - ('', ['']), - (':', ['', '']), - ('::', ['', '', '']), + ("", [""]), + (":", ["", ""]), + ("::", ["", "", ""]), ('"', ['"']), ('""', ['""']), ('"""', ['"""']), - ('"localhost"', ['localhost']), - ('"localhost":', ['localhost', '']), - ('"localhost"::', ['localhost', '', '']), - ('"local:host"', ['local:host']), - ('"local:host:"pass"', ['"local', 'host', "pass"]), - ('"local":"host"', ['local', 'host']), - ('"local":host"', ['local', 'host"']), - ('localhost:6379:1:pass"word:"my-app-namespace:dev"', - ['localhost', '6379', '1', 'pass"word', 'my-app-namespace:dev']), + ('"localhost"', ["localhost"]), + ('"localhost":', ["localhost", ""]), + ('"localhost"::', ["localhost", "", ""]), + ('"local:host"', ["local:host"]), + ('"local:host:"pass"', ['"local', "host", "pass"]), + ('"local":"host"', ["local", "host"]), + ('"local":host"', ["local", 'host"']), + ( + 'localhost:6379:1:pass"word:"my-app-namespace:dev"', + ["localhost", "6379", "1", 'pass"word', "my-app-namespace:dev"], + ), ] for src, args in params: self.assertEqual(args, parse_source_args(src)) + class ReadOnlyTokenFileTestCase(unittest.TestCase): - patch('os.path.isdir', MagicMock(return_value=False)) + patch("os.path.isdir", MagicMock(return_value=False)) + def test_empty(self): - plugin = ReadOnlyTokenFile('configfile') + plugin = ReadOnlyTokenFile("configfile") config = "" pyopen = mock_open(read_data=config) with patch("websockify.token_plugins.open", pyopen, create=True): - result = plugin.lookup('testhost') + result = plugin.lookup("testhost") - pyopen.assert_called_once_with('configfile') + pyopen.assert_called_once_with("configfile") self.assertIsNone(result) - patch('os.path.isdir', MagicMock(return_value=False)) + patch("os.path.isdir", MagicMock(return_value=False)) + def test_simple(self): - plugin = ReadOnlyTokenFile('configfile') + plugin = ReadOnlyTokenFile("configfile") config = "testhost: remote_host:remote_port" pyopen = mock_open(read_data=config) with patch("websockify.token_plugins.open", pyopen, create=True): - result = plugin.lookup('testhost') + result = plugin.lookup("testhost") - pyopen.assert_called_once_with('configfile') + pyopen.assert_called_once_with("configfile") self.assertIsNotNone(result) self.assertEqual(result[0], "remote_host") self.assertEqual(result[1], "remote_port") - patch('os.path.isdir', MagicMock(return_value=False)) + patch("os.path.isdir", MagicMock(return_value=False)) + def test_tabs(self): - plugin = ReadOnlyTokenFile('configfile') + plugin = ReadOnlyTokenFile("configfile") config = "testhost:\tremote_host:remote_port" pyopen = mock_open(read_data=config) with patch("websockify.token_plugins.open", pyopen, create=True): - result = plugin.lookup('testhost') + result = plugin.lookup("testhost") - pyopen.assert_called_once_with('configfile') + pyopen.assert_called_once_with("configfile") self.assertIsNotNone(result) self.assertEqual(result[0], "remote_host") self.assertEqual(result[1], "remote_port") + class JWSTokenTestCase(unittest.TestCase): def test_asymmetric_jws_token_plugin(self): plugin = JWTTokenApi("./tests/fixtures/public.pem") @@ -82,7 +95,9 @@ def test_asymmetric_jws_token_plugin(self): key = jwk.JWK() private_key = open("./tests/fixtures/private.pem", "rb").read() key.import_from_pem(private_key) - jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port"}) + jwt_token = jwt.JWT( + {"alg": "RS256"}, {"host": "remote_host", "port": "remote_port"} + ) jwt_token.make_signed_token(key) result = plugin.lookup(jwt_token.serialize()) @@ -97,21 +112,26 @@ def test_asymmetric_jws_token_plugin_with_illigal_key_exception(self): key = jwk.JWK() private_key = open("./tests/fixtures/private.pem", "rb").read() key.import_from_pem(private_key) - jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port"}) + jwt_token = jwt.JWT( + {"alg": "RS256"}, {"host": "remote_host", "port": "remote_port"} + ) jwt_token.make_signed_token(key) result = plugin.lookup(jwt_token.serialize()) self.assertIsNone(result) - @patch('time.time') + @patch("time.time") def test_jwt_valid_time(self, mock_time): plugin = JWTTokenApi("./tests/fixtures/public.pem") key = jwk.JWK() private_key = open("./tests/fixtures/private.pem", "rb").read() key.import_from_pem(private_key) - jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port", 'nbf': 100, 'exp': 200 }) + jwt_token = jwt.JWT( + {"alg": "RS256"}, + {"host": "remote_host", "port": "remote_port", "nbf": 100, "exp": 200}, + ) jwt_token.make_signed_token(key) mock_time.return_value = 150 @@ -121,14 +141,17 @@ def test_jwt_valid_time(self, mock_time): self.assertEqual(result[0], "remote_host") self.assertEqual(result[1], "remote_port") - @patch('time.time') + @patch("time.time") def test_jwt_early_time(self, mock_time): plugin = JWTTokenApi("./tests/fixtures/public.pem") key = jwk.JWK() private_key = open("./tests/fixtures/private.pem", "rb").read() key.import_from_pem(private_key) - jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port", 'nbf': 100, 'exp': 200 }) + jwt_token = jwt.JWT( + {"alg": "RS256"}, + {"host": "remote_host", "port": "remote_port", "nbf": 100, "exp": 200}, + ) jwt_token.make_signed_token(key) mock_time.return_value = 50 @@ -136,14 +159,17 @@ def test_jwt_early_time(self, mock_time): self.assertIsNone(result) - @patch('time.time') + @patch("time.time") def test_jwt_late_time(self, mock_time): plugin = JWTTokenApi("./tests/fixtures/public.pem") key = jwk.JWK() private_key = open("./tests/fixtures/private.pem", "rb").read() key.import_from_pem(private_key) - jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port", 'nbf': 100, 'exp': 200 }) + jwt_token = jwt.JWT( + {"alg": "RS256"}, + {"host": "remote_host", "port": "remote_port", "nbf": 100, "exp": 200}, + ) jwt_token.make_signed_token(key) mock_time.return_value = 250 @@ -156,8 +182,10 @@ def test_symmetric_jws_token_plugin(self): secret = open("./tests/fixtures/symmetric.key").read() key = jwk.JWK() - key.import_key(kty="oct",k=secret) - jwt_token = jwt.JWT({"alg": "HS256"}, {'host': "remote_host", 'port': "remote_port"}) + key.import_key(kty="oct", k=secret) + jwt_token = jwt.JWT( + {"alg": "HS256"}, {"host": "remote_host", "port": "remote_port"} + ) jwt_token.make_signed_token(key) result = plugin.lookup(jwt_token.serialize()) @@ -171,8 +199,10 @@ def test_symmetric_jws_token_plugin_with_illigal_key_exception(self): secret = open("./tests/fixtures/symmetric.key").read() key = jwk.JWK() - key.import_key(kty="oct",k=secret) - jwt_token = jwt.JWT({"alg": "HS256"}, {'host': "remote_host", 'port': "remote_port"}) + key.import_key(kty="oct", k=secret) + jwt_token = jwt.JWT( + {"alg": "HS256"}, {"host": "remote_host", "port": "remote_port"} + ) jwt_token.make_signed_token(key) result = plugin.lookup(jwt_token.serialize()) @@ -188,10 +218,14 @@ def test_asymmetric_jwe_token_plugin(self): public_key_data = open("./tests/fixtures/public.pem", "rb").read() private_key.import_from_pem(private_key_data) public_key.import_from_pem(public_key_data) - jwt_token = jwt.JWT({"alg": "RS256"}, {'host': "remote_host", 'port': "remote_port"}) + jwt_token = jwt.JWT( + {"alg": "RS256"}, {"host": "remote_host", "port": "remote_port"} + ) jwt_token.make_signed_token(private_key) - jwe_token = jwt.JWT(header={"alg": "RSA-OAEP", "enc": "A256CBC-HS512"}, - claims=jwt_token.serialize()) + jwe_token = jwt.JWT( + header={"alg": "RSA-OAEP", "enc": "A256CBC-HS512"}, + claims=jwt_token.serialize(), + ) jwe_token.make_encrypted_token(public_key) result = plugin.lookup(jwt_token.serialize()) @@ -200,103 +234,104 @@ def test_asymmetric_jwe_token_plugin(self): self.assertEqual(result[0], "remote_host") self.assertEqual(result[1], "remote_port") + class TokenRedisTestCase(unittest.TestCase): def setUp(self): try: import redis except ImportError: - patcher = patch.dict(sys.modules, {'redis': MagicMock()}) + patcher = patch.dict(sys.modules, {"redis": MagicMock()}) patcher.start() self.addCleanup(patcher.stop) - @patch('redis.Redis') + @patch("redis.Redis") def test_empty(self, mock_redis): - plugin = TokenRedis('127.0.0.1:1234') + plugin = TokenRedis("127.0.0.1:1234") instance = mock_redis.return_value instance.get.return_value = None - result = plugin.lookup('testhost') + result = plugin.lookup("testhost") - instance.get.assert_called_once_with('testhost') + instance.get.assert_called_once_with("testhost") self.assertIsNone(result) - @patch('redis.Redis') + @patch("redis.Redis") def test_simple(self, mock_redis): - plugin = TokenRedis('127.0.0.1:1234') + plugin = TokenRedis("127.0.0.1:1234") instance = mock_redis.return_value instance.get.return_value = b'{"host": "remote_host:remote_port"}' - result = plugin.lookup('testhost') + result = plugin.lookup("testhost") - instance.get.assert_called_once_with('testhost') + instance.get.assert_called_once_with("testhost") self.assertIsNotNone(result) - self.assertEqual(result[0], 'remote_host') - self.assertEqual(result[1], 'remote_port') + self.assertEqual(result[0], "remote_host") + self.assertEqual(result[1], "remote_port") - @patch('redis.Redis') + @patch("redis.Redis") def test_json_token_with_spaces(self, mock_redis): - plugin = TokenRedis('127.0.0.1:1234') + plugin = TokenRedis("127.0.0.1:1234") instance = mock_redis.return_value instance.get.return_value = b' {"host": "remote_host:remote_port"} ' - result = plugin.lookup('testhost') + result = plugin.lookup("testhost") - instance.get.assert_called_once_with('testhost') + instance.get.assert_called_once_with("testhost") self.assertIsNotNone(result) - self.assertEqual(result[0], 'remote_host') - self.assertEqual(result[1], 'remote_port') + self.assertEqual(result[0], "remote_host") + self.assertEqual(result[1], "remote_port") - @patch('redis.Redis') + @patch("redis.Redis") def test_text_token(self, mock_redis): - plugin = TokenRedis('127.0.0.1:1234') + plugin = TokenRedis("127.0.0.1:1234") instance = mock_redis.return_value - instance.get.return_value = b'remote_host:remote_port' + instance.get.return_value = b"remote_host:remote_port" - result = plugin.lookup('testhost') + result = plugin.lookup("testhost") - instance.get.assert_called_once_with('testhost') + instance.get.assert_called_once_with("testhost") self.assertIsNotNone(result) - self.assertEqual(result[0], 'remote_host') - self.assertEqual(result[1], 'remote_port') + self.assertEqual(result[0], "remote_host") + self.assertEqual(result[1], "remote_port") - @patch('redis.Redis') + @patch("redis.Redis") def test_text_token_with_spaces(self, mock_redis): - plugin = TokenRedis('127.0.0.1:1234') + plugin = TokenRedis("127.0.0.1:1234") instance = mock_redis.return_value - instance.get.return_value = b' remote_host:remote_port ' + instance.get.return_value = b" remote_host:remote_port " - result = plugin.lookup('testhost') + result = plugin.lookup("testhost") - instance.get.assert_called_once_with('testhost') + instance.get.assert_called_once_with("testhost") self.assertIsNotNone(result) - self.assertEqual(result[0], 'remote_host') - self.assertEqual(result[1], 'remote_port') + self.assertEqual(result[0], "remote_host") + self.assertEqual(result[1], "remote_port") - @patch('redis.Redis') + @patch("redis.Redis") def test_invalid_token(self, mock_redis): - plugin = TokenRedis('127.0.0.1:1234') + plugin = TokenRedis("127.0.0.1:1234") instance = mock_redis.return_value instance.get.return_value = b'{"host": "remote_host:remote_port" ' - result = plugin.lookup('testhost') + result = plugin.lookup("testhost") - instance.get.assert_called_once_with('testhost') + instance.get.assert_called_once_with("testhost") self.assertIsNone(result) - @patch('redis.Redis') + @patch("redis.Redis") def test_token_without_namespace(self, mock_redis): - plugin = TokenRedis('127.0.0.1:1234') - token = 'testhost' + plugin = TokenRedis("127.0.0.1:1234") + token = "testhost" def mock_redis_get(key): self.assertEqual(key, token) - return b'remote_host:remote_port' + return b"remote_host:remote_port" instance = mock_redis.return_value instance.get = mock_redis_get @@ -304,17 +339,17 @@ def mock_redis_get(key): result = plugin.lookup(token) self.assertIsNotNone(result) - self.assertEqual(result[0], 'remote_host') - self.assertEqual(result[1], 'remote_port') + self.assertEqual(result[0], "remote_host") + self.assertEqual(result[1], "remote_port") - @patch('redis.Redis') + @patch("redis.Redis") def test_token_with_namespace(self, mock_redis): - plugin = TokenRedis('127.0.0.1:1234:::namespace') - token = 'testhost' + plugin = TokenRedis("127.0.0.1:1234:::namespace") + token = "testhost" def mock_redis_get(key): self.assertEqual(key, "namespace:" + token) - return b'remote_host:remote_port' + return b"remote_host:remote_port" instance = mock_redis.return_value instance.get = mock_redis_get @@ -322,103 +357,103 @@ def mock_redis_get(key): result = plugin.lookup(token) self.assertIsNotNone(result) - self.assertEqual(result[0], 'remote_host') - self.assertEqual(result[1], 'remote_port') + self.assertEqual(result[0], "remote_host") + self.assertEqual(result[1], "remote_port") def test_src_only_host(self): - plugin = TokenRedis('127.0.0.1') + plugin = TokenRedis("127.0.0.1") - self.assertEqual(plugin._server, '127.0.0.1') + self.assertEqual(plugin._server, "127.0.0.1") self.assertEqual(plugin._port, 6379) self.assertEqual(plugin._db, 0) self.assertEqual(plugin._password, None) self.assertEqual(plugin._namespace, "") def test_src_with_host_port(self): - plugin = TokenRedis('127.0.0.1:1234') + plugin = TokenRedis("127.0.0.1:1234") - self.assertEqual(plugin._server, '127.0.0.1') + self.assertEqual(plugin._server, "127.0.0.1") self.assertEqual(plugin._port, 1234) self.assertEqual(plugin._db, 0) self.assertEqual(plugin._password, None) self.assertEqual(plugin._namespace, "") def test_src_with_host_port_db(self): - plugin = TokenRedis('127.0.0.1:1234:2') + plugin = TokenRedis("127.0.0.1:1234:2") - self.assertEqual(plugin._server, '127.0.0.1') + self.assertEqual(plugin._server, "127.0.0.1") self.assertEqual(plugin._port, 1234) self.assertEqual(plugin._db, 2) self.assertEqual(plugin._password, None) self.assertEqual(plugin._namespace, "") def test_src_with_host_port_db_pass(self): - plugin = TokenRedis('127.0.0.1:1234:2:verysecret') + plugin = TokenRedis("127.0.0.1:1234:2:verysecret") - self.assertEqual(plugin._server, '127.0.0.1') + self.assertEqual(plugin._server, "127.0.0.1") self.assertEqual(plugin._port, 1234) self.assertEqual(plugin._db, 2) - self.assertEqual(plugin._password, 'verysecret') + self.assertEqual(plugin._password, "verysecret") self.assertEqual(plugin._namespace, "") def test_src_with_host_port_db_pass_namespace(self): - plugin = TokenRedis('127.0.0.1:1234:2:verysecret:namespace') + plugin = TokenRedis("127.0.0.1:1234:2:verysecret:namespace") - self.assertEqual(plugin._server, '127.0.0.1') + self.assertEqual(plugin._server, "127.0.0.1") self.assertEqual(plugin._port, 1234) self.assertEqual(plugin._db, 2) - self.assertEqual(plugin._password, 'verysecret') + self.assertEqual(plugin._password, "verysecret") self.assertEqual(plugin._namespace, "namespace:") def test_src_with_host_empty_port_empty_db_pass_no_namespace(self): - plugin = TokenRedis('127.0.0.1:::verysecret') + plugin = TokenRedis("127.0.0.1:::verysecret") - self.assertEqual(plugin._server, '127.0.0.1') + self.assertEqual(plugin._server, "127.0.0.1") self.assertEqual(plugin._port, 6379) self.assertEqual(plugin._db, 0) - self.assertEqual(plugin._password, 'verysecret') + self.assertEqual(plugin._password, "verysecret") self.assertEqual(plugin._namespace, "") def test_src_with_host_empty_port_empty_db_empty_pass_empty_namespace(self): - plugin = TokenRedis('127.0.0.1::::') + plugin = TokenRedis("127.0.0.1::::") - self.assertEqual(plugin._server, '127.0.0.1') + self.assertEqual(plugin._server, "127.0.0.1") self.assertEqual(plugin._port, 6379) self.assertEqual(plugin._db, 0) self.assertEqual(plugin._password, None) self.assertEqual(plugin._namespace, "") def test_src_with_host_empty_port_empty_db_empty_pass_no_namespace(self): - plugin = TokenRedis('127.0.0.1:::') + plugin = TokenRedis("127.0.0.1:::") - self.assertEqual(plugin._server, '127.0.0.1') + self.assertEqual(plugin._server, "127.0.0.1") self.assertEqual(plugin._port, 6379) self.assertEqual(plugin._db, 0) self.assertEqual(plugin._password, None) self.assertEqual(plugin._namespace, "") def test_src_with_host_empty_port_empty_db_no_pass_no_namespace(self): - plugin = TokenRedis('127.0.0.1::') + plugin = TokenRedis("127.0.0.1::") - self.assertEqual(plugin._server, '127.0.0.1') + self.assertEqual(plugin._server, "127.0.0.1") self.assertEqual(plugin._port, 6379) self.assertEqual(plugin._db, 0) self.assertEqual(plugin._password, None) self.assertEqual(plugin._namespace, "") def test_src_with_host_empty_port_no_db_no_pass_no_namespace(self): - plugin = TokenRedis('127.0.0.1:') + plugin = TokenRedis("127.0.0.1:") - self.assertEqual(plugin._server, '127.0.0.1') + self.assertEqual(plugin._server, "127.0.0.1") self.assertEqual(plugin._port, 6379) self.assertEqual(plugin._db, 0) self.assertEqual(plugin._password, None) self.assertEqual(plugin._namespace, "") def test_src_with_host_empty_port_empty_db_empty_pass_namespace(self): - plugin = TokenRedis('127.0.0.1::::namespace') + plugin = TokenRedis("127.0.0.1::::namespace") - self.assertEqual(plugin._server, '127.0.0.1') + self.assertEqual(plugin._server, "127.0.0.1") self.assertEqual(plugin._port, 6379) self.assertEqual(plugin._db, 0) self.assertEqual(plugin._password, None) @@ -427,43 +462,43 @@ def test_src_with_host_empty_port_empty_db_empty_pass_namespace(self): def test_src_with_host_empty_port_empty_db_empty_pass_nested_namespace(self): plugin = TokenRedis('127.0.0.1::::"ns1:ns2"') - self.assertEqual(plugin._server, '127.0.0.1') + self.assertEqual(plugin._server, "127.0.0.1") self.assertEqual(plugin._port, 6379) self.assertEqual(plugin._db, 0) self.assertEqual(plugin._password, None) self.assertEqual(plugin._namespace, "ns1:ns2:") def test_src_with_host_empty_port_db_no_pass_no_namespace(self): - plugin = TokenRedis('127.0.0.1::2') + plugin = TokenRedis("127.0.0.1::2") - self.assertEqual(plugin._server, '127.0.0.1') + self.assertEqual(plugin._server, "127.0.0.1") self.assertEqual(plugin._port, 6379) self.assertEqual(plugin._db, 2) self.assertEqual(plugin._password, None) self.assertEqual(plugin._namespace, "") def test_src_with_host_port_empty_db_pass_no_namespace(self): - plugin = TokenRedis('127.0.0.1:1234::verysecret') + plugin = TokenRedis("127.0.0.1:1234::verysecret") - self.assertEqual(plugin._server, '127.0.0.1') + self.assertEqual(plugin._server, "127.0.0.1") self.assertEqual(plugin._port, 1234) self.assertEqual(plugin._db, 0) - self.assertEqual(plugin._password, 'verysecret') + self.assertEqual(plugin._password, "verysecret") self.assertEqual(plugin._namespace, "") def test_src_with_host_empty_port_db_pass_no_namespace(self): - plugin = TokenRedis('127.0.0.1::2:verysecret') + plugin = TokenRedis("127.0.0.1::2:verysecret") - self.assertEqual(plugin._server, '127.0.0.1') + self.assertEqual(plugin._server, "127.0.0.1") self.assertEqual(plugin._port, 6379) self.assertEqual(plugin._db, 2) - self.assertEqual(plugin._password, 'verysecret') + self.assertEqual(plugin._password, "verysecret") self.assertEqual(plugin._namespace, "") def test_src_with_host_empty_port_db_empty_pass_no_namespace(self): - plugin = TokenRedis('127.0.0.1::2:') + plugin = TokenRedis("127.0.0.1::2:") - self.assertEqual(plugin._server, '127.0.0.1') + self.assertEqual(plugin._server, "127.0.0.1") self.assertEqual(plugin._port, 6379) self.assertEqual(plugin._db, 2) self.assertEqual(plugin._password, None) diff --git a/tests/test_websocket.py b/tests/test_websocket.py index 8ee44f96..4570646a 100644 --- a/tests/test_websocket.py +++ b/tests/test_websocket.py @@ -14,199 +14,268 @@ # License for the specific language governing permissions and limitations # under the License. -""" Unit tests for websocket """ +"""Unit tests for websocket""" + import unittest from websockify import websocket + class FakeSocket: def __init__(self): - self.data = b'' + self.data = b"" def send(self, buf): self.data += buf return len(buf) + class AcceptTestCase(unittest.TestCase): def test_success(self): ws = websocket.WebSocket() sock = FakeSocket() - ws.accept(sock, {'upgrade': 'websocket', - 'Sec-WebSocket-Version': '13', - 'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q=='}) - self.assertEqual(sock.data[:13], b'HTTP/1.1 101 ') - self.assertTrue(b'\r\nUpgrade: websocket\r\n' in sock.data) - self.assertTrue(b'\r\nConnection: Upgrade\r\n' in sock.data) - self.assertTrue(b'\r\nSec-WebSocket-Accept: pczpYSQsvE1vBpTQYjFQPcuoj6M=\r\n' in sock.data) + ws.accept( + sock, + { + "upgrade": "websocket", + "Sec-WebSocket-Version": "13", + "Sec-WebSocket-Key": "DKURYVK9cRFul1vOZVA56Q==", + }, + ) + self.assertEqual(sock.data[:13], b"HTTP/1.1 101 ") + self.assertTrue(b"\r\nUpgrade: websocket\r\n" in sock.data) + self.assertTrue(b"\r\nConnection: Upgrade\r\n" in sock.data) + self.assertTrue( + b"\r\nSec-WebSocket-Accept: pczpYSQsvE1vBpTQYjFQPcuoj6M=\r\n" in sock.data + ) def test_bad_version(self): ws = websocket.WebSocket() sock = FakeSocket() - self.assertRaises(Exception, ws.accept, - sock, {'upgrade': 'websocket', - 'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q=='}) - self.assertRaises(Exception, ws.accept, - sock, {'upgrade': 'websocket', - 'Sec-WebSocket-Version': '5', - 'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q=='}) - self.assertRaises(Exception, ws.accept, - sock, {'upgrade': 'websocket', - 'Sec-WebSocket-Version': '20', - 'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q=='}) + self.assertRaises( + Exception, + ws.accept, + sock, + {"upgrade": "websocket", "Sec-WebSocket-Key": "DKURYVK9cRFul1vOZVA56Q=="}, + ) + self.assertRaises( + Exception, + ws.accept, + sock, + { + "upgrade": "websocket", + "Sec-WebSocket-Version": "5", + "Sec-WebSocket-Key": "DKURYVK9cRFul1vOZVA56Q==", + }, + ) + self.assertRaises( + Exception, + ws.accept, + sock, + { + "upgrade": "websocket", + "Sec-WebSocket-Version": "20", + "Sec-WebSocket-Key": "DKURYVK9cRFul1vOZVA56Q==", + }, + ) def test_bad_upgrade(self): ws = websocket.WebSocket() sock = FakeSocket() - self.assertRaises(Exception, ws.accept, - sock, {'Sec-WebSocket-Version': '13', - 'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q=='}) - self.assertRaises(Exception, ws.accept, - sock, {'upgrade': 'websocket2', - 'Sec-WebSocket-Version': '13', - 'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q=='}) + self.assertRaises( + Exception, + ws.accept, + sock, + { + "Sec-WebSocket-Version": "13", + "Sec-WebSocket-Key": "DKURYVK9cRFul1vOZVA56Q==", + }, + ) + self.assertRaises( + Exception, + ws.accept, + sock, + { + "upgrade": "websocket2", + "Sec-WebSocket-Version": "13", + "Sec-WebSocket-Key": "DKURYVK9cRFul1vOZVA56Q==", + }, + ) def test_missing_key(self): ws = websocket.WebSocket() sock = FakeSocket() - self.assertRaises(Exception, ws.accept, - sock, {'upgrade': 'websocket', - 'Sec-WebSocket-Version': '13'}) + self.assertRaises( + Exception, + ws.accept, + sock, + {"upgrade": "websocket", "Sec-WebSocket-Version": "13"}, + ) def test_protocol(self): class ProtoSocket(websocket.WebSocket): def select_subprotocol(self, protocol): - return 'gazonk' + return "gazonk" ws = ProtoSocket() sock = FakeSocket() - ws.accept(sock, {'upgrade': 'websocket', - 'Sec-WebSocket-Version': '13', - 'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q==', - 'Sec-WebSocket-Protocol': 'foobar gazonk'}) - self.assertEqual(sock.data[:13], b'HTTP/1.1 101 ') - self.assertTrue(b'\r\nSec-WebSocket-Protocol: gazonk\r\n' in sock.data) + ws.accept( + sock, + { + "upgrade": "websocket", + "Sec-WebSocket-Version": "13", + "Sec-WebSocket-Key": "DKURYVK9cRFul1vOZVA56Q==", + "Sec-WebSocket-Protocol": "foobar gazonk", + }, + ) + self.assertEqual(sock.data[:13], b"HTTP/1.1 101 ") + self.assertTrue(b"\r\nSec-WebSocket-Protocol: gazonk\r\n" in sock.data) def test_no_protocol(self): ws = websocket.WebSocket() sock = FakeSocket() - ws.accept(sock, {'upgrade': 'websocket', - 'Sec-WebSocket-Version': '13', - 'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q=='}) - self.assertEqual(sock.data[:13], b'HTTP/1.1 101 ') - self.assertFalse(b'\r\nSec-WebSocket-Protocol:' in sock.data) + ws.accept( + sock, + { + "upgrade": "websocket", + "Sec-WebSocket-Version": "13", + "Sec-WebSocket-Key": "DKURYVK9cRFul1vOZVA56Q==", + }, + ) + self.assertEqual(sock.data[:13], b"HTTP/1.1 101 ") + self.assertFalse(b"\r\nSec-WebSocket-Protocol:" in sock.data) def test_missing_protocol(self): ws = websocket.WebSocket() sock = FakeSocket() - self.assertRaises(Exception, ws.accept, - sock, {'upgrade': 'websocket', - 'Sec-WebSocket-Version': '13', - 'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q==', - 'Sec-WebSocket-Protocol': 'foobar gazonk'}) + self.assertRaises( + Exception, + ws.accept, + sock, + { + "upgrade": "websocket", + "Sec-WebSocket-Version": "13", + "Sec-WebSocket-Key": "DKURYVK9cRFul1vOZVA56Q==", + "Sec-WebSocket-Protocol": "foobar gazonk", + }, + ) def test_protocol(self): class ProtoSocket(websocket.WebSocket): def select_subprotocol(self, protocol): - return 'oddball' + return "oddball" ws = ProtoSocket() sock = FakeSocket() - self.assertRaises(Exception, ws.accept, - sock, {'upgrade': 'websocket', - 'Sec-WebSocket-Version': '13', - 'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q==', - 'Sec-WebSocket-Protocol': 'foobar gazonk'}) + self.assertRaises( + Exception, + ws.accept, + sock, + { + "upgrade": "websocket", + "Sec-WebSocket-Version": "13", + "Sec-WebSocket-Key": "DKURYVK9cRFul1vOZVA56Q==", + "Sec-WebSocket-Protocol": "foobar gazonk", + }, + ) + class PingPongTest(unittest.TestCase): def setUp(self): self.ws = websocket.WebSocket() self.sock = FakeSocket() - self.ws.accept(self.sock, {'upgrade': 'websocket', - 'Sec-WebSocket-Version': '13', - 'Sec-WebSocket-Key': 'DKURYVK9cRFul1vOZVA56Q=='}) - self.assertEqual(self.sock.data[:13], b'HTTP/1.1 101 ') - self.sock.data = b'' + self.ws.accept( + self.sock, + { + "upgrade": "websocket", + "Sec-WebSocket-Version": "13", + "Sec-WebSocket-Key": "DKURYVK9cRFul1vOZVA56Q==", + }, + ) + self.assertEqual(self.sock.data[:13], b"HTTP/1.1 101 ") + self.sock.data = b"" def test_ping(self): self.ws.ping() - self.assertEqual(self.sock.data, b'\x89\x00') + self.assertEqual(self.sock.data, b"\x89\x00") def test_pong(self): self.ws.pong() - self.assertEqual(self.sock.data, b'\x8a\x00') + self.assertEqual(self.sock.data, b"\x8a\x00") def test_ping_data(self): - self.ws.ping(b'foo') - self.assertEqual(self.sock.data, b'\x89\x03foo') + self.ws.ping(b"foo") + self.assertEqual(self.sock.data, b"\x89\x03foo") def test_pong_data(self): - self.ws.pong(b'foo') - self.assertEqual(self.sock.data, b'\x8a\x03foo') + self.ws.pong(b"foo") + self.assertEqual(self.sock.data, b"\x8a\x03foo") + class HyBiEncodeDecodeTestCase(unittest.TestCase): def test_decode_hybi_text(self): - buf = b'\x81\x85\x37\xfa\x21\x3d\x7f\x9f\x4d\x51\x58' + buf = b"\x81\x85\x37\xfa\x21\x3d\x7f\x9f\x4d\x51\x58" ws = websocket.WebSocket() res = ws._decode_hybi(buf) - self.assertEqual(res['fin'], 1) - self.assertEqual(res['opcode'], 0x1) - self.assertEqual(res['masked'], True) - self.assertEqual(res['length'], len(buf)) - self.assertEqual(res['payload'], b'Hello') + self.assertEqual(res["fin"], 1) + self.assertEqual(res["opcode"], 0x1) + self.assertEqual(res["masked"], True) + self.assertEqual(res["length"], len(buf)) + self.assertEqual(res["payload"], b"Hello") def test_decode_hybi_binary(self): - buf = b'\x82\x04\x01\x02\x03\x04' + buf = b"\x82\x04\x01\x02\x03\x04" ws = websocket.WebSocket() res = ws._decode_hybi(buf) - self.assertEqual(res['fin'], 1) - self.assertEqual(res['opcode'], 0x2) - self.assertEqual(res['length'], len(buf)) - self.assertEqual(res['payload'], b'\x01\x02\x03\x04') + self.assertEqual(res["fin"], 1) + self.assertEqual(res["opcode"], 0x2) + self.assertEqual(res["length"], len(buf)) + self.assertEqual(res["payload"], b"\x01\x02\x03\x04") def test_decode_hybi_extended_16bit_binary(self): - data = (b'\x01\x02\x03\x04' * 65) # len > 126 -- len == 260 - buf = b'\x82\x7e\x01\x04' + data + data = b"\x01\x02\x03\x04" * 65 # len > 126 -- len == 260 + buf = b"\x82\x7e\x01\x04" + data ws = websocket.WebSocket() res = ws._decode_hybi(buf) - self.assertEqual(res['fin'], 1) - self.assertEqual(res['opcode'], 0x2) - self.assertEqual(res['length'], len(buf)) - self.assertEqual(res['payload'], data) + self.assertEqual(res["fin"], 1) + self.assertEqual(res["opcode"], 0x2) + self.assertEqual(res["length"], len(buf)) + self.assertEqual(res["payload"], data) def test_decode_hybi_extended_64bit_binary(self): - data = (b'\x01\x02\x03\x04' * 65) # len > 126 -- len == 260 - buf = b'\x82\x7f\x00\x00\x00\x00\x00\x00\x01\x04' + data + data = b"\x01\x02\x03\x04" * 65 # len > 126 -- len == 260 + buf = b"\x82\x7f\x00\x00\x00\x00\x00\x00\x01\x04" + data ws = websocket.WebSocket() res = ws._decode_hybi(buf) - self.assertEqual(res['fin'], 1) - self.assertEqual(res['opcode'], 0x2) - self.assertEqual(res['length'], len(buf)) - self.assertEqual(res['payload'], data) + self.assertEqual(res["fin"], 1) + self.assertEqual(res["opcode"], 0x2) + self.assertEqual(res["length"], len(buf)) + self.assertEqual(res["payload"], data) def test_decode_hybi_multi(self): - buf1 = b'\x01\x03\x48\x65\x6c' - buf2 = b'\x80\x02\x6c\x6f' + buf1 = b"\x01\x03\x48\x65\x6c" + buf2 = b"\x80\x02\x6c\x6f" ws = websocket.WebSocket() res1 = ws._decode_hybi(buf1) - self.assertEqual(res1['fin'], 0) - self.assertEqual(res1['opcode'], 0x1) - self.assertEqual(res1['length'], len(buf1)) - self.assertEqual(res1['payload'], b'Hel') + self.assertEqual(res1["fin"], 0) + self.assertEqual(res1["opcode"], 0x1) + self.assertEqual(res1["length"], len(buf1)) + self.assertEqual(res1["payload"], b"Hel") res2 = ws._decode_hybi(buf2) - self.assertEqual(res2['fin'], 1) - self.assertEqual(res2['opcode'], 0x0) - self.assertEqual(res2['length'], len(buf2)) - self.assertEqual(res2['payload'], b'lo') + self.assertEqual(res2["fin"], 1) + self.assertEqual(res2["opcode"], 0x0) + self.assertEqual(res2["length"], len(buf2)) + self.assertEqual(res2["payload"], b"lo") def test_encode_hybi_basic(self): ws = websocket.WebSocket() - res = ws._encode_hybi(0x1, b'Hello') - expected = b'\x81\x05\x48\x65\x6c\x6c\x6f' + res = ws._encode_hybi(0x1, b"Hello") + expected = b"\x81\x05\x48\x65\x6c\x6c\x6f" self.assertEqual(res, expected) diff --git a/tests/test_websocketproxy.py b/tests/test_websocketproxy.py index e56bef72..fc69040c 100644 --- a/tests/test_websocketproxy.py +++ b/tests/test_websocketproxy.py @@ -14,7 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. -""" Unit tests for websocketproxy """ +"""Unit tests for websocketproxy""" import sys import unittest @@ -30,7 +30,7 @@ class FakeSocket: - def __init__(self, data=b''): + def __init__(self, data=b""): self._data = data def recv(self, amt, flags=None): @@ -40,11 +40,11 @@ def recv(self, amt, flags=None): return res - def makefile(self, mode='r', buffsize=None): - if 'b' in mode: + def makefile(self, mode="r", buffsize=None): + if "b" in mode: return BytesIO(self._data) else: - return StringIO(self._data.decode('latin_1')) + return StringIO(self._data.decode("latin_1")) class FakeServer: @@ -58,14 +58,16 @@ def __init__(self): self.ssl_target = None self.unix_target = None + class ProxyRequestHandlerTestCase(unittest.TestCase): def setUp(self): super().setUp() self.handler = websocketproxy.ProxyRequestHandler( - FakeSocket(), "127.0.0.1", FakeServer()) + FakeSocket(), "127.0.0.1", FakeServer() + ) self.handler.path = "https://localhost:6080/websockify?token=blah" self.handler.headers = {} - patch('websockify.websockifyserver.WebSockifyServer.socket').start() + patch("websockify.websockifyserver.WebSockifyServer.socket").start() def tearDown(self): patch.stopall() @@ -76,8 +78,7 @@ class TestPlugin(token_plugins.BasePlugin): def lookup(self, token): return ("some host", "some port") - host, port = self.handler.get_target( - TestPlugin(None)) + host, port = self.handler.get_target(TestPlugin(None)) self.assertEqual(host, "some host") self.assertEqual(port, "some port") @@ -87,8 +88,7 @@ class TestPlugin(token_plugins.BasePlugin): def lookup(self, token): return ("unix_socket", "/tmp/socket") - _, socket = self.handler.get_target( - TestPlugin(None)) + _, socket = self.handler.get_target(TestPlugin(None)) self.assertEqual(socket, "/tmp/socket") @@ -100,11 +100,11 @@ def lookup(self, token): with self.assertRaises(FakeServer.EClose): self.handler.get_target(TestPlugin(None)) - @patch('websockify.websocketproxy.ProxyRequestHandler.send_auth_error', MagicMock()) + @patch("websockify.websocketproxy.ProxyRequestHandler.send_auth_error", MagicMock()) def test_token_plugin(self): class TestPlugin(token_plugins.BasePlugin): def lookup(self, token): - return (self.source + token).split(',') + return (self.source + token).split(",") self.handler.server.token_plugin = TestPlugin("somehost,") self.handler.validate_connection() @@ -112,7 +112,7 @@ def lookup(self, token): self.assertEqual(self.handler.server.target_host, "somehost") self.assertEqual(self.handler.server.target_port, "blah") - @patch('websockify.websocketproxy.ProxyRequestHandler.send_auth_error', MagicMock()) + @patch("websockify.websocketproxy.ProxyRequestHandler.send_auth_error", MagicMock()) def test_auth_plugin(self): class TestPlugin(auth_plugins.BasePlugin): def authenticate(self, headers, target_host, target_port): @@ -128,4 +128,3 @@ def authenticate(self, headers, target_host, target_port): self.handler.server.target_host = "someotherhost" self.handler.auth_connection() - diff --git a/tests/test_websocketserver.py b/tests/test_websocketserver.py index 0e37e3da..a1732e64 100644 --- a/tests/test_websocketserver.py +++ b/tests/test_websocketserver.py @@ -1,5 +1,5 @@ +"""Unit tests for websocketserver""" -""" Unit tests for websocketserver """ import unittest from unittest.mock import patch, MagicMock @@ -66,4 +66,3 @@ def test_end_headers(self): # Then req_obj.end_headers.assert_called_once_with() - diff --git a/tests/test_websockifyserver.py b/tests/test_websockifyserver.py index 42edb4aa..0c09126a 100644 --- a/tests/test_websockifyserver.py +++ b/tests/test_websockifyserver.py @@ -14,7 +14,8 @@ # License for the specific language governing permissions and limitations # under the License. -""" Unit tests for websockifyserver """ +"""Unit tests for websockifyserver""" + import errno import os import logging @@ -36,11 +37,11 @@ def raise_oserror(*args, **kwargs): - raise OSError('fake error') + raise OSError("fake error") class FakeSocket: - def __init__(self, data=b''): + def __init__(self, data=b""): self._data = data def recv(self, amt, flags=None): @@ -50,19 +51,19 @@ def recv(self, amt, flags=None): return res - def makefile(self, mode='r', buffsize=None): - if 'b' in mode: + def makefile(self, mode="r", buffsize=None): + if "b" in mode: return BytesIO(self._data) else: - return StringIO(self._data.decode('latin_1')) + return StringIO(self._data.decode("latin_1")) class WebSockifyRequestHandlerTestCase(unittest.TestCase): def setUp(self): super().setUp() - self.tmpdir = tempfile.mkdtemp('-websockify-tests') + self.tmpdir = tempfile.mkdtemp("-websockify-tests") # Mock this out cause it screws tests up - patch('os.chdir').start() + patch("os.chdir").start() def tearDown(self): """Called automatically after each test.""" @@ -70,31 +71,41 @@ def tearDown(self): os.rmdir(self.tmpdir) super().tearDown() - def _get_server(self, handler_class=websockifyserver.WebSockifyRequestHandler, - **kwargs): - web = kwargs.pop('web', self.tmpdir) + def _get_server( + self, handler_class=websockifyserver.WebSockifyRequestHandler, **kwargs + ): + web = kwargs.pop("web", self.tmpdir) return websockifyserver.WebSockifyServer( - handler_class, listen_host='localhost', - listen_port=80, key=self.tmpdir, web=web, - record=self.tmpdir, daemon=False, ssl_only=0, idle_timeout=1, - **kwargs) - - @patch('websockify.websockifyserver.WebSockifyRequestHandler.send_error') + handler_class, + listen_host="localhost", + listen_port=80, + key=self.tmpdir, + web=web, + record=self.tmpdir, + daemon=False, + ssl_only=0, + idle_timeout=1, + **kwargs, + ) + + @patch("websockify.websockifyserver.WebSockifyRequestHandler.send_error") def test_normal_get_with_only_upgrade_returns_error(self, send_error): server = self._get_server(web=None) handler = websockifyserver.WebSockifyRequestHandler( - FakeSocket(b'GET /tmp.txt HTTP/1.1'), '127.0.0.1', server) + FakeSocket(b"GET /tmp.txt HTTP/1.1"), "127.0.0.1", server + ) handler.do_GET() send_error.assert_called_with(405) - @patch('websockify.websockifyserver.WebSockifyRequestHandler.send_error') + @patch("websockify.websockifyserver.WebSockifyRequestHandler.send_error") def test_list_dir_with_file_only_returns_error(self, send_error): server = self._get_server(file_only=True) handler = websockifyserver.WebSockifyRequestHandler( - FakeSocket(b'GET / HTTP/1.1'), '127.0.0.1', server) + FakeSocket(b"GET / HTTP/1.1"), "127.0.0.1", server + ) - handler.path = '/' + handler.path = "/" handler.do_GET() send_error.assert_called_with(404) @@ -102,9 +113,9 @@ def test_list_dir_with_file_only_returns_error(self, send_error): class WebSockifyServerTestCase(unittest.TestCase): def setUp(self): super().setUp() - self.tmpdir = tempfile.mkdtemp('-websockify-tests') + self.tmpdir = tempfile.mkdtemp("-websockify-tests") # Mock this out cause it screws tests up - patch('os.chdir').start() + patch("os.chdir").start() def tearDown(self): """Called automatically after each test.""" @@ -112,32 +123,38 @@ def tearDown(self): os.rmdir(self.tmpdir) super().tearDown() - def _get_server(self, handler_class=websockifyserver.WebSockifyRequestHandler, - **kwargs): + def _get_server( + self, handler_class=websockifyserver.WebSockifyRequestHandler, **kwargs + ): return websockifyserver.WebSockifyServer( - handler_class, listen_host='localhost', - listen_port=80, key=self.tmpdir, web=self.tmpdir, - record=self.tmpdir, **kwargs) + handler_class, + listen_host="localhost", + listen_port=80, + key=self.tmpdir, + web=self.tmpdir, + record=self.tmpdir, + **kwargs, + ) def test_daemonize_raises_error_while_closing_fds(self): server = self._get_server(daemon=True, ssl_only=1, idle_timeout=1) - patch('os.fork').start().return_value = 0 - patch('signal.signal').start() - patch('os.setsid').start() - patch('os.close').start().side_effect = raise_oserror - self.assertRaises(OSError, server.daemonize, keepfd=None, chdir='./') + patch("os.fork").start().return_value = 0 + patch("signal.signal").start() + patch("os.setsid").start() + patch("os.close").start().side_effect = raise_oserror + self.assertRaises(OSError, server.daemonize, keepfd=None, chdir="./") def test_daemonize_ignores_ebadf_error_while_closing_fds(self): def raise_oserror_ebadf(fd): - raise OSError(errno.EBADF, 'fake error') + raise OSError(errno.EBADF, "fake error") server = self._get_server(daemon=True, ssl_only=1, idle_timeout=1) - patch('os.fork').start().return_value = 0 - patch('signal.signal').start() - patch('os.setsid').start() - patch('os.close').start().side_effect = raise_oserror_ebadf - patch('os.open').start().side_effect = raise_oserror - self.assertRaises(OSError, server.daemonize, keepfd=None, chdir='./') + patch("os.fork").start().return_value = 0 + patch("signal.signal").start() + patch("os.setsid").start() + patch("os.close").start().side_effect = raise_oserror_ebadf + patch("os.open").start().side_effect = raise_oserror + self.assertRaises(OSError, server.daemonize, keepfd=None, chdir="./") def test_handshake_fails_on_not_ready(self): server = self._get_server(daemon=True, ssl_only=0, idle_timeout=1) @@ -145,23 +162,29 @@ def test_handshake_fails_on_not_ready(self): def fake_select(rlist, wlist, xlist, timeout=None): return ([], [], []) - patch('select.select').start().side_effect = fake_select + patch("select.select").start().side_effect = fake_select self.assertRaises( - websockifyserver.WebSockifyServer.EClose, server.do_handshake, - FakeSocket(), '127.0.0.1') + websockifyserver.WebSockifyServer.EClose, + server.do_handshake, + FakeSocket(), + "127.0.0.1", + ) def test_empty_handshake_fails(self): server = self._get_server(daemon=True, ssl_only=0, idle_timeout=1) - sock = FakeSocket('') + sock = FakeSocket("") def fake_select(rlist, wlist, xlist, timeout=None): return ([sock], [], []) - patch('select.select').start().side_effect = fake_select + patch("select.select").start().side_effect = fake_select self.assertRaises( - websockifyserver.WebSockifyServer.EClose, server.do_handshake, - sock, '127.0.0.1') + websockifyserver.WebSockifyServer.EClose, + server.do_handshake, + sock, + "127.0.0.1", + ) def test_handshake_policy_request(self): # TODO(directxman12): implement @@ -170,35 +193,39 @@ def test_handshake_policy_request(self): def test_handshake_ssl_only_without_ssl_raises_error(self): server = self._get_server(daemon=True, ssl_only=1, idle_timeout=1) - sock = FakeSocket(b'some initial data') + sock = FakeSocket(b"some initial data") def fake_select(rlist, wlist, xlist, timeout=None): return ([sock], [], []) - patch('select.select').start().side_effect = fake_select + patch("select.select").start().side_effect = fake_select self.assertRaises( - websockifyserver.WebSockifyServer.EClose, server.do_handshake, - sock, '127.0.0.1') + websockifyserver.WebSockifyServer.EClose, + server.do_handshake, + sock, + "127.0.0.1", + ) def test_do_handshake_no_ssl(self): class FakeHandler: CALLED = False + def __init__(self, *args, **kwargs): type(self).CALLED = True FakeHandler.CALLED = False server = self._get_server( - handler_class=FakeHandler, daemon=True, - ssl_only=0, idle_timeout=1) + handler_class=FakeHandler, daemon=True, ssl_only=0, idle_timeout=1 + ) - sock = FakeSocket(b'some initial data') + sock = FakeSocket(b"some initial data") def fake_select(rlist, wlist, xlist, timeout=None): return ([sock], [], []) - patch('select.select').start().side_effect = fake_select - self.assertEqual(server.do_handshake(sock, '127.0.0.1'), sock) + patch("select.select").start().side_effect = fake_select + self.assertEqual(server.do_handshake(sock, "127.0.0.1"), sock) self.assertTrue(FakeHandler.CALLED, True) def test_do_handshake_ssl(self): @@ -210,18 +237,22 @@ def test_do_handshake_ssl_without_ssl_raises_error(self): pass def test_do_handshake_ssl_without_cert_raises_error(self): - server = self._get_server(daemon=True, ssl_only=0, idle_timeout=1, - cert='afdsfasdafdsafdsafdsafdas') + server = self._get_server( + daemon=True, ssl_only=0, idle_timeout=1, cert="afdsfasdafdsafdsafdsafdas" + ) sock = FakeSocket(b"\x16some ssl data") def fake_select(rlist, wlist, xlist, timeout=None): return ([sock], [], []) - patch('select.select').start().side_effect = fake_select + patch("select.select").start().side_effect = fake_select self.assertRaises( - websockifyserver.WebSockifyServer.EClose, server.do_handshake, - sock, '127.0.0.1') + websockifyserver.WebSockifyServer.EClose, + server.do_handshake, + sock, + "127.0.0.1", + ) def test_do_handshake_ssl_error_eof_raises_close_error(self): server = self._get_server(daemon=True, ssl_only=0, idle_timeout=1) @@ -234,58 +265,79 @@ def fake_select(rlist, wlist, xlist, timeout=None): def fake_wrap_socket(*args, **kwargs): raise ssl.SSLError(ssl.SSL_ERROR_EOF) - class fake_create_default_context(): + class fake_create_default_context: def __init__(self, purpose): self.verify_mode = None self.options = 0 + def load_cert_chain(self, certfile, keyfile, password): pass + def set_default_verify_paths(self): pass + def load_verify_locations(self, cafile): pass + def wrap_socket(self, *args, **kwargs): raise ssl.SSLError(ssl.SSL_ERROR_EOF) - patch('select.select').start().side_effect = fake_select - patch('ssl.create_default_context').start().side_effect = fake_create_default_context + patch("select.select").start().side_effect = fake_select + patch( + "ssl.create_default_context" + ).start().side_effect = fake_create_default_context self.assertRaises( - websockifyserver.WebSockifyServer.EClose, server.do_handshake, - sock, '127.0.0.1') + websockifyserver.WebSockifyServer.EClose, + server.do_handshake, + sock, + "127.0.0.1", + ) def test_do_handshake_ssl_sets_ciphers(self): - test_ciphers = 'TEST-CIPHERS-1:TEST-CIPHER-2' + test_ciphers = "TEST-CIPHERS-1:TEST-CIPHER-2" class FakeHandler: def __init__(self, *args, **kwargs): pass - server = self._get_server(handler_class=FakeHandler, daemon=True, - idle_timeout=1, ssl_ciphers=test_ciphers) + server = self._get_server( + handler_class=FakeHandler, + daemon=True, + idle_timeout=1, + ssl_ciphers=test_ciphers, + ) sock = FakeSocket(b"\x16some ssl data") def fake_select(rlist, wlist, xlist, timeout=None): return ([sock], [], []) - class fake_create_default_context(): - CIPHERS = '' + class fake_create_default_context: + CIPHERS = "" + def __init__(self, purpose): self.verify_mode = None self.options = 0 + def load_cert_chain(self, certfile, keyfile, password): pass + def set_default_verify_paths(self): pass + def load_verify_locations(self, cafile): pass + def wrap_socket(self, *args, **kwargs): pass + def set_ciphers(self, ciphers_to_set): fake_create_default_context.CIPHERS = ciphers_to_set - patch('select.select').start().side_effect = fake_select - patch('ssl.create_default_context').start().side_effect = fake_create_default_context - server.do_handshake(sock, '127.0.0.1') + patch("select.select").start().side_effect = fake_select + patch( + "ssl.create_default_context" + ).start().side_effect = fake_create_default_context + server.do_handshake(sock, "127.0.0.1") self.assertEqual(fake_create_default_context.CIPHERS, test_ciphers) def test_do_handshake_ssl_sets_opions(self): @@ -295,8 +347,12 @@ class FakeHandler: def __init__(self, *args, **kwargs): pass - server = self._get_server(handler_class=FakeHandler, daemon=True, - idle_timeout=1, ssl_options=test_options) + server = self._get_server( + handler_class=FakeHandler, + daemon=True, + idle_timeout=1, + ssl_options=test_options, + ) sock = FakeSocket(b"\x16some ssl data") def fake_select(rlist, wlist, xlist, timeout=None): @@ -304,26 +360,36 @@ def fake_select(rlist, wlist, xlist, timeout=None): class fake_create_default_context: OPTIONS = 0 + def __init__(self, purpose): self.verify_mode = None self._options = 0 + def load_cert_chain(self, certfile, keyfile, password): pass + def set_default_verify_paths(self): pass + def load_verify_locations(self, cafile): pass + def wrap_socket(self, *args, **kwargs): pass + def get_options(self): return self._options + def set_options(self, val): fake_create_default_context.OPTIONS = val + options = property(get_options, set_options) - patch('select.select').start().side_effect = fake_select - patch('ssl.create_default_context').start().side_effect = fake_create_default_context - server.do_handshake(sock, '127.0.0.1') + patch("select.select").start().side_effect = fake_select + patch( + "ssl.create_default_context" + ).start().side_effect = fake_create_default_context + server.do_handshake(sock, "127.0.0.1") self.assertEqual(fake_create_default_context.OPTIONS, test_options) def test_fallback_sigchld_handler(self): @@ -332,38 +398,38 @@ def test_fallback_sigchld_handler(self): def test_start_server_error(self): server = self._get_server(daemon=False, ssl_only=1, idle_timeout=1) - sock = server.socket('localhost') + sock = server.socket("localhost") def fake_select(rlist, wlist, xlist, timeout=None): raise Exception("fake error") - patch('websockify.websockifyserver.WebSockifyServer.socket').start() - patch('websockify.websockifyserver.WebSockifyServer.daemonize').start() - patch('select.select').start().side_effect = fake_select + patch("websockify.websockifyserver.WebSockifyServer.socket").start() + patch("websockify.websockifyserver.WebSockifyServer.daemonize").start() + patch("select.select").start().side_effect = fake_select server.start_server() def test_start_server_keyboardinterrupt(self): server = self._get_server(daemon=False, ssl_only=0, idle_timeout=1) - sock = server.socket('localhost') + sock = server.socket("localhost") def fake_select(rlist, wlist, xlist, timeout=None): raise KeyboardInterrupt - patch('websockify.websockifyserver.WebSockifyServer.socket').start() - patch('websockify.websockifyserver.WebSockifyServer.daemonize').start() - patch('select.select').start().side_effect = fake_select + patch("websockify.websockifyserver.WebSockifyServer.socket").start() + patch("websockify.websockifyserver.WebSockifyServer.daemonize").start() + patch("select.select").start().side_effect = fake_select server.start_server() def test_start_server_systemexit(self): server = self._get_server(daemon=False, ssl_only=0, idle_timeout=1) - sock = server.socket('localhost') + sock = server.socket("localhost") def fake_select(rlist, wlist, xlist, timeout=None): sys.exit() - patch('websockify.websockifyserver.WebSockifyServer.socket').start() - patch('websockify.websockifyserver.WebSockifyServer.daemonize').start() - patch('select.select').start().side_effect = fake_select + patch("websockify.websockifyserver.WebSockifyServer.socket").start() + patch("websockify.websockifyserver.WebSockifyServer.daemonize").start() + patch("select.select").start().side_effect = fake_select server.start_server() def test_socket_set_keepalive_options(self): @@ -372,29 +438,37 @@ def test_socket_set_keepalive_options(self): keepintvl = 56 server = self._get_server(daemon=False, ssl_only=0, idle_timeout=1) - sock = server.socket('localhost', - tcp_keepcnt=keepcnt, - tcp_keepidle=keepidle, - tcp_keepintvl=keepintvl) - - if hasattr(socket, 'TCP_KEEPCNT'): - self.assertEqual(sock.getsockopt(socket.SOL_TCP, - socket.TCP_KEEPCNT), keepcnt) - self.assertEqual(sock.getsockopt(socket.SOL_TCP, - socket.TCP_KEEPIDLE), keepidle) - self.assertEqual(sock.getsockopt(socket.SOL_TCP, - socket.TCP_KEEPINTVL), keepintvl) - - sock = server.socket('localhost', - tcp_keepalive=False, - tcp_keepcnt=keepcnt, - tcp_keepidle=keepidle, - tcp_keepintvl=keepintvl) - - if hasattr(socket, 'TCP_KEEPCNT'): - self.assertNotEqual(sock.getsockopt(socket.SOL_TCP, - socket.TCP_KEEPCNT), keepcnt) - self.assertNotEqual(sock.getsockopt(socket.SOL_TCP, - socket.TCP_KEEPIDLE), keepidle) - self.assertNotEqual(sock.getsockopt(socket.SOL_TCP, - socket.TCP_KEEPINTVL), keepintvl) + sock = server.socket( + "localhost", + tcp_keepcnt=keepcnt, + tcp_keepidle=keepidle, + tcp_keepintvl=keepintvl, + ) + + if hasattr(socket, "TCP_KEEPCNT"): + self.assertEqual( + sock.getsockopt(socket.SOL_TCP, socket.TCP_KEEPCNT), keepcnt + ) + self.assertEqual(sock.getsockopt(socket.SOL_TCP, socket.TCP_KEEPIDLE), keepidle) + self.assertEqual( + sock.getsockopt(socket.SOL_TCP, socket.TCP_KEEPINTVL), keepintvl + ) + + sock = server.socket( + "localhost", + tcp_keepalive=False, + tcp_keepcnt=keepcnt, + tcp_keepidle=keepidle, + tcp_keepintvl=keepintvl, + ) + + if hasattr(socket, "TCP_KEEPCNT"): + self.assertNotEqual( + sock.getsockopt(socket.SOL_TCP, socket.TCP_KEEPCNT), keepcnt + ) + self.assertNotEqual( + sock.getsockopt(socket.SOL_TCP, socket.TCP_KEEPIDLE), keepidle + ) + self.assertNotEqual( + sock.getsockopt(socket.SOL_TCP, socket.TCP_KEEPINTVL), keepintvl + ) diff --git a/websockify.py b/websockify.py index e5224d53..f5bdd214 120000 --- a/websockify.py +++ b/websockify.py @@ -1 +1 @@ -run \ No newline at end of file +run diff --git a/websockify/__main__.py b/websockify/__main__.py index 8378d467..c16794ff 100644 --- a/websockify/__main__.py +++ b/websockify/__main__.py @@ -1,4 +1,4 @@ import websockify -if __name__ == '__main__': +if __name__ == "__main__": websockify.websocketproxy.websockify_init() diff --git a/websockify/auth_plugins.py b/websockify/auth_plugins.py index 36fac520..b08bdfb8 100644 --- a/websockify/auth_plugins.py +++ b/websockify/auth_plugins.py @@ -1,4 +1,4 @@ -class BasePlugin(): +class BasePlugin: def __init__(self, src=None): self.source = src @@ -7,7 +7,9 @@ def authenticate(self, headers, target_host, target_port): class AuthenticationError(Exception): - def __init__(self, log_msg=None, response_code=403, response_headers={}, response_msg=None): + def __init__( + self, log_msg=None, response_code=403, response_headers={}, response_msg=None + ): self.code = response_code self.headers = response_headers self.msg = response_msg @@ -15,7 +17,7 @@ def __init__(self, log_msg=None, response_code=403, response_headers={}, respons if log_msg is None: log_msg = response_msg - super().__init__('%s %s' % (self.code, log_msg)) + super().__init__("%s %s" % (self.code, log_msg)) class InvalidOriginError(AuthenticationError): @@ -24,12 +26,13 @@ def __init__(self, expected, actual): self.actual_origin = actual super().__init__( - response_msg='Invalid Origin', + response_msg="Invalid Origin", log_msg="Invalid Origin Header: Expected one of " - "%s, got '%s'" % (expected, actual)) + "%s, got '%s'" % (expected, actual), + ) -class BasicHTTPAuth(): +class BasicHTTPAuth: """Verifies Basic Auth headers. Specify src as username:password""" def __init__(self, src=None): @@ -37,9 +40,10 @@ def __init__(self, src=None): def authenticate(self, headers, target_host, target_port): import base64 - auth_header = headers.get('Authorization') + + auth_header = headers.get("Authorization") if auth_header: - if not auth_header.startswith('Basic '): + if not auth_header.startswith("Basic "): self.auth_error() try: @@ -49,11 +53,11 @@ def authenticate(self, headers, target_host, target_port): try: # http://stackoverflow.com/questions/7242316/what-encoding-should-i-use-for-http-basic-authentication - user_pass_as_text = user_pass_raw.decode('ISO-8859-1') + user_pass_as_text = user_pass_raw.decode("ISO-8859-1") except UnicodeDecodeError: self.auth_error() - user_pass = user_pass_as_text.split(':', 1) + user_pass = user_pass_as_text.split(":", 1) if len(user_pass) != 2: self.auth_error() @@ -64,7 +68,7 @@ def authenticate(self, headers, target_host, target_port): self.demand_auth() def validate_creds(self, username, password): - if '%s:%s' % (username, password) == self.src: + if "%s:%s" % (username, password) == self.src: return True else: return False @@ -73,10 +77,13 @@ def auth_error(self): raise AuthenticationError(response_code=403) def demand_auth(self): - raise AuthenticationError(response_code=401, - response_headers={'WWW-Authenticate': 'Basic realm="Websockify"'}) + raise AuthenticationError( + response_code=401, + response_headers={"WWW-Authenticate": 'Basic realm="Websockify"'}, + ) + -class ExpectOrigin(): +class ExpectOrigin: def __init__(self, src=None): if src is None: self.source = [] @@ -84,11 +91,12 @@ def __init__(self, src=None): self.source = src.split() def authenticate(self, headers, target_host, target_port): - origin = headers.get('Origin', None) + origin = headers.get("Origin", None) if origin is None or origin not in self.source: raise InvalidOriginError(expected=self.source, actual=origin) -class ClientCertCNAuth(): + +class ClientCertCNAuth: """Verifies client by SSL certificate. Specify src as whitespace separated list of common names.""" def __init__(self, src=None): @@ -98,5 +106,5 @@ def __init__(self, src=None): self.source = src.split() def authenticate(self, headers, target_host, target_port): - if headers.get('SSL_CLIENT_S_DN_CN', None) not in self.source: + if headers.get("SSL_CLIENT_S_DN_CN", None) not in self.source: raise AuthenticationError(response_code=403) diff --git a/websockify/sysloghandler.py b/websockify/sysloghandler.py index 8b344a1a..6abdf1be 100644 --- a/websockify/sysloghandler.py +++ b/websockify/sysloghandler.py @@ -7,23 +7,26 @@ class WebsockifySysLogHandler(handlers.SysLogHandler): as defined by RFC 5424. """ - _legacy_head_fmt = '<{pri}>{ident}[{pid}]: ' - _rfc5424_head_fmt = '<{pri}>1 {timestamp} {hostname} {ident} {pid} - - ' + _legacy_head_fmt = "<{pri}>{ident}[{pid}]: " + _rfc5424_head_fmt = "<{pri}>1 {timestamp} {hostname} {ident} {pid} - - " _head_fmt = _rfc5424_head_fmt _legacy = False - _timestamp_fmt = '%Y-%m-%dT%H:%M:%SZ' + _timestamp_fmt = "%Y-%m-%dT%H:%M:%SZ" _max_hostname = 255 - _max_ident = 24 #safer for old daemons + _max_ident = 24 # safer for old daemons _send_length = False - _tail = '\n' - + _tail = "\n" ident = None - - def __init__(self, address=('localhost', handlers.SYSLOG_UDP_PORT), - facility=handlers.SysLogHandler.LOG_USER, - socktype=None, ident=None, legacy=False): + def __init__( + self, + address=("localhost", handlers.SYSLOG_UDP_PORT), + facility=handlers.SysLogHandler.LOG_USER, + socktype=None, + ident=None, + legacy=False, + ): """ Initialize a handler. @@ -46,7 +49,6 @@ def __init__(self, address=('localhost', handlers.SYSLOG_UDP_PORT), super().__init__(address, facility, socktype) - def emit(self, record): """ Emit a record. @@ -57,46 +59,44 @@ def emit(self, record): try: # Gather info. - text = self.format(record).replace(self._tail, ' ') - if not text: # nothing to log + text = self.format(record).replace(self._tail, " ") + if not text: # nothing to log return - pri = self.encodePriority(self.facility, - self.mapPriority(record.levelname)) - - timestamp = time.strftime(self._timestamp_fmt, time.gmtime()); + pri = self.encodePriority(self.facility, self.mapPriority(record.levelname)) - hostname = socket.gethostname()[:self._max_hostname] + timestamp = time.strftime(self._timestamp_fmt, time.gmtime()) + hostname = socket.gethostname()[: self._max_hostname] if self.ident: - ident = self.ident[:self._max_ident] + ident = self.ident[: self._max_ident] else: - ident = '' + ident = "" - pid = os.getpid() # shouldn't need truncation + pid = os.getpid() # shouldn't need truncation # Format the header. head = { - 'pri': pri, - 'timestamp': timestamp, - 'hostname': hostname, - 'ident': ident, - 'pid': pid, + "pri": pri, + "timestamp": timestamp, + "hostname": hostname, + "ident": ident, + "pid": pid, } - msg = self._head_fmt.format(**head).encode('ascii', 'ignore') + msg = self._head_fmt.format(**head).encode("ascii", "ignore") # Encode text as plain ASCII if possible, else use UTF-8 with BOM. try: - msg += text.encode('ascii') + msg += text.encode("ascii") except UnicodeEncodeError: - msg += text.encode('utf-8-sig') + msg += text.encode("utf-8-sig") # Add length or tail character, if necessary. if self.socktype != socket.SOCK_DGRAM: if self._send_length: - msg = ('%d ' % len(msg)).encode('ascii') + msg + msg = ("%d " % len(msg)).encode("ascii") + msg else: - msg += self._tail.encode('ascii') + msg += self._tail.encode("ascii") # Send the message. if self.unixsocket: diff --git a/websockify/token_plugins.py b/websockify/token_plugins.py index 5a95490c..4c46d1b9 100644 --- a/websockify/token_plugins.py +++ b/websockify/token_plugins.py @@ -10,8 +10,8 @@ _SOURCE_SPLIT_REGEX = re.compile( r'(?<=^)"([^"]+)"(?=:|$)' r'|(?<=:)"([^"]+)"(?=:|$)' - r'|(?<=^)([^:]*)(?=:|$)' - r'|(?<=:)([^:]*)(?=:|$)', + r"|(?<=^)([^:]*)(?=:|$)" + r"|(?<=:)([^:]*)(?=:|$)", ) @@ -26,7 +26,7 @@ def parse_source_args(src): return [m[0] or m[1] or m[2] or m[3] for m in matches] -class BasePlugin(): +class BasePlugin: def __init__(self, src): self.source = src @@ -44,8 +44,7 @@ def __init__(self, *args, **kwargs): def _load_targets(self): if os.path.isdir(self.source): - cfg_files = [os.path.join(self.source, f) for - f in os.listdir(self.source)] + cfg_files = [os.path.join(self.source, f) for f in os.listdir(self.source)] else: cfg_files = [self.source] @@ -53,12 +52,14 @@ def _load_targets(self): index = 1 for f in cfg_files: for line in [l.strip() for l in open(f).readlines()]: - if line and not line.startswith('#'): + if line and not line.startswith("#"): try: - tok, target = re.split(r':\s', line) - self._targets[tok] = target.strip().rsplit(':', 1) + tok, target = re.split(r":\s", line) + self._targets[tok] = target.strip().rsplit(":", 1) except ValueError: - logger.error("Syntax error in %s on line %d" % (self.source, index)) + logger.error( + "Syntax error in %s on line %d" % (self.source, index) + ) index += 1 def lookup(self, token): @@ -83,6 +84,7 @@ def lookup(self, token): return super().lookup(token) + class TokenFileName(BasePlugin): # source is a directory # token is filename @@ -91,12 +93,12 @@ def __init__(self, src): super().__init__(src) if not os.path.isdir(src): raise Exception("TokenFileName plugin requires a directory") - + def lookup(self, token): token = os.path.basename(token) path = os.path.join(self.source, token) if os.path.exists(path): - return open(path).read().strip().split(':') + return open(path).read().strip().split(":") else: return None @@ -109,9 +111,9 @@ class BaseTokenAPI(BasePlugin): # in this file can be used w/o unnecessary dependencies def process_result(self, resp): - host, port = resp.text.split(':') - port = port.encode('ascii','ignore') - return [ host, port ] + host, port = resp.text.split(":") + port = port.encode("ascii", "ignore") + return [host, port] def lookup(self, token): import requests @@ -130,7 +132,7 @@ class JSONTokenApi(BaseTokenAPI): def process_result(self, resp): resp_json = resp.json() - return (resp_json['host'], resp_json['port']) + return (resp_json["host"], resp_json["port"]) class JWTTokenApi(BasePlugin): @@ -145,7 +147,7 @@ def lookup(self, token): key = jwk.JWK() try: - with open(self.source, 'rb') as key_file: + with open(self.source, "rb") as key_file: key_data = key_file.read() except Exception as e: logger.error("Error loading key file: %s" % str(e)) @@ -155,39 +157,41 @@ def lookup(self, token): key.import_from_pem(key_data) except: try: - key.import_key(k=key_data.decode('utf-8'),kty='oct') + key.import_key(k=key_data.decode("utf-8"), kty="oct") except: - logger.error('Failed to correctly parse key data!') + logger.error("Failed to correctly parse key data!") return None try: token = jwt.JWT(key=key, jwt=token) parsed_header = json.loads(token.header) - if 'enc' in parsed_header: + if "enc" in parsed_header: # Token is encrypted, so we need to decrypt by passing the claims to a new instance token = jwt.JWT(key=key, jwt=token.claims) parsed = json.loads(token.claims) - if 'nbf' in parsed: + if "nbf" in parsed: # Not Before is present, so we need to check it - if time.time() < parsed['nbf']: - logger.warning('Token can not be used yet!') + if time.time() < parsed["nbf"]: + logger.warning("Token can not be used yet!") return None - if 'exp' in parsed: + if "exp" in parsed: # Expiration time is present, so we need to check it - if time.time() > parsed['exp']: - logger.warning('Token has expired!') + if time.time() > parsed["exp"]: + logger.warning("Token has expired!") return None - return (parsed['host'], parsed['port']) + return (parsed["host"], parsed["port"]) except Exception as e: logger.error("Failed to parse token: %s" % str(e)) return None except ImportError: - logger.error("package jwcrypto not found, are you sure you've installed it correctly?") + logger.error( + "package jwcrypto not found, are you sure you've installed it correctly?" + ) return None @@ -251,6 +255,7 @@ class TokenRedis(BasePlugin): pip install redis """ + def __init__(self, src): try: import redis @@ -285,7 +290,9 @@ def __init__(self, src): if not self._password: self._password = None elif len(fields) == 5: - self._server, self._port, self._db, self._password, self._namespace = fields + self._server, self._port, self._db, self._password, self._namespace = ( + fields + ) if not self._port: self._port = 6379 if not self._db: @@ -301,24 +308,30 @@ def __init__(self, src): if self._namespace: self._namespace += ":" - logger.info("TokenRedis backend initialized (%s:%s)" % - (self._server, self._port)) + logger.info( + "TokenRedis backend initialized (%s:%s)" % (self._server, self._port) + ) except ValueError: - logger.error("The provided --token-source='%s' is not in the " - "expected format [:[:[:[:]]]]" % - src) + logger.error( + "The provided --token-source='%s' is not in the " + "expected format [:[:[:[:]]]]" + % src + ) sys.exit() def lookup(self, token): try: import redis except ImportError: - logger.error("package redis not found, are you sure you've installed them correctly?") + logger.error( + "package redis not found, are you sure you've installed them correctly?" + ) sys.exit() logger.info("resolving token '%s'" % token) - client = redis.Redis(host=self._server, port=self._port, - db=self._db, password=self._password) + client = redis.Redis( + host=self._server, port=self._port, db=self._db, password=self._password + ) stuff = client.get(self._namespace + token) if stuff is None: return None @@ -330,14 +343,14 @@ def lookup(self, token): combo = json.loads(responseStr) host, port = combo["host"].split(":") except ValueError: - logger.error("Unable to decode JSON token: %s" % - responseStr) + logger.error("Unable to decode JSON token: %s" % responseStr) return None except KeyError: - logger.error("Unable to find 'host' key in JSON token: %s" % - responseStr) + logger.error( + "Unable to find 'host' key in JSON token: %s" % responseStr + ) return None - elif re.match(r'\S+:\S+', responseStr): + elif re.match(r"\S+:\S+", responseStr): host, port = responseStr.split(":") else: logger.error("Unable to parse token: %s" % responseStr) @@ -368,7 +381,7 @@ def lookup(self, token): if not stat.S_ISSOCK(os.stat(uds_path).st_mode): return None - return [ 'unix_socket', uds_path ] + return ["unix_socket", uds_path] except Exception as e: - logger.error("Error finding unix domain socket: %s" % str(e)) - return None + logger.error("Error finding unix domain socket: %s" % str(e)) + return None diff --git a/websockify/websocket.py b/websockify/websocket.py index 1bed8ccd..b249ee9a 100644 --- a/websockify/websocket.py +++ b/websockify/websocket.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -''' +""" Python WebSocket library Copyright 2011 Joel Martin Copyright 2016 Pierre Ossman @@ -10,7 +10,7 @@ - http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07 - http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10 - http://tools.ietf.org/html/rfc6455 -''' +""" import sys import array @@ -28,14 +28,19 @@ import numpy except ImportError: import warnings + warnings.warn("no 'numpy' module, HyBi protocol will be slower") numpy = None + class WebSocketWantReadError(ssl.SSLWantReadError): pass + + class WebSocketWantWriteError(ssl.SSLWantWriteError): pass + class WebSocket: """WebSocket protocol socket like class. @@ -73,11 +78,11 @@ def __init__(self): self._state = "new" - self._partial_msg = b'' + self._partial_msg = b"" - self._recv_buffer = b'' + self._recv_buffer = b"" self._recv_queue = [] - self._send_buffer = b'' + self._send_buffer = b"" self._previous_sendmsg = None @@ -91,16 +96,22 @@ def __init__(self): def __getattr__(self, name): # These methods are just redirected to the underlying socket - if name in ["fileno", - "getpeername", "getsockname", - "getsockopt", "setsockopt", - "gettimeout", "settimeout", - "setblocking"]: + if name in [ + "fileno", + "getpeername", + "getsockname", + "getsockopt", + "setsockopt", + "gettimeout", + "settimeout", + "setblocking", + ]: assert self.socket is not None return getattr(self.socket, name) else: - raise AttributeError("%s instance has no attribute '%s'" % - (self.__class__.__name__, name)) + raise AttributeError( + "%s instance has no attribute '%s'" % (self.__class__.__name__, name) + ) def connect(self, uri, origin=None, protocols=[]): """Establishes a new connection to a WebSocket server. @@ -118,8 +129,7 @@ def connect(self, uri, origin=None, protocols=[]): connect() must retain the same arguments. """ - self.client = True; - + self.client = True uri = urlparse(uri) port = uri.port @@ -140,8 +150,9 @@ def connect(self, uri, origin=None, protocols=[]): if uri.scheme in ("wss", "https"): context = ssl.create_default_context() - self.socket = context.wrap_socket(self.socket, - server_hostname=uri.hostname) + self.socket = context.wrap_socket( + self.socket, server_hostname=uri.hostname + ) self._state = "ssl_handshake" else: self._state = "headers" @@ -151,7 +162,7 @@ def connect(self, uri, origin=None, protocols=[]): self._state = "headers" if self._state == "headers": - self._key = '' + self._key = "" for i in range(16): self._key += chr(random.randrange(256)) self._key = b64encode(self._key.encode("latin-1")).decode("ascii") @@ -184,10 +195,10 @@ def connect(self, uri, origin=None, protocols=[]): if not self._recv(): raise Exception("Socket closed unexpectedly") - if self._recv_buffer.find(b'\r\n\r\n') == -1: + if self._recv_buffer.find(b"\r\n\r\n") == -1: raise WebSocketWantReadError - (request, self._recv_buffer) = self._recv_buffer.split(b'\r\n', 1) + (request, self._recv_buffer) = self._recv_buffer.split(b"\r\n", 1) request = request.decode("latin-1") words = request.split() @@ -196,17 +207,17 @@ def connect(self, uri, origin=None, protocols=[]): if words[1] != "101": raise Exception("WebSocket request denied: %s" % " ".join(words[1:])) - (headers, self._recv_buffer) = self._recv_buffer.split(b'\r\n\r\n', 1) - headers = headers.decode('latin-1') + '\r\n' + (headers, self._recv_buffer) = self._recv_buffer.split(b"\r\n\r\n", 1) + headers = headers.decode("latin-1") + "\r\n" headers = email.message_from_string(headers) if headers.get("Upgrade", "").lower() != "websocket": print(type(headers)) raise Exception("Missing or incorrect upgrade header") - accept = headers.get('Sec-WebSocket-Accept') + accept = headers.get("Sec-WebSocket-Accept") if accept is None: - raise Exception("Missing Sec-WebSocket-Accept header"); + raise Exception("Missing Sec-WebSocket-Accept header") expected = sha1((self._key + self.GUID).encode("ascii")).digest() expected = b64encode(expected).decode("ascii") @@ -214,9 +225,9 @@ def connect(self, uri, origin=None, protocols=[]): del self._key if accept != expected: - raise Exception("Invalid Sec-WebSocket-Accept header"); + raise Exception("Invalid Sec-WebSocket-Accept header") - self.protocol = headers.get('Sec-WebSocket-Protocol') + self.protocol = headers.get("Sec-WebSocket-Protocol") if len(protocols) == 0: if self.protocol is not None: raise Exception("Unexpected Sec-WebSocket-Protocol header") @@ -256,34 +267,34 @@ def accept(self, socket, headers): if headers.get("upgrade", "").lower() != "websocket": raise Exception("Missing or incorrect upgrade header") - ver = headers.get('Sec-WebSocket-Version') + ver = headers.get("Sec-WebSocket-Version") if ver is None: - raise Exception("Missing Sec-WebSocket-Version header"); + raise Exception("Missing Sec-WebSocket-Version header") # HyBi-07 report version 7 # HyBi-08 - HyBi-12 report version 8 # HyBi-13 reports version 13 - if ver in ['7', '8', '13']: + if ver in ["7", "8", "13"]: self.version = "hybi-%02d" % int(ver) else: raise Exception("Unsupported protocol version %s" % ver) - key = headers.get('Sec-WebSocket-Key') + key = headers.get("Sec-WebSocket-Key") if key is None: - raise Exception("Missing Sec-WebSocket-Key header"); + raise Exception("Missing Sec-WebSocket-Key header") # Generate the hash value for the accept header accept = sha1((key + self.GUID).encode("ascii")).digest() accept = b64encode(accept).decode("ascii") - self.protocol = '' - protocols = headers.get('Sec-WebSocket-Protocol', '').split(',') + self.protocol = "" + protocols = headers.get("Sec-WebSocket-Protocol", "").split(",") if protocols: self.protocol = self.select_subprotocol(protocols) # We are required to choose one of the protocols # presented by the client if self.protocol not in protocols: - raise Exception('Invalid protocol selected') + raise Exception("Invalid protocol selected") self.send_response(101, "Switching Protocols") self.send_header("Upgrade", "websocket") @@ -461,7 +472,7 @@ def end_headers(self): def send_request(self, type, path): self._queue_str("%s %s HTTP/1.1\r\n" % (type.upper(), path)) - def ping(self, data=b''): + def ping(self, data=b""): """Write a ping message to the WebSocket WebSocketWantWriteError can be raised if there is insufficient @@ -486,7 +497,7 @@ def ping(self, data=b''): self._previous_sendmsg = data raise - def pong(self, data=b''): + def pong(self, data=b""): """Write a pong message to the WebSocket WebSocketWantWriteError can be raised if there is insufficient @@ -540,7 +551,7 @@ def shutdown(self, how, code=1000, reason=None): self._sent_close = True - msg = b'' + msg = b"" if code is not None: msg += struct.pack(">H", code) if reason is not None: @@ -602,7 +613,7 @@ def _recv_frames(self): frame = self._decode_hybi(self._recv_buffer) if frame is None: break - self._recv_buffer = self._recv_buffer[frame['length']:] + self._recv_buffer = self._recv_buffer[frame["length"] :] self._recv_queue.append(frame) return True @@ -612,29 +623,39 @@ def _recvmsg(self): while self._recv_queue: frame = self._recv_queue.pop(0) - if not self.client and not frame['masked']: - self.shutdown(socket.SHUT_RDWR, 1002, "Procotol error: Frame not masked") + if not self.client and not frame["masked"]: + self.shutdown( + socket.SHUT_RDWR, 1002, "Procotol error: Frame not masked" + ) continue - if self.client and frame['masked']: + if self.client and frame["masked"]: self.shutdown(socket.SHUT_RDWR, 1002, "Procotol error: Frame masked") continue if frame["opcode"] == 0x0: if not self._partial_msg: - self.shutdown(socket.SHUT_RDWR, 1002, "Procotol error: Unexpected continuation frame") + self.shutdown( + socket.SHUT_RDWR, + 1002, + "Procotol error: Unexpected continuation frame", + ) continue self._partial_msg += frame["payload"] if frame["fin"]: msg = self._partial_msg - self._partial_msg = b'' + self._partial_msg = b"" return msg elif frame["opcode"] == 0x1: - self.shutdown(socket.SHUT_RDWR, 1003, "Unsupported: Text frames are not supported") + self.shutdown( + socket.SHUT_RDWR, 1003, "Unsupported: Text frames are not supported" + ) elif frame["opcode"] == 0x2: if self._partial_msg: - self.shutdown(socket.SHUT_RDWR, 1002, "Procotol error: Unexpected new frame") + self.shutdown( + socket.SHUT_RDWR, 1002, "Procotol error: Unexpected new frame" + ) continue if frame["fin"]: @@ -652,7 +673,9 @@ def _recvmsg(self): return None if not frame["fin"]: - self.shutdown(socket.SHUT_RDWR, 1003, "Unsupported: Fragmented close") + self.shutdown( + socket.SHUT_RDWR, 1003, "Unsupported: Fragmented close" + ) continue code = None @@ -664,7 +687,11 @@ def _recvmsg(self): try: reason = reason.decode("UTF-8") except UnicodeDecodeError: - self.shutdown(socket.SHUT_RDWR, 1002, "Procotol error: Invalid UTF-8 in close") + self.shutdown( + socket.SHUT_RDWR, + 1002, + "Procotol error: Invalid UTF-8 in close", + ) continue if code is None: @@ -679,18 +706,26 @@ def _recvmsg(self): return None elif frame["opcode"] == 0x9: if not frame["fin"]: - self.shutdown(socket.SHUT_RDWR, 1003, "Unsupported: Fragmented ping") + self.shutdown( + socket.SHUT_RDWR, 1003, "Unsupported: Fragmented ping" + ) continue self.handle_ping(frame["payload"]) elif frame["opcode"] == 0xA: if not frame["fin"]: - self.shutdown(socket.SHUT_RDWR, 1003, "Unsupported: Fragmented pong") + self.shutdown( + socket.SHUT_RDWR, 1003, "Unsupported: Fragmented pong" + ) continue self.handle_pong(frame["payload"]) else: - self.shutdown(socket.SHUT_RDWR, 1003, "Unsupported: Unknown opcode 0x%02x" % frame["opcode"]) + self.shutdown( + socket.SHUT_RDWR, + 1003, + "Unsupported: Unknown opcode 0x%02x" % frame["opcode"], + ) raise WebSocketWantReadError @@ -731,7 +766,7 @@ def _queue_str(self, string): def _sendmsg(self, opcode, msg): # Sends a standard data message if self.client: - mask = b'' + mask = b"" for i in range(4): mask += random.randrange(256).to_bytes() frame = self._encode_hybi(opcode, msg, mask) @@ -755,35 +790,36 @@ def _unmask(self, buf, mask): plen = len(buf) pstart = 0 pend = plen - b = c = b'' + b = c = b"" if plen >= 4: - dtype=numpy.dtype('') + dtype = numpy.dtype("") mask = numpy.frombuffer(mask, dtype, count=1) data = numpy.frombuffer(buf, dtype, count=int(plen / 4)) - #b = numpy.bitwise_xor(data, mask).data + # b = numpy.bitwise_xor(data, mask).data b = numpy.bitwise_xor(data, mask).tobytes() if plen % 4: - dtype=numpy.dtype('B') - if sys.byteorder == 'big': - dtype = dtype.newbyteorder('>') + dtype = numpy.dtype("B") + if sys.byteorder == "big": + dtype = dtype.newbyteorder(">") mask = numpy.frombuffer(mask, dtype, count=(plen % 4)) - data = numpy.frombuffer(buf, dtype, - offset=plen - (plen % 4), count=(plen % 4)) + data = numpy.frombuffer( + buf, dtype, offset=plen - (plen % 4), count=(plen % 4) + ) c = numpy.bitwise_xor(data, mask).tobytes() return b + c else: # Slower fallback - data = array.array('B') + data = array.array("B") data.frombytes(buf) for i in range(len(data)): data[i] ^= mask[i % 4] return data.tobytes() def _encode_hybi(self, opcode, buf, mask_key=None, fin=True): - """ Encode a HyBi style WebSocket frame. + """Encode a HyBi style WebSocket frame. Optional opcode: 0x0 - continuation 0x1 - text frame @@ -793,7 +829,7 @@ def _encode_hybi(self, opcode, buf, mask_key=None, fin=True): 0xA - pong """ - b1 = opcode & 0x0f + b1 = opcode & 0x0F if fin: b1 |= 0x80 @@ -804,11 +840,11 @@ def _encode_hybi(self, opcode, buf, mask_key=None, fin=True): payload_len = len(buf) if payload_len <= 125: - header = struct.pack('>BB', b1, payload_len | mask_bit) + header = struct.pack(">BB", b1, payload_len | mask_bit) elif payload_len > 125 and payload_len < 65536: - header = struct.pack('>BBH', b1, 126 | mask_bit, payload_len) + header = struct.pack(">BBH", b1, 126 | mask_bit, payload_len) elif payload_len >= 65536: - header = struct.pack('>BBQ', b1, 127 | mask_bit, payload_len) + header = struct.pack(">BBQ", b1, 127 | mask_bit, payload_len) if mask_key is not None: return header + mask_key + buf @@ -816,7 +852,7 @@ def _encode_hybi(self, opcode, buf, mask_key=None, fin=True): return header + buf def _decode_hybi(self, buf): - """ Decode HyBi style WebSocket packets. + """Decode HyBi style WebSocket packets. Returns: {'fin' : boolean, 'opcode' : number, @@ -825,11 +861,7 @@ def _decode_hybi(self, buf): 'payload' : decoded_buffer} """ - f = {'fin' : 0, - 'opcode' : 0, - 'masked' : False, - 'length' : 0, - 'payload' : None} + f = {"fin": 0, "opcode": 0, "masked": False, "length": 0, "payload": None} blen = len(buf) hlen = 2 @@ -838,39 +870,38 @@ def _decode_hybi(self, buf): return None b1, b2 = struct.unpack(">BB", buf[:2]) - f['opcode'] = b1 & 0x0f - f['fin'] = not not (b1 & 0x80) - f['masked'] = not not (b2 & 0x80) + f["opcode"] = b1 & 0x0F + f["fin"] = not not (b1 & 0x80) + f["masked"] = not not (b2 & 0x80) - if f['masked']: + if f["masked"]: hlen += 4 if blen < hlen: return None - length = b2 & 0x7f + length = b2 & 0x7F if length == 126: hlen += 2 if blen < hlen: return None - length, = struct.unpack('>H', buf[2:4]) + (length,) = struct.unpack(">H", buf[2:4]) elif length == 127: hlen += 8 if blen < hlen: return None - length, = struct.unpack('>Q', buf[2:10]) + (length,) = struct.unpack(">Q", buf[2:10]) - f['length'] = hlen + length + f["length"] = hlen + length - if blen < f['length']: + if blen < f["length"]: return None - if f['masked']: + if f["masked"]: # unmask payload - mask_key = buf[hlen-4:hlen] - f['payload'] = self._unmask(buf[hlen:(hlen+length)], mask_key) + mask_key = buf[hlen - 4 : hlen] + f["payload"] = self._unmask(buf[hlen : (hlen + length)], mask_key) else: - f['payload'] = buf[hlen:(hlen+length)] + f["payload"] = buf[hlen : (hlen + length)] return f - diff --git a/websockify/websocketproxy.py b/websockify/websocketproxy.py index 5afec817..8076d032 100644 --- a/websockify/websocketproxy.py +++ b/websockify/websocketproxy.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -''' +""" A WebSocket to TCP socket proxy with support for "wss://" encryption. Copyright 2011 Joel Martin Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3) @@ -9,7 +9,7 @@ openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem as taken from http://docs.python.org/dev/library/ssl.html#certificates -''' +""" import signal, socket, optparse, time, os, sys, subprocess, logging, errno, ssl, stat from socketserver import ThreadingMixIn @@ -20,8 +20,8 @@ from websockify import auth_plugins as auth from urllib.parse import parse_qs, urlparse -class ProxyRequestHandler(websockifyserver.WebSockifyRequestHandler): +class ProxyRequestHandler(websockifyserver.WebSockifyRequestHandler): buffer_size = 65536 traffic_legend = """ @@ -38,7 +38,7 @@ class ProxyRequestHandler(websockifyserver.WebSockifyRequestHandler): def send_auth_error(self, ex): self.send_response(ex.code, ex.msg) - self.send_header('Content-Type', 'text/html') + self.send_header("Content-Type", "text/html") for name, val in ex.headers.items(): self.send_header(name, val) @@ -49,7 +49,7 @@ def validate_connection(self): return host, port = self.get_target(self.server.token_plugin) - if host == 'unix_socket': + if host == "unix_socket": self.server.unix_target = port else: @@ -62,7 +62,7 @@ def auth_connection(self): # clear out any existing SSL_ headers that the client might # have maliciously set - ssl_headers = [ h for h in self.headers if h.startswith('SSL_') ] + ssl_headers = [h for h in self.headers if h.startswith("SSL_")] for h in ssl_headers: del self.headers[h] @@ -70,19 +70,21 @@ def auth_connection(self): # get client certificate data client_cert_data = self.request.getpeercert() # extract subject information - client_cert_subject = client_cert_data['subject'] + client_cert_subject = client_cert_data["subject"] # flatten data structure client_cert_subject = dict([x[0] for x in client_cert_subject]) # add common name to headers (apache +StdEnvVars style) - self.headers['SSL_CLIENT_S_DN_CN'] = client_cert_subject['commonName'] + self.headers["SSL_CLIENT_S_DN_CN"] = client_cert_subject["commonName"] except (TypeError, AttributeError, KeyError): # not a SSL connection or client presented no certificate with valid data pass try: self.server.auth_plugin.authenticate( - headers=self.headers, target_host=self.server.target_host, - target_port=self.server.target_port) + headers=self.headers, + target_host=self.server.target_host, + target_port=self.server.target_port, + ) except auth.AuthenticationError: ex = sys.exc_info()[1] self.send_auth_error(ex) @@ -96,26 +98,37 @@ def new_websocket_client(self): # Connect to the target if self.server.wrap_cmd: - msg = "connecting to command: '%s' (port %s)" % (" ".join(self.server.wrap_cmd), self.server.target_port) + msg = "connecting to command: '%s' (port %s)" % ( + " ".join(self.server.wrap_cmd), + self.server.target_port, + ) elif self.server.unix_target: msg = "connecting to unix socket: %s" % self.server.unix_target else: msg = "connecting to: %s:%s" % ( - self.server.target_host, self.server.target_port) + self.server.target_host, + self.server.target_port, + ) if self.server.ssl_target: msg += " (using SSL)" self.log_message(msg) try: - tsock = websockifyserver.WebSockifyServer.socket(self.server.target_host, - self.server.target_port, - connect=True, - use_ssl=self.server.ssl_target, - unix_socket=self.server.unix_target) + tsock = websockifyserver.WebSockifyServer.socket( + self.server.target_host, + self.server.target_port, + connect=True, + use_ssl=self.server.ssl_target, + unix_socket=self.server.unix_target, + ) except Exception as e: - self.log_message("Failed to connect to %s:%s: %s", - self.server.target_host, self.server.target_port, e) + self.log_message( + "Failed to connect to %s:%s: %s", + self.server.target_host, + self.server.target_port, + e, + ) raise self.CClose(1011, "Failed to connect to downstream server") # Option unavailable when listening to unix socket @@ -134,8 +147,11 @@ def new_websocket_client(self): tsock.shutdown(socket.SHUT_RDWR) tsock.close() if self.verbose: - self.log_message("%s:%s: Closed target", - self.server.target_host, self.server.target_port) + self.log_message( + "%s:%s: Closed target", + self.server.target_host, + self.server.target_port, + ) def get_target(self, target_plugin): """ @@ -149,20 +165,20 @@ def get_target(self, target_plugin): if self.host_token: # Use hostname as token - token = self.headers.get('Host') + token = self.headers.get("Host") # Remove port from hostname, as it'll always be the one where # websockify listens (unless something between the client and # websockify is redirecting traffic, but that's beside the point) if token: - token = token.partition(':')[0] + token = token.partition(":")[0] else: # Extract the token parameter from url - args = parse_qs(urlparse(self.path)[4]) # 4 is the query from url + args = parse_qs(urlparse(self.path)[4]) # 4 is the query from url - if 'token' in args and len(args['token']): - token = args['token'][0].rstrip('\n') + if "token" in args and len(args["token"]): + token = args["token"][0].rstrip("\n") else: token = None @@ -200,13 +216,15 @@ def do_proxy(self, target): self.heartbeat = now + self.server.heartbeat self.send_ping() - if tqueue: wlist.append(target) - if cqueue or c_pend: wlist.append(self.request) + if tqueue: + wlist.append(target) + if cqueue or c_pend: + wlist.append(self.request) try: ins, outs, excepts = select.select(rlist, wlist, [], 1) except OSError: exc = sys.exc_info()[1] - if hasattr(exc, 'errno'): + if hasattr(exc, "errno"): err = exc.errno else: err = exc[0] @@ -216,7 +234,8 @@ def do_proxy(self, target): else: continue - if excepts: raise Exception("Socket exception") + if excepts: + raise Exception("Socket exception") if self.request in outs: # Send queued target data to the client @@ -230,8 +249,7 @@ def do_proxy(self, target): tqueue.extend(bufs) if closed: - - while (len(tqueue) != 0): + while len(tqueue) != 0: # Send queued client data to the target dat = tqueue.pop(0) sent = target.send(dat) @@ -244,10 +262,12 @@ def do_proxy(self, target): # TODO: What about blocking on client socket? if self.verbose: - self.log_message("%s:%s: Client closed connection", - self.server.target_host, self.server.target_port) - raise self.CClose(closed['code'], closed['reason']) - + self.log_message( + "%s:%s: Client closed connection", + self.server.target_host, + self.server.target_port, + ) + raise self.CClose(closed["code"], closed["reason"]) if target in outs: # Send queued client data to the target @@ -260,29 +280,31 @@ def do_proxy(self, target): tqueue.insert(0, dat[sent:]) self.print_traffic(".>") - if target in ins: # Receive target data, encode it and queue for client buf = target.recv(self.buffer_size) if len(buf) == 0: - # Target socket closed, flushing queues and closing client-side websocket # Send queued target data to the client if len(cqueue) != 0: c_pend = True - while(c_pend): + while c_pend: c_pend = self.send_frames(cqueue) cqueue = [] if self.verbose: - self.log_message("%s:%s: Target closed connection", - self.server.target_host, self.server.target_port) + self.log_message( + "%s:%s: Target closed connection", + self.server.target_host, + self.server.target_port, + ) raise self.CClose(1000, "Target closed") cqueue.append(buf) self.print_traffic("{") + class WebSocketProxy(websockifyserver.WebSockifyServer): """ Proxy traffic to and from a WebSockets client to a normal TCP @@ -293,27 +315,29 @@ class WebSocketProxy(websockifyserver.WebSockifyServer): def __init__(self, RequestHandlerClass=ProxyRequestHandler, *args, **kwargs): # Save off proxy specific options - self.target_host = kwargs.pop('target_host', None) - self.target_port = kwargs.pop('target_port', None) - self.wrap_cmd = kwargs.pop('wrap_cmd', None) - self.wrap_mode = kwargs.pop('wrap_mode', None) - self.unix_target = kwargs.pop('unix_target', None) - self.ssl_target = kwargs.pop('ssl_target', None) - self.heartbeat = kwargs.pop('heartbeat', None) - - self.token_plugin = kwargs.pop('token_plugin', None) - self.host_token = kwargs.pop('host_token', None) - self.auth_plugin = kwargs.pop('auth_plugin', None) + self.target_host = kwargs.pop("target_host", None) + self.target_port = kwargs.pop("target_port", None) + self.wrap_cmd = kwargs.pop("wrap_cmd", None) + self.wrap_mode = kwargs.pop("wrap_mode", None) + self.unix_target = kwargs.pop("unix_target", None) + self.ssl_target = kwargs.pop("ssl_target", None) + self.heartbeat = kwargs.pop("heartbeat", None) + + self.token_plugin = kwargs.pop("token_plugin", None) + self.host_token = kwargs.pop("host_token", None) + self.auth_plugin = kwargs.pop("auth_plugin", None) # Last 3 timestamps command was run - self.wrap_times = [0, 0, 0] + self.wrap_times = [0, 0, 0] if self.wrap_cmd: wsdir = os.path.dirname(sys.argv[0]) - rebinder_path = [os.path.join(wsdir, "..", "lib"), - os.path.join(wsdir, "..", "lib", "websockify"), - os.path.join(wsdir, ".."), - wsdir] + rebinder_path = [ + os.path.join(wsdir, "..", "lib"), + os.path.join(wsdir, "..", "lib", "websockify"), + os.path.join(wsdir, ".."), + wsdir, + ] self.rebinder = None for rdir in rebinder_path: @@ -329,17 +353,22 @@ def __init__(self, RequestHandlerClass=ProxyRequestHandler, *args, **kwargs): self.target_host = "127.0.0.1" # Loopback # Find a free high port sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.bind(('', 0)) + sock.bind(("", 0)) self.target_port = sock.getsockname()[1] sock.close() # Insert rebinder at the head of the (possibly empty) LD_PRELOAD pathlist - ld_preloads = filter(None, [ self.rebinder, os.environ.get("LD_PRELOAD", None) ]) - - os.environ.update({ - "LD_PRELOAD": os.pathsep.join(ld_preloads), - "REBIND_OLD_PORT": str(kwargs['listen_port']), - "REBIND_NEW_PORT": str(self.target_port)}) + ld_preloads = filter( + None, [self.rebinder, os.environ.get("LD_PRELOAD", None)] + ) + + os.environ.update( + { + "LD_PRELOAD": os.pathsep.join(ld_preloads), + "REBIND_OLD_PORT": str(kwargs["listen_port"]), + "REBIND_NEW_PORT": str(self.target_port), + } + ) super().__init__(RequestHandlerClass, *args, **kwargs) @@ -348,7 +377,8 @@ def run_wrap_cmd(self): self.wrap_times.append(time.time()) self.wrap_times.pop(0) self.cmd = subprocess.Popen( - self.wrap_cmd, env=os.environ, preexec_fn=_subprocess_setup) + self.wrap_cmd, env=os.environ, preexec_fn=_subprocess_setup + ) self.spawn_message = True def started(self): @@ -371,10 +401,11 @@ def started(self): if self.token_plugin: msg = " - proxying from %s to targets generated by %s" % ( - src_string, type(self.token_plugin).__name__) + src_string, + type(self.token_plugin).__name__, + ) else: - msg = " - proxying from %s to %s" % ( - src_string, dst_string) + msg = " - proxying from %s to %s" % (src_string, dst_string) if self.ssl_target: msg += " (using SSL)" @@ -401,7 +432,7 @@ def poll(self): sys.exit(ret) elif self.wrap_mode == "respawn": now = time.time() - avg = sum(self.wrap_times)/len(self.wrap_times) + avg = sum(self.wrap_times) / len(self.wrap_times) if (now - avg) < 10: # 3 times in the last 10 seconds if self.spawn_message: @@ -418,15 +449,25 @@ def _subprocess_setup(): SSL_OPTIONS = { - 'default': ssl.OP_ALL, - 'tlsv1_1': ssl.PROTOCOL_SSLv23 | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | - ssl.OP_NO_TLSv1, - 'tlsv1_2': ssl.PROTOCOL_SSLv23 | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | - ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1, - 'tlsv1_3': ssl.PROTOCOL_SSLv23 | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | - ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_TLSv1_2, + "default": ssl.OP_ALL, + "tlsv1_1": ssl.PROTOCOL_SSLv23 + | ssl.OP_NO_SSLv2 + | ssl.OP_NO_SSLv3 + | ssl.OP_NO_TLSv1, + "tlsv1_2": ssl.PROTOCOL_SSLv23 + | ssl.OP_NO_SSLv2 + | ssl.OP_NO_SSLv3 + | ssl.OP_NO_TLSv1 + | ssl.OP_NO_TLSv1_1, + "tlsv1_3": ssl.PROTOCOL_SSLv23 + | ssl.OP_NO_SSLv2 + | ssl.OP_NO_SSLv3 + | ssl.OP_NO_TLSv1 + | ssl.OP_NO_TLSv1_1 + | ssl.OP_NO_TLSv1_2, } + def select_ssl_version(version): """Returns SSL options for the most secure TSL version available on this Python version""" @@ -439,11 +480,11 @@ def select_ssl_version(version): keys.sort() fallback = keys[-1] logger = logging.getLogger(WebSocketProxy.log_prefix) - logger.warn("TLS version %s unsupported. Falling back to %s", - version, fallback) + logger.warn("TLS version %s unsupported. Falling back to %s", version, fallback) return SSL_OPTIONS[fallback] + def websockify_init(): # Setup basic logging to stderr. stderr_handler = logging.StreamHandler() @@ -464,105 +505,198 @@ def websockify_init(): usage += "\n %prog [options]" usage += " [source_addr:]source_port -- WRAP_COMMAND_LINE" parser = optparse.OptionParser(usage=usage) - parser.add_option("--verbose", "-v", action="store_true", - help="verbose messages") - parser.add_option("--traffic", action="store_true", - help="per frame traffic") - parser.add_option("--record", - help="record sessions to FILE.[session_number]", metavar="FILE") - parser.add_option("--daemon", "-D", - dest="daemon", action="store_true", - help="become a daemon (background process)") - parser.add_option("--run-once", action="store_true", - help="handle a single WebSocket connection and exit") - parser.add_option("--timeout", type=int, default=0, - help="after TIMEOUT seconds exit when not connected") - parser.add_option("--idle-timeout", type=int, default=0, - help="server exits after TIMEOUT seconds if there are no " - "active connections") - parser.add_option("--cert", default="self.pem", - help="SSL certificate file") - parser.add_option("--key", default=None, - help="SSL key file (if separate from cert)") - parser.add_option("--key-password", default=None, - help="SSL key password") - parser.add_option("--ssl-only", action="store_true", - help="disallow non-encrypted client connections") - parser.add_option("--ssl-target", action="store_true", - help="connect to SSL target as SSL client") - parser.add_option("--verify-client", action="store_true", - help="require encrypted client to present a valid certificate " - "(needs Python 2.7.9 or newer or Python 3.4 or newer)") - parser.add_option("--cafile", metavar="FILE", - help="file of concatenated certificates of authorities trusted " - "for validating clients (only effective with --verify-client). " - "If omitted, system default list of CAs is used.") - parser.add_option("--ssl-version", type="choice", default="default", - choices=["default", "tlsv1_1", "tlsv1_2", "tlsv1_3"], action="store", - help="minimum TLS version to use (default, tlsv1_1, tlsv1_2, tlsv1_3)") - parser.add_option("--ssl-ciphers", action="store", - help="list of ciphers allowed for connection. For a list of " - "supported ciphers run `openssl ciphers`") - parser.add_option("--unix-listen", - help="listen to unix socket", metavar="FILE", default=None) - parser.add_option("--unix-listen-mode", default=None, - help="specify mode for unix socket (defaults to 0600)") - parser.add_option("--unix-target", - help="connect to unix socket target", metavar="FILE") - parser.add_option("--inetd", - help="inetd mode, receive listening socket from stdin", action="store_true") - parser.add_option("--web", default=None, metavar="DIR", - help="run webserver on same port. Serve files from DIR.") - parser.add_option("--web-auth", action="store_true", - help="require authentication to access webserver.") - parser.add_option("--wrap-mode", default="exit", metavar="MODE", - choices=["exit", "ignore", "respawn"], - help="action to take when the wrapped program exits " - "or daemonizes: exit (default), ignore, respawn") - parser.add_option("--prefer-ipv6", "-6", - action="store_true", dest="source_is_ipv6", - help="prefer IPv6 when resolving source_addr") - parser.add_option("--libserver", action="store_true", - help="use Python library SocketServer engine") - parser.add_option("--target-config", metavar="FILE", - dest="target_cfg", - help="Configuration file containing valid targets " - "in the form 'token: host:port' or, alternatively, a " - "directory containing configuration files of this form " - "(DEPRECATED: use `--token-plugin TokenFile --token-source " - " path/to/token/file` instead)") - parser.add_option("--token-plugin", default=None, metavar="CLASS", - help="use a Python class, usually one from websockify.token_plugins, " - "such as TokenFile, to process tokens into host:port pairs") - parser.add_option("--token-source", default=None, metavar="ARG", - help="an argument to be passed to the token plugin " - "on instantiation") - parser.add_option("--host-token", action="store_true", - help="use the host HTTP header as token instead of the " - "token URL query parameter") - parser.add_option("--auth-plugin", default=None, metavar="CLASS", - help="use a Python class, usually one from websockify.auth_plugins, " - "such as BasicHTTPAuth, to determine if a connection is allowed") - parser.add_option("--auth-source", default=None, metavar="ARG", - help="an argument to be passed to the auth plugin " - "on instantiation") - parser.add_option("--heartbeat", type=int, default=0, metavar="INTERVAL", - help="send a ping to the client every INTERVAL seconds") - parser.add_option("--log-file", metavar="FILE", - dest="log_file", - help="File where logs will be saved") - parser.add_option("--syslog", default=None, metavar="SERVER", - help="Log to syslog server. SERVER can be local socket, " - "such as /dev/log, or a UDP host:port pair.") - parser.add_option("--legacy-syslog", action="store_true", - help="Use the old syslog protocol instead of RFC 5424. " - "Use this if the messages produced by websockify seem abnormal.") - parser.add_option("--file-only", action="store_true", - help="use this to disable directory listings in web server.") + parser.add_option("--verbose", "-v", action="store_true", help="verbose messages") + parser.add_option("--traffic", action="store_true", help="per frame traffic") + parser.add_option( + "--record", help="record sessions to FILE.[session_number]", metavar="FILE" + ) + parser.add_option( + "--daemon", + "-D", + dest="daemon", + action="store_true", + help="become a daemon (background process)", + ) + parser.add_option( + "--run-once", + action="store_true", + help="handle a single WebSocket connection and exit", + ) + parser.add_option( + "--timeout", + type=int, + default=0, + help="after TIMEOUT seconds exit when not connected", + ) + parser.add_option( + "--idle-timeout", + type=int, + default=0, + help="server exits after TIMEOUT seconds if there are no active connections", + ) + parser.add_option("--cert", default="self.pem", help="SSL certificate file") + parser.add_option( + "--key", default=None, help="SSL key file (if separate from cert)" + ) + parser.add_option("--key-password", default=None, help="SSL key password") + parser.add_option( + "--ssl-only", + action="store_true", + help="disallow non-encrypted client connections", + ) + parser.add_option( + "--ssl-target", action="store_true", help="connect to SSL target as SSL client" + ) + parser.add_option( + "--verify-client", + action="store_true", + help="require encrypted client to present a valid certificate " + "(needs Python 2.7.9 or newer or Python 3.4 or newer)", + ) + parser.add_option( + "--cafile", + metavar="FILE", + help="file of concatenated certificates of authorities trusted " + "for validating clients (only effective with --verify-client). " + "If omitted, system default list of CAs is used.", + ) + parser.add_option( + "--ssl-version", + type="choice", + default="default", + choices=["default", "tlsv1_1", "tlsv1_2", "tlsv1_3"], + action="store", + help="minimum TLS version to use (default, tlsv1_1, tlsv1_2, tlsv1_3)", + ) + parser.add_option( + "--ssl-ciphers", + action="store", + help="list of ciphers allowed for connection. For a list of " + "supported ciphers run `openssl ciphers`", + ) + parser.add_option( + "--unix-listen", help="listen to unix socket", metavar="FILE", default=None + ) + parser.add_option( + "--unix-listen-mode", + default=None, + help="specify mode for unix socket (defaults to 0600)", + ) + parser.add_option( + "--unix-target", help="connect to unix socket target", metavar="FILE" + ) + parser.add_option( + "--inetd", + help="inetd mode, receive listening socket from stdin", + action="store_true", + ) + parser.add_option( + "--web", + default=None, + metavar="DIR", + help="run webserver on same port. Serve files from DIR.", + ) + parser.add_option( + "--web-auth", + action="store_true", + help="require authentication to access webserver.", + ) + parser.add_option( + "--wrap-mode", + default="exit", + metavar="MODE", + choices=["exit", "ignore", "respawn"], + help="action to take when the wrapped program exits " + "or daemonizes: exit (default), ignore, respawn", + ) + parser.add_option( + "--prefer-ipv6", + "-6", + action="store_true", + dest="source_is_ipv6", + help="prefer IPv6 when resolving source_addr", + ) + parser.add_option( + "--libserver", + action="store_true", + help="use Python library SocketServer engine", + ) + parser.add_option( + "--target-config", + metavar="FILE", + dest="target_cfg", + help="Configuration file containing valid targets " + "in the form 'token: host:port' or, alternatively, a " + "directory containing configuration files of this form " + "(DEPRECATED: use `--token-plugin TokenFile --token-source " + " path/to/token/file` instead)", + ) + parser.add_option( + "--token-plugin", + default=None, + metavar="CLASS", + help="use a Python class, usually one from websockify.token_plugins, " + "such as TokenFile, to process tokens into host:port pairs", + ) + parser.add_option( + "--token-source", + default=None, + metavar="ARG", + help="an argument to be passed to the token plugin on instantiation", + ) + parser.add_option( + "--host-token", + action="store_true", + help="use the host HTTP header as token instead of the " + "token URL query parameter", + ) + parser.add_option( + "--auth-plugin", + default=None, + metavar="CLASS", + help="use a Python class, usually one from websockify.auth_plugins, " + "such as BasicHTTPAuth, to determine if a connection is allowed", + ) + parser.add_option( + "--auth-source", + default=None, + metavar="ARG", + help="an argument to be passed to the auth plugin on instantiation", + ) + parser.add_option( + "--heartbeat", + type=int, + default=0, + metavar="INTERVAL", + help="send a ping to the client every INTERVAL seconds", + ) + parser.add_option( + "--log-file", + metavar="FILE", + dest="log_file", + help="File where logs will be saved", + ) + parser.add_option( + "--syslog", + default=None, + metavar="SERVER", + help="Log to syslog server. SERVER can be local socket, " + "such as /dev/log, or a UDP host:port pair.", + ) + parser.add_option( + "--legacy-syslog", + action="store_true", + help="Use the old syslog protocol instead of RFC 5424. " + "Use this if the messages produced by websockify seem abnormal.", + ) + parser.add_option( + "--file-only", + action="store_true", + help="use this to disable directory listings in web server.", + ) (opts, args) = parser.parse_args() - # Validate options. if opts.token_source and not opts.token_plugin: @@ -583,11 +717,9 @@ def websockify_init(): if opts.legacy_syslog and not opts.syslog: parser.error("You must use --syslog to use --legacy-syslog") - opts.ssl_options = select_ssl_version(opts.ssl_version) del opts.ssl_version - if opts.log_file: # Setup logging to user-specified file. opts.log_file = os.path.abspath(opts.log_file) @@ -601,9 +733,9 @@ def websockify_init(): if opts.syslog: # Determine how to connect to syslog... - if opts.syslog.count(':'): + if opts.syslog.count(":"): # User supplied a host:port pair. - syslog_host, syslog_port = opts.syslog.rsplit(':', 1) + syslog_host, syslog_port = opts.syslog.rsplit(":", 1) try: syslog_port = int(syslog_port) except ValueError: @@ -622,10 +754,12 @@ def websockify_init(): syslog_facility = WebsockifySysLogHandler.LOG_USER # Start logging to syslog. - syslog_handler = WebsockifySysLogHandler(address=syslog_dest, - facility=syslog_facility, - ident='websockify', - legacy=opts.legacy_syslog) + syslog_handler = WebsockifySysLogHandler( + address=syslog_dest, + facility=syslog_facility, + ident="websockify", + legacy=opts.legacy_syslog, + ) syslog_handler.setLevel(logging.DEBUG) syslog_handler.setFormatter(log_formatter) root = logging.getLogger() @@ -638,24 +772,23 @@ def websockify_init(): root = logging.getLogger() root.setLevel(logging.DEBUG) - # Transform to absolute path as daemon may chdir if opts.target_cfg: opts.target_cfg = os.path.abspath(opts.target_cfg) if opts.target_cfg: - opts.token_plugin = 'TokenFile' + opts.token_plugin = "TokenFile" opts.token_source = opts.target_cfg del opts.target_cfg - if sys.argv.count('--'): + if sys.argv.count("--"): opts.wrap_cmd = args[1:] else: opts.wrap_cmd = None if not websockifyserver.ssl and opts.ssl_target: - parser.error("SSL target requested and Python SSL module not loaded."); + parser.error("SSL target requested and Python SSL module not loaded.") if opts.ssl_only and not os.path.exists(opts.cert): parser.error("SSL only and %s not found" % opts.cert) @@ -677,11 +810,11 @@ def websockify_init(): parser.error("Too few arguments") arg = args.pop(0) # Parse host:port and convert ports to numbers - if arg.count(':') > 0: - opts.listen_host, opts.listen_port = arg.rsplit(':', 1) - opts.listen_host = opts.listen_host.strip('[]') + if arg.count(":") > 0: + opts.listen_host, opts.listen_port = arg.rsplit(":", 1) + opts.listen_host = opts.listen_host.strip("[]") else: - opts.listen_host, opts.listen_port = '', arg + opts.listen_host, opts.listen_port = "", arg try: opts.listen_port = int(opts.listen_port) @@ -697,9 +830,9 @@ def websockify_init(): if len(args) < 1: parser.error("Too few arguments") arg = args.pop(0) - if arg.count(':') > 0: - opts.target_host, opts.target_port = arg.rsplit(':', 1) - opts.target_host = opts.target_host.strip('[]') + if arg.count(":") > 0: + opts.target_host, opts.target_port = arg.rsplit(":", 1) + opts.target_host = opts.target_host.strip("[]") else: parser.error("Error parsing target") @@ -712,11 +845,10 @@ def websockify_init(): parser.error("Too many arguments") if opts.token_plugin is not None: - if '.' not in opts.token_plugin: - opts.token_plugin = ( - 'websockify.token_plugins.%s' % opts.token_plugin) + if "." not in opts.token_plugin: + opts.token_plugin = "websockify.token_plugins.%s" % opts.token_plugin - token_plugin_module, token_plugin_cls = opts.token_plugin.rsplit('.', 1) + token_plugin_module, token_plugin_cls = opts.token_plugin.rsplit(".", 1) __import__(token_plugin_module) token_plugin_cls = getattr(sys.modules[token_plugin_module], token_plugin_cls) @@ -726,10 +858,10 @@ def websockify_init(): del opts.token_source if opts.auth_plugin is not None: - if '.' not in opts.auth_plugin: - opts.auth_plugin = 'websockify.auth_plugins.%s' % opts.auth_plugin + if "." not in opts.auth_plugin: + opts.auth_plugin = "websockify.auth_plugins.%s" % opts.auth_plugin - auth_plugin_module, auth_plugin_cls = opts.auth_plugin.rsplit('.', 1) + auth_plugin_module, auth_plugin_cls = opts.auth_plugin.rsplit(".", 1) __import__(auth_plugin_module) auth_plugin_cls = getattr(sys.modules[auth_plugin_module], auth_plugin_cls) @@ -759,32 +891,32 @@ class LibProxyServer(ThreadingMixIn, HTTPServer): def __init__(self, RequestHandlerClass=ProxyRequestHandler, **kwargs): # Save off proxy specific options - self.target_host = kwargs.pop('target_host', None) - self.target_port = kwargs.pop('target_port', None) - self.wrap_cmd = kwargs.pop('wrap_cmd', None) - self.wrap_mode = kwargs.pop('wrap_mode', None) - self.unix_target = kwargs.pop('unix_target', None) - self.ssl_target = kwargs.pop('ssl_target', None) - self.token_plugin = kwargs.pop('token_plugin', None) - self.auth_plugin = kwargs.pop('auth_plugin', None) - self.heartbeat = kwargs.pop('heartbeat', None) + self.target_host = kwargs.pop("target_host", None) + self.target_port = kwargs.pop("target_port", None) + self.wrap_cmd = kwargs.pop("wrap_cmd", None) + self.wrap_mode = kwargs.pop("wrap_mode", None) + self.unix_target = kwargs.pop("unix_target", None) + self.ssl_target = kwargs.pop("ssl_target", None) + self.token_plugin = kwargs.pop("token_plugin", None) + self.auth_plugin = kwargs.pop("auth_plugin", None) + self.heartbeat = kwargs.pop("heartbeat", None) self.token_plugin = None self.auth_plugin = None self.daemon = False # Server configuration - listen_host = kwargs.pop('listen_host', '') - listen_port = kwargs.pop('listen_port', None) - web = kwargs.pop('web', '') + listen_host = kwargs.pop("listen_host", "") + listen_port = kwargs.pop("listen_port", None) + web = kwargs.pop("web", "") # Configuration affecting base request handler - self.only_upgrade = not web - self.verbose = kwargs.pop('verbose', False) - record = kwargs.pop('record', '') + self.only_upgrade = not web + self.verbose = kwargs.pop("verbose", False) + record = kwargs.pop("record", "") if record: self.record = os.path.abspath(record) - self.run_once = kwargs.pop('run_once', False) + self.run_once = kwargs.pop("run_once", False) self.handler_id = 0 for arg in kwargs.keys(): @@ -795,12 +927,11 @@ def __init__(self, RequestHandlerClass=ProxyRequestHandler, **kwargs): super().__init__((listen_host, listen_port), RequestHandlerClass) - def process_request(self, request, client_address): """Override process_request to implement a counter""" self.handler_id += 1 super().process_request(request, client_address) -if __name__ == '__main__': +if __name__ == "__main__": websockify_init() diff --git a/websockify/websocketserver.py b/websockify/websocketserver.py index 4e62f2ec..cd86e2d5 100644 --- a/websockify/websocketserver.py +++ b/websockify/websocketserver.py @@ -1,19 +1,25 @@ #!/usr/bin/env python -''' +""" Python WebSocket server base Copyright 2011 Joel Martin Copyright 2016-2018 Pierre Ossman Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3) -''' +""" import sys from http.server import BaseHTTPRequestHandler, HTTPServer -from websockify.websocket import WebSocket, WebSocketWantReadError, WebSocketWantWriteError +from websockify.websocket import ( + WebSocket, + WebSocketWantReadError, + WebSocketWantWriteError, +) + class HttpWebSocket(WebSocket): """Class to glue websocket and http request functionality together""" + def __init__(self, request_handler): super().__init__() @@ -62,8 +68,10 @@ def _websocket_do_GET(self): # Checks if it is a websocket request and redirects self.do_GET = self._real_do_GET - if (self.headers.get('upgrade') and - self.headers.get('upgrade').lower() == 'websocket'): + if ( + self.headers.get("upgrade") + and self.headers.get("upgrade").lower() == "websocket" + ): self.handle_upgrade() else: self.do_GET() @@ -93,18 +101,20 @@ def handle_upgrade(self): def handle_websocket(self): """Handle a WebSocket connection. - + This is called when the WebSocket is ready to be used. A sub-class should perform the necessary communication here and return once done. """ pass + # Convenient ready made classes -class WebSocketRequestHandler(WebSocketRequestHandlerMixIn, - BaseHTTPRequestHandler): + +class WebSocketRequestHandler(WebSocketRequestHandlerMixIn, BaseHTTPRequestHandler): pass + class WebSocketServer(HTTPServer): pass diff --git a/websockify/websockifyserver.py b/websockify/websockifyserver.py index 78613fbd..fe8a85ac 100644 --- a/websockify/websockifyserver.py +++ b/websockify/websockifyserver.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -''' +""" Python WebSocket server base with support for "wss://" encryption. Copyright 2011 Joel Martin Copyright 2016 Pierre Ossman @@ -10,35 +10,39 @@ openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem as taken from http://docs.python.org/dev/library/ssl.html#certificates -''' +""" import os, sys, time, errno, signal, socket, select, logging import multiprocessing from http.server import SimpleHTTPRequestHandler # Degraded functionality if these imports are missing -for mod, msg in [('ssl', 'TLS/SSL/wss is disabled'), - ('resource', 'daemonizing is disabled')]: +for mod, msg in [ + ("ssl", "TLS/SSL/wss is disabled"), + ("resource", "daemonizing is disabled"), +]: try: globals()[mod] = __import__(mod) except ImportError: globals()[mod] = None print("WARNING: no '%s' module, %s" % (mod, msg)) -if sys.platform == 'win32': +if sys.platform == "win32": # make sockets pickle-able/inheritable import multiprocessing.reduction from websockify.websocket import WebSocketWantReadError, WebSocketWantWriteError from websockify.websocketserver import WebSocketRequestHandlerMixIn + class CompatibleWebSocket(WebSocketRequestHandlerMixIn.SocketClass): def select_subprotocol(self, protocols): # Handle old websockify clients that still specify a sub-protocol - if 'binary' in protocols: - return 'binary' + if "binary" in protocols: + return "binary" else: - return '' + return "" + # HTTP handler with WebSocket upgrade support class WebSockifyRequestHandler(WebSocketRequestHandlerMixIn, SimpleHTTPRequestHandler): @@ -56,6 +60,7 @@ class WebSockifyRequestHandler(WebSocketRequestHandlerMixIn, SimpleHTTPRequestHa * run_once: Handle a single request * handler_id: A sequence number for this connection, appended to record filename """ + server_version = "WebSockify" protocol_version = "HTTP/1.1" @@ -73,7 +78,7 @@ def __init__(self, req, addr, server): self.daemon = getattr(server, "daemon", False) self.record = getattr(server, "record", False) self.run_once = getattr(server, "run_once", False) - self.rec = None + self.rec = None self.handler_id = getattr(server, "handler_id", False) self.file_only = getattr(server, "file_only", False) self.traffic = getattr(server, "traffic", False) @@ -87,30 +92,33 @@ def __init__(self, req, addr, server): super().__init__(req, addr, server) def log_message(self, format, *args): - self.logger.info("%s - - [%s] %s" % (self.client_address[0], self.log_date_time_string(), format % args)) + self.logger.info( + "%s - - [%s] %s" + % (self.client_address[0], self.log_date_time_string(), format % args) + ) # # WebSocketRequestHandler logging/output functions # def print_traffic(self, token="."): - """ Show traffic flow mode. """ + """Show traffic flow mode.""" if self.traffic: sys.stdout.write(token) sys.stdout.flush() def msg(self, msg, *args, **kwargs): - """ Output message with handler_id prefix. """ + """Output message with handler_id prefix.""" prefix = "% 3d: " % self.handler_id self.logger.log(logging.INFO, "%s%s" % (prefix, msg), *args, **kwargs) def vmsg(self, msg, *args, **kwargs): - """ Same as msg() but as debug. """ + """Same as msg() but as debug.""" prefix = "% 3d: " % self.handler_id self.logger.log(logging.DEBUG, "%s%s" % (prefix, msg), *args, **kwargs) def warn(self, msg, *args, **kwargs): - """ Same as msg() but as warning. """ + """Same as msg() but as warning.""" prefix = "% 3d: " % self.handler_id self.logger.log(logging.WARN, "%s%s" % (prefix, msg), *args, **kwargs) @@ -118,19 +126,24 @@ def warn(self, msg, *args, **kwargs): # Main WebSocketRequestHandler methods # def send_frames(self, bufs=None): - """ Encode and send WebSocket frames. Any frames already + """Encode and send WebSocket frames. Any frames already queued will be sent first. If buf is not set then only queued frames will be sent. Returns True if any frames could not be fully sent, in which case the caller should call again when - the socket is ready. """ + the socket is ready.""" - tdelta = int(time.time()*1000) - self.start_time + tdelta = int(time.time() * 1000) - self.start_time if bufs: for buf in bufs: if self.rec: # Python 3 compatible conversion - bufstr = buf.decode('latin1').encode('unicode_escape').decode('ascii').replace("'", "\\'") + bufstr = ( + buf.decode("latin1") + .encode("unicode_escape") + .decode("ascii") + .replace("'", "\\'") + ) self.rec.write("'{{{0}{{{1}',\n".format(tdelta, bufstr)) self.send_parts.append(buf) @@ -147,7 +160,7 @@ def send_frames(self, bufs=None): return False def recv_frames(self): - """ Receive and decode WebSocket frames. + """Receive and decode WebSocket frames. Returns: (bufs_list, closed_string) @@ -155,7 +168,7 @@ def recv_frames(self): closed = False bufs = [] - tdelta = int(time.time()*1000) - self.start_time + tdelta = int(time.time() * 1000) - self.start_time while True: try: @@ -165,15 +178,22 @@ def recv_frames(self): break if buf is None: - closed = {'code': self.request.close_code, - 'reason': self.request.close_reason} + closed = { + "code": self.request.close_code, + "reason": self.request.close_reason, + } return bufs, closed self.print_traffic("}") if self.rec: # Python 3 compatible conversion - bufstr = buf.decode('latin1').encode('unicode_escape').decode('ascii').replace("'", "\\'") + bufstr = ( + buf.decode("latin1") + .encode("unicode_escape") + .decode("ascii") + .replace("'", "\\'") + ) self.rec.write("'}}{0}}}{1}',\n".format(tdelta, bufstr)) bufs.append(buf) @@ -183,16 +203,16 @@ def recv_frames(self): return bufs, closed - def send_close(self, code=1000, reason=''): - """ Send a WebSocket orderly close frame. """ + def send_close(self, code=1000, reason=""): + """Send a WebSocket orderly close frame.""" self.request.shutdown(socket.SHUT_RDWR, code, reason) - def send_pong(self, data=b''): - """ Send a WebSocket pong frame. """ + def send_pong(self, data=b""): + """Send a WebSocket pong frame.""" self.request.pong(data) - def send_ping(self, data=b''): - """ Send a WebSocket ping frame. """ + def send_ping(self, data=b""): + """Send a WebSocket ping frame.""" self.request.ping(data) def handle_upgrade(self): @@ -207,8 +227,8 @@ def handle_websocket(self): self.server.ws_connection = True # Initialize per client settings self.send_parts = [] - self.recv_part = None - self.start_time = int(time.time()*1000) + self.recv_part = None + self.start_time = int(time.time() * 1000) # client_address is empty with, say, UNIX domain sockets client_addr = "" @@ -224,17 +244,15 @@ def handle_websocket(self): else: self.stype = "Plain non-SSL (ws://)" - self.log_message("%s: %s WebSocket connection", client_addr, - self.stype) - if self.path != '/': + self.log_message("%s: %s WebSocket connection", client_addr, self.stype) + if self.path != "/": self.log_message("%s: Path: '%s'", client_addr, self.path) if self.record: # Record raw frame data as JavaScript array - fname = "%s.%s" % (self.record, - self.handler_id) + fname = "%s.%s" % (self.record, self.handler_id) self.log_message("opening record file: %s", fname) - self.rec = open(fname, 'w+') + self.rec = open(fname, "w+") self.rec.write("var VNC_frame_data = [\n") try: @@ -261,15 +279,17 @@ def list_directory(self, path): return super().list_directory(path) def new_websocket_client(self): - """ Do something with a WebSockets client connection. """ - raise Exception("WebSocketRequestHandler.new_websocket_client() must be overloaded") + """Do something with a WebSockets client connection.""" + raise Exception( + "WebSocketRequestHandler.new_websocket_client() must be overloaded" + ) def validate_connection(self): - """ Ensure that the connection has a valid token, and set the target. """ + """Ensure that the connection has a valid token, and set the target.""" pass def auth_connection(self): - """ Ensure that the connection is authorized. """ + """Ensure that the connection is authorized.""" pass def do_HEAD(self): @@ -296,12 +316,12 @@ def handle(self): else: super().handle() - def log_request(self, code='-', size='-'): + def log_request(self, code="-", size="-"): if self.verbose: super().log_request(code, size) -class WebSockifyServer(): +class WebSockifyServer: """ WebSockets server class. As an alternative, the standard library SocketServer can be used @@ -317,48 +337,69 @@ class EClose(Exception): class Terminate(Exception): pass - def __init__(self, RequestHandlerClass, listen_fd=None, - listen_host='', listen_port=None, source_is_ipv6=False, - verbose=False, cert='', key='', key_password=None, ssl_only=None, - verify_client=False, cafile=None, - daemon=False, record='', web='', web_auth=False, - file_only=False, - run_once=False, timeout=0, idle_timeout=0, traffic=False, - tcp_keepalive=True, tcp_keepcnt=None, tcp_keepidle=None, - tcp_keepintvl=None, ssl_ciphers=None, ssl_options=0, - unix_listen=None, unix_listen_mode=None): - + def __init__( + self, + RequestHandlerClass, + listen_fd=None, + listen_host="", + listen_port=None, + source_is_ipv6=False, + verbose=False, + cert="", + key="", + key_password=None, + ssl_only=None, + verify_client=False, + cafile=None, + daemon=False, + record="", + web="", + web_auth=False, + file_only=False, + run_once=False, + timeout=0, + idle_timeout=0, + traffic=False, + tcp_keepalive=True, + tcp_keepcnt=None, + tcp_keepidle=None, + tcp_keepintvl=None, + ssl_ciphers=None, + ssl_options=0, + unix_listen=None, + unix_listen_mode=None, + ): # settings self.RequestHandlerClass = RequestHandlerClass - self.verbose = verbose - self.listen_fd = listen_fd - self.unix_listen = unix_listen - self.unix_listen_mode = unix_listen_mode - self.listen_host = listen_host - self.listen_port = listen_port - self.prefer_ipv6 = source_is_ipv6 - self.ssl_only = ssl_only - self.ssl_ciphers = ssl_ciphers - self.ssl_options = ssl_options - self.verify_client = verify_client - self.daemon = daemon - self.run_once = run_once - self.timeout = timeout - self.idle_timeout = idle_timeout - self.traffic = traffic - self.file_only = file_only - self.web_auth = web_auth - - self.launch_time = time.time() - self.ws_connection = False - self.handler_id = 1 - self.terminating = False - - self.logger = self.get_logger() - self.tcp_keepalive = tcp_keepalive - self.tcp_keepcnt = tcp_keepcnt - self.tcp_keepidle = tcp_keepidle - self.tcp_keepintvl = tcp_keepintvl + self.verbose = verbose + self.listen_fd = listen_fd + self.unix_listen = unix_listen + self.unix_listen_mode = unix_listen_mode + self.listen_host = listen_host + self.listen_port = listen_port + self.prefer_ipv6 = source_is_ipv6 + self.ssl_only = ssl_only + self.ssl_ciphers = ssl_ciphers + self.ssl_options = ssl_options + self.verify_client = verify_client + self.daemon = daemon + self.run_once = run_once + self.timeout = timeout + self.idle_timeout = idle_timeout + self.traffic = traffic + self.file_only = file_only + self.web_auth = web_auth + + self.launch_time = time.time() + self.ws_connection = False + self.handler_id = 1 + self.terminating = False + + self.logger = self.get_logger() + self.tcp_keepalive = tcp_keepalive + self.tcp_keepcnt = tcp_keepcnt + self.tcp_keepidle = tcp_keepidle + self.tcp_keepintvl = tcp_keepintvl # keyfile path must be None if not specified self.key = None @@ -366,7 +407,7 @@ def __init__(self, RequestHandlerClass, listen_fd=None, # Make paths settings absolute self.cert = os.path.abspath(cert) - self.web = self.record = self.cafile = '' + self.web = self.record = self.cafile = "" if key: self.key = os.path.abspath(key) if web: @@ -393,11 +434,12 @@ def __init__(self, RequestHandlerClass, listen_fd=None, elif self.unix_listen != None: self.msg(" - Listen on unix socket %s", self.unix_listen) else: - self.msg(" - Listen on %s:%s", - self.listen_host, self.listen_port) + self.msg(" - Listen on %s:%s", self.listen_host, self.listen_port) if self.web: if self.file_only: - self.msg(" - Web server (no directory listings). Web root: %s", self.web) + self.msg( + " - Web server (no directory listings). Web root: %s", self.web + ) else: self.msg(" - Web server. Web root: %s", self.web) if ssl: @@ -420,34 +462,45 @@ def __init__(self, RequestHandlerClass, listen_fd=None, @staticmethod def get_logger(): - return logging.getLogger("%s.%s" % ( - WebSockifyServer.log_prefix, - WebSockifyServer.__class__.__name__)) + return logging.getLogger( + "%s.%s" % (WebSockifyServer.log_prefix, WebSockifyServer.__class__.__name__) + ) @staticmethod - def socket(host, port=None, connect=False, prefer_ipv6=False, - unix_socket=None, unix_socket_mode=None, unix_socket_listen=False, - use_ssl=False, tcp_keepalive=True, tcp_keepcnt=None, - tcp_keepidle=None, tcp_keepintvl=None): - """ Resolve a host (and optional port) to an IPv4 or IPv6 + def socket( + host, + port=None, + connect=False, + prefer_ipv6=False, + unix_socket=None, + unix_socket_mode=None, + unix_socket_listen=False, + use_ssl=False, + tcp_keepalive=True, + tcp_keepcnt=None, + tcp_keepidle=None, + tcp_keepintvl=None, + ): + """Resolve a host (and optional port) to an IPv4 or IPv6 address. Create a socket. Bind to it if listen is set, otherwise connect to it. Return the socket. """ flags = 0 - if host == '': + if host == "": host = None if connect and not (port or unix_socket): raise Exception("Connect mode requires a port") if use_ssl and not ssl: - raise Exception("SSL socket requested but Python SSL module not loaded."); + raise Exception("SSL socket requested but Python SSL module not loaded.") if not connect and use_ssl: raise Exception("SSL only supported in connect mode (for now)") if not connect: flags = flags | socket.AI_PASSIVE if not unix_socket: - addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM, - socket.IPPROTO_TCP, flags) + addrs = socket.getaddrinfo( + host, port, 0, socket.SOCK_STREAM, socket.IPPROTO_TCP, flags + ) if not addrs: raise Exception("Could not resolve host '%s'" % host) addrs.sort(key=lambda x: x[0]) @@ -455,17 +508,14 @@ def socket(host, port=None, connect=False, prefer_ipv6=False, addrs.reverse() sock = socket.socket(addrs[0][0], addrs[0][1]) - if tcp_keepalive: + if tcp_keepalive: sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) if tcp_keepcnt: - sock.setsockopt(socket.SOL_TCP, socket.TCP_KEEPCNT, - tcp_keepcnt) + sock.setsockopt(socket.SOL_TCP, socket.TCP_KEEPCNT, tcp_keepcnt) if tcp_keepidle: - sock.setsockopt(socket.SOL_TCP, socket.TCP_KEEPIDLE, - tcp_keepidle) + sock.setsockopt(socket.SOL_TCP, socket.TCP_KEEPIDLE, tcp_keepidle) if tcp_keepintvl: - sock.setsockopt(socket.SOL_TCP, socket.TCP_KEEPINTVL, - tcp_keepintvl) + sock.setsockopt(socket.SOL_TCP, socket.TCP_KEEPINTVL, tcp_keepintvl) if connect: sock.connect(addrs[0][4]) @@ -497,8 +547,7 @@ def socket(host, port=None, connect=False, prefer_ipv6=False, return sock @staticmethod - def daemonize(keepfd=None, chdir='/'): - + def daemonize(keepfd=None, chdir="/"): if keepfd is None: keepfd = [] @@ -506,14 +555,16 @@ def daemonize(keepfd=None, chdir='/'): if chdir: os.chdir(chdir) else: - os.chdir('/') + os.chdir("/") os.setgid(os.getgid()) # relinquish elevations os.setuid(os.getuid()) # relinquish elevations # Double fork to daemonize - if os.fork() > 0: os._exit(0) # Parent exits - os.setsid() # Obtain new process group - if os.fork() > 0: os._exit(0) # Parent exits + if os.fork() > 0: + os._exit(0) # Parent exits + os.setsid() # Obtain new process group + if os.fork() > 0: + os._exit(0) # Parent exits # Signal handling signal.signal(signal.SIGTERM, signal.SIG_IGN) @@ -521,14 +572,16 @@ def daemonize(keepfd=None, chdir='/'): # Close open files maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1] - if maxfd == resource.RLIM_INFINITY: maxfd = 256 + if maxfd == resource.RLIM_INFINITY: + maxfd = 256 for fd in reversed(range(maxfd)): try: if fd not in keepfd: os.close(fd) except OSError: _, exc, _ = sys.exc_info() - if exc.errno != errno.EBADF: raise + if exc.errno != errno.EBADF: + raise # Redirect I/O to /dev/null os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdin.fileno()) @@ -557,7 +610,7 @@ def do_handshake(self, sock, address): # Peek, but do not read the data so that we have a opportunity # to SSL wrap the socket first handshake = sock.recv(1024, socket.MSG_PEEK) - #self.msg("Handshake [%s]" % handshake) + # self.msg("Handshake [%s]" % handshake) if not handshake: raise self.EClose("") @@ -567,8 +620,7 @@ def do_handshake(self, sock, address): if not ssl: raise self.EClose("SSL connection but no 'ssl' module") if not os.path.exists(self.cert): - raise self.EClose("SSL connection but '%s' not found" - % self.cert) + raise self.EClose("SSL connection but '%s' not found" % self.cert) retsock = None try: # create new-style SSL wrapping for extended features @@ -576,16 +628,16 @@ def do_handshake(self, sock, address): if self.ssl_ciphers is not None: context.set_ciphers(self.ssl_ciphers) context.options = self.ssl_options - context.load_cert_chain(certfile=self.cert, keyfile=self.key, password=self.key_password) + context.load_cert_chain( + certfile=self.cert, keyfile=self.key, password=self.key_password + ) if self.verify_client: context.verify_mode = ssl.CERT_REQUIRED if self.cafile: context.load_verify_locations(cafile=self.cafile) else: context.set_default_verify_paths() - retsock = context.wrap_socket( - sock, - server_side=True) + retsock = context.wrap_socket(sock, server_side=True) except ssl.SSLError: _, x, _ = sys.exc_info() if x.args[0] == ssl.SSL_ERROR_EOF: @@ -618,28 +670,27 @@ def do_handshake(self, sock, address): # def msg(self, *args, **kwargs): - """ Output message as info """ + """Output message as info""" self.logger.log(logging.INFO, *args, **kwargs) def vmsg(self, *args, **kwargs): - """ Same as msg() but as debug. """ + """Same as msg() but as debug.""" self.logger.log(logging.DEBUG, *args, **kwargs) def warn(self, *args, **kwargs): - """ Same as msg() but as warning. """ + """Same as msg() but as warning.""" self.logger.log(logging.WARN, *args, **kwargs) - # # Events that can/should be overridden in sub-classes # def started(self): - """ Called after WebSockets startup """ + """Called after WebSockets startup""" self.vmsg("WebSockets server started") def poll(self): - """ Run periodically while waiting for connections. """ - #self.vmsg("Running poll()") + """Run periodically while waiting for connections.""" + # self.vmsg("Running poll()") pass def terminate(self): @@ -661,7 +712,7 @@ def fallback_SIGCHLD(self, sig, stack): while result[0]: self.vmsg("Reaped child process %s" % result[0]) result = os.waitpid(-1, os.WNOHANG) - except (OSError): + except OSError: pass def do_SIGINT(self, sig, stack): @@ -675,7 +726,7 @@ def do_SIGTERM(self, sig, stack): self.terminate() def top_new_client(self, startsock, address): - """ Do something with a WebSockets client connection. """ + """Do something with a WebSockets client connection.""" # handler process client = None try: @@ -693,7 +744,6 @@ def top_new_client(self, startsock, address): self.msg("handler exception: %s" % str(exc)) self.vmsg("exception", exc_info=True) finally: - if client and client != startsock: # Close the SSL wrapped socket # Original socket closed by caller @@ -721,19 +771,27 @@ def start_server(self): try: if self.listen_fd != None: - lsock = socket.fromfd(self.listen_fd, socket.AF_INET, socket.SOCK_STREAM) + lsock = socket.fromfd( + self.listen_fd, socket.AF_INET, socket.SOCK_STREAM + ) elif self.unix_listen != None: - lsock = self.socket(host=None, - unix_socket=self.unix_listen, - unix_socket_mode=self.unix_listen_mode, - unix_socket_listen=True) + lsock = self.socket( + host=None, + unix_socket=self.unix_listen, + unix_socket_mode=self.unix_listen_mode, + unix_socket_listen=True, + ) else: - lsock = self.socket(self.listen_host, self.listen_port, False, - self.prefer_ipv6, - tcp_keepalive=self.tcp_keepalive, - tcp_keepcnt=self.tcp_keepcnt, - tcp_keepidle=self.tcp_keepidle, - tcp_keepintvl=self.tcp_keepintvl) + lsock = self.socket( + self.listen_host, + self.listen_port, + False, + self.prefer_ipv6, + tcp_keepalive=self.tcp_keepalive, + tcp_keepcnt=self.tcp_keepcnt, + tcp_keepidle=self.tcp_keepidle, + tcp_keepintvl=self.tcp_keepintvl, + ) except OSError as e: self.msg("Openening socket failed: %s", str(e)) self.vmsg("exception", exc_info=True) @@ -751,13 +809,13 @@ def start_server(self): signal.SIGINT: signal.getsignal(signal.SIGINT), signal.SIGTERM: signal.getsignal(signal.SIGTERM), } - if getattr(signal, 'SIGCHLD', None) is not None: + if getattr(signal, "SIGCHLD", None) is not None: original_signals[signal.SIGCHLD] = signal.getsignal(signal.SIGCHLD) signal.signal(signal.SIGINT, self.do_SIGINT) signal.signal(signal.SIGTERM, self.do_SIGTERM) # make sure that _cleanup is called when children die # by calling active_children on SIGCHLD - if getattr(signal, 'SIGCHLD', None) is not None: + if getattr(signal, "SIGCHLD", None) is not None: signal.signal(signal.SIGCHLD, self.multiprocessing_SIGCHLD) last_active_time = self.launch_time @@ -774,8 +832,7 @@ def start_server(self): time_elapsed = time.time() - self.launch_time if self.timeout and time_elapsed > self.timeout: - self.msg('listener exit due to --timeout %s' - % self.timeout) + self.msg("listener exit due to --timeout %s" % self.timeout) break if self.idle_timeout: @@ -787,8 +844,10 @@ def start_server(self): last_active_time = time.time() if idle_time > self.idle_timeout and child_count == 0: - self.msg('listener exit due to --idle-timeout %s' - % self.idle_timeout) + self.msg( + "listener exit due to --idle-timeout %s" + % self.idle_timeout + ) break try: @@ -799,16 +858,16 @@ def start_server(self): startsock, address = lsock.accept() # Unix Socket will not report address (empty string), but address[0] is logged a bunch if self.unix_listen != None: - address = [ self.unix_listen ] + address = [self.unix_listen] else: continue except self.Terminate: raise except Exception: _, exc, _ = sys.exc_info() - if hasattr(exc, 'errno'): + if hasattr(exc, "errno"): err = exc.errno - elif hasattr(exc, 'args'): + elif hasattr(exc, "args"): err = exc.args[0] else: err = exc[0] @@ -821,15 +880,14 @@ def start_server(self): if self.run_once: # Run in same process if run_once self.top_new_client(startsock, address) - if self.ws_connection : - self.msg('%s: exiting due to --run-once' - % address[0]) + if self.ws_connection: + self.msg("%s: exiting due to --run-once" % address[0]) break else: - self.vmsg('%s: new handler Process' % address[0]) + self.vmsg("%s: new handler Process" % address[0]) p = multiprocessing.Process( - target=self.top_new_client, - args=(startsock, address)) + target=self.top_new_client, args=(startsock, address) + ) p.start() # child will not return @@ -857,12 +915,11 @@ def start_server(self): startsock.close() finally: # Close listen port - self.vmsg("Closing socket listening at %s:%s", - self.listen_host, self.listen_port) + self.vmsg( + "Closing socket listening at %s:%s", self.listen_host, self.listen_port + ) lsock.close() # Restore signals for sig, func in original_signals.items(): signal.signal(sig, func) - -