add pylint and lint the project

This commit is contained in:
a 2022-12-25 18:32:57 -06:00
parent 6f1d90cc0c
commit ac571289d4
7 changed files with 414 additions and 100 deletions

197
pdm.lock
View File

@ -8,6 +8,17 @@ dependencies = [
"sniffio>=1.1",
]
[[package]]
name = "astroid"
version = "2.12.13"
requires_python = ">=3.7.2"
summary = "An abstract syntax tree for Python with inference support."
dependencies = [
"lazy-object-proxy>=1.4.0",
"wrapt<2,>=1.11; python_version < \"3.11\"",
"wrapt<2,>=1.14; python_version >= \"3.11\"",
]
[[package]]
name = "click"
version = "8.1.3"
@ -23,6 +34,12 @@ version = "0.4.6"
requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
summary = "Cross-platform colored terminal text."
[[package]]
name = "dill"
version = "0.3.6"
requires_python = ">=3.7"
summary = "serialize all of python"
[[package]]
name = "fastapi"
version = "0.88.0"
@ -51,6 +68,30 @@ version = "3.4"
requires_python = ">=3.5"
summary = "Internationalized Domain Names in Applications (IDNA)"
[[package]]
name = "isort"
version = "5.11.4"
requires_python = ">=3.7.0"
summary = "A Python utility / library to sort Python imports."
[[package]]
name = "lazy-object-proxy"
version = "1.8.0"
requires_python = ">=3.7"
summary = "A fast and thorough lazy object proxy."
[[package]]
name = "mccabe"
version = "0.7.0"
requires_python = ">=3.6"
summary = "McCabe checker, plugin for flake8"
[[package]]
name = "platformdirs"
version = "2.6.0"
requires_python = ">=3.7"
summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
[[package]]
name = "pydantic"
version = "1.10.2"
@ -60,6 +101,23 @@ dependencies = [
"typing-extensions>=4.1.0",
]
[[package]]
name = "pylint"
version = "2.15.9"
requires_python = ">=3.7.2"
summary = "python code static checker"
dependencies = [
"astroid<=2.14.0-dev0,>=2.12.13",
"colorama>=0.4.5; sys_platform == \"win32\"",
"dill>=0.2; python_version < \"3.11\"",
"dill>=0.3.6; python_version >= \"3.11\"",
"isort<6,>=4.2.5",
"mccabe<0.8,>=0.6",
"platformdirs>=2.2.0",
"tomli>=1.1.0; python_version < \"3.11\"",
"tomlkit>=0.10.1",
]
[[package]]
name = "python-dotenv"
version = "0.21.0"
@ -87,6 +145,18 @@ dependencies = [
"anyio<5,>=3.4.0",
]
[[package]]
name = "tomli"
version = "2.0.1"
requires_python = ">=3.7"
summary = "A lil' TOML parser"
[[package]]
name = "tomlkit"
version = "0.11.6"
requires_python = ">=3.6"
summary = "Style preserving TOML library"
[[package]]
name = "typing-extensions"
version = "4.4.0"
@ -141,15 +211,25 @@ version = "10.4"
requires_python = ">=3.7"
summary = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
[[package]]
name = "wrapt"
version = "1.14.1"
requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
summary = "Module for decorators, wrappers and monkey patching."
[metadata]
lock_version = "4.1"
content_hash = "sha256:b322cb00d8d7fb1b82a352ce22f81402bfde370223353b1df64cd11ec9639a92"
content_hash = "sha256:9a10f6941e9a68f98e1a2ce7e300b98d1c7cc79f71526d28a1030d59c7e560bb"
[metadata.files]
"anyio 3.6.2" = [
{url = "https://files.pythonhosted.org/packages/77/2b/b4c0b7a3f3d61adb1a1e0b78f90a94e2b6162a043880704b7437ef297cad/anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"},
{url = "https://files.pythonhosted.org/packages/8b/94/6928d4345f2bc1beecbff03325cad43d320717f51ab74ab5a571324f4f5a/anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"},
]
"astroid 2.12.13" = [
{url = "https://files.pythonhosted.org/packages/61/d0/e7cfca72ec7d6c5e0da725c003db99bb056e9b6c2f4ee6fae1145adf28a6/astroid-2.12.13.tar.gz", hash = "sha256:1493fe8bd3dfd73dc35bd53c9d5b6e49ead98497c47b2307662556a5692d29d7"},
{url = "https://files.pythonhosted.org/packages/b1/61/42e075b7d29ed4d452d91cbaaca142710d50d04e68eb7161ce5807a00a30/astroid-2.12.13-py3-none-any.whl", hash = "sha256:10e0ad5f7b79c435179d0d0f0df69998c4eef4597534aae44910db060baeb907"},
]
"click 8.1.3" = [
{url = "https://files.pythonhosted.org/packages/59/87/84326af34517fca8c58418d148f2403df25303e02736832403587318e9e8/click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
{url = "https://files.pythonhosted.org/packages/c2/f1/df59e28c642d583f7dacffb1e0965d0e00b218e0186d7858ac5233dce840/click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
@ -158,6 +238,10 @@ content_hash = "sha256:b322cb00d8d7fb1b82a352ce22f81402bfde370223353b1df64cd11ec
{url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
"dill 0.3.6" = [
{url = "https://files.pythonhosted.org/packages/7c/e7/364a09134e1062d4d5ff69b853a56cf61c223e0afcc6906b6832bcd51ea8/dill-0.3.6.tar.gz", hash = "sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"},
{url = "https://files.pythonhosted.org/packages/be/e3/a84bf2e561beed15813080d693b4b27573262433fced9c1d1fea59e60553/dill-0.3.6-py3-none-any.whl", hash = "sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0"},
]
"fastapi 0.88.0" = [
{url = "https://files.pythonhosted.org/packages/d8/09/ce090f6d53ce8b6335954488087210fa1e054c4a65f74d5f76aed254c159/fastapi-0.88.0-py3-none-any.whl", hash = "sha256:263b718bb384422fe3d042ffc9a0c8dece5e034ab6586ff034f6b4b1667c3eee"},
{url = "https://files.pythonhosted.org/packages/f0/d1/55559f6b09ac336cbb4640ab4c2da0fd79a955eed31f475f0a5ba2ad102e/fastapi-0.88.0.tar.gz", hash = "sha256:915bf304180a0e7c5605ec81097b7d4cd8826ff87a02bb198e336fb9f3b5ff02"},
@ -213,6 +297,39 @@ content_hash = "sha256:b322cb00d8d7fb1b82a352ce22f81402bfde370223353b1df64cd11ec
{url = "https://files.pythonhosted.org/packages/8b/e1/43beb3d38dba6cb420cefa297822eac205a277ab43e5ba5d5c46faf96438/idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
{url = "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
]
"isort 5.11.4" = [
{url = "https://files.pythonhosted.org/packages/76/46/004e2dd6c312e8bb7cb40a6c01b770956e0ef137857e82d47bd9c829356b/isort-5.11.4.tar.gz", hash = "sha256:6db30c5ded9815d813932c04c2f85a360bcdd35fed496f4d8f35495ef0a261b6"},
{url = "https://files.pythonhosted.org/packages/91/3b/a63bafb8141b67c397841b36ad46e7469716af2b2d00cb0be2dfb9667130/isort-5.11.4-py3-none-any.whl", hash = "sha256:c033fd0edb91000a7f09527fe5c75321878f98322a77ddcc81adbd83724afb7b"},
]
"lazy-object-proxy 1.8.0" = [
{url = "https://files.pythonhosted.org/packages/0a/68/5839136508651d813c1adce568e2f7417bb66083dc8d604a69d465ee53c0/lazy_object_proxy-1.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6850e4aeca6d0df35bb06e05c8b934ff7c533734eb51d0ceb2d63696f1e6030c"},
{url = "https://files.pythonhosted.org/packages/30/c3/81c176ce53d9107947d355b273f9661a4f4cad6d56d1daf1c9d6969902e8/lazy_object_proxy-1.8.0-cp310-cp310-win32.whl", hash = "sha256:b70d6e7a332eb0217e7872a73926ad4fdc14f846e85ad6749ad111084e76df25"},
{url = "https://files.pythonhosted.org/packages/34/c5/1ef17ab530068f7a5549ab376726f83fe2221db592dbdfd4f8fd4662e45d/lazy_object_proxy-1.8.0-cp311-cp311-win32.whl", hash = "sha256:e20bfa6db17a39c706d24f82df8352488d2943a3b7ce7d4c22579cb89ca8896e"},
{url = "https://files.pythonhosted.org/packages/46/35/55c3650f29858869596871b7fedf4a6b211b61dcc4dd8e8d5702eb85370e/lazy_object_proxy-1.8.0-cp39-cp39-win32.whl", hash = "sha256:8f6ce2118a90efa7f62dd38c7dbfffd42f468b180287b748626293bf12ed468f"},
{url = "https://files.pythonhosted.org/packages/60/c1/bf324cf9a0577b0e3781b1a38696405235ac784c4a6d889f97a36dcedc70/lazy_object_proxy-1.8.0-cp37-cp37m-win32.whl", hash = "sha256:5b51d6f3bfeb289dfd4e95de2ecd464cd51982fe6f00e2be1d0bf94864d58acd"},
{url = "https://files.pythonhosted.org/packages/64/ed/ad47931e7780a5c39f7439de9124438794137840ffdb5f3ffd2995228071/lazy_object_proxy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:eb329f8d8145379bf5dbe722182410fe8863d186e51bf034d2075eb8d85ee25b"},
{url = "https://files.pythonhosted.org/packages/65/08/836c9e4e6edf3a267e5b1d0c03923a70ee1a233baf6eb00bfab88d795c51/lazy_object_proxy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4e2d9f764f1befd8bdc97673261b8bb888764dfdbd7a4d8f55e4fbcabb8c3fb7"},
{url = "https://files.pythonhosted.org/packages/74/37/591f89e8a09ae4574391bdf8a5eecd34a3dbe545917333e625c9de9a66b0/lazy-object-proxy-1.8.0.tar.gz", hash = "sha256:c219a00245af0f6fa4e95901ed28044544f50152840c5b6a3e7b2568db34d156"},
{url = "https://files.pythonhosted.org/packages/7c/0f/60db0efe9a1d61fc830cfd2806d54c5fb64761e8009b9d163bf0ede5b12d/lazy_object_proxy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4fd031589121ad46e293629b39604031d354043bb5cdf83da4e93c2d7f3389fe"},
{url = "https://files.pythonhosted.org/packages/80/aa/71f82fd17211767419d6b1fc3dc00ba4641c11f9c9358f7acc5222e693b9/lazy_object_proxy-1.8.0-cp38-cp38-win32.whl", hash = "sha256:d176f392dbbdaacccf15919c77f526edf11a34aece58b55ab58539807b85436f"},
{url = "https://files.pythonhosted.org/packages/95/97/44ee4e0247754bcb878886baf2e06856ff268b0d67e86f1d750f251e3c87/lazy_object_proxy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c1c7c0433154bb7c54185714c6929acc0ba04ee1b167314a779b9025517eada"},
{url = "https://files.pythonhosted.org/packages/9d/23/7e78292a5b72121a8bdfff128fcfb8d3630af74336855d3e527f73eaa4c0/lazy_object_proxy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:71d9ae8a82203511a6f60ca5a1b9f8ad201cac0fc75038b2dc5fa519589c9288"},
{url = "https://files.pythonhosted.org/packages/9d/d1/6dd90b049748d02d9120a496c3649220ac4f6803dd74c9ae48f2bb001239/lazy_object_proxy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:eac3a9a5ef13b332c059772fd40b4b1c3d45a3a2b05e33a361dee48e54a4dad0"},
{url = "https://files.pythonhosted.org/packages/b9/a2/e6b92d1ce6da768a1570d436616f4c565420fcf1c4b2b5246cf77624fe36/lazy_object_proxy-1.8.0-pp37-pypy37_pp73-any.whl", hash = "sha256:ae032743794fba4d171b5b67310d69176287b5bf82a21f588282406a79498891"},
{url = "https://files.pythonhosted.org/packages/d7/8a/7bf9154dd7e6e9bda733a105e3baca3636abe72091cd1dcbf636979b667f/lazy_object_proxy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:14010b49a2f56ec4943b6cf925f597b534ee2fe1f0738c84b3bce0c1a11ff10d"},
{url = "https://files.pythonhosted.org/packages/e0/d3/0cdabfa685eb152a9f4d179fa95f121b3810171f246e8e51f45d100b345c/lazy_object_proxy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:afcaa24e48bb23b3be31e329deb3f1858f1f1df86aea3d70cb5c8578bfe5261c"},
{url = "https://files.pythonhosted.org/packages/e3/90/4c8d2ce638791874f48894761e305afa2bf6f85f315f1d51946eb1e2b18f/lazy_object_proxy-1.8.0-pp38-pypy38_pp73-any.whl", hash = "sha256:7e1561626c49cb394268edd00501b289053a652ed762c58e1081224c8d881cec"},
{url = "https://files.pythonhosted.org/packages/f5/dc/11168f6697ed68ec29a4f0887308c0d7836d96148a81eb0abb7b8e77b8e8/lazy_object_proxy-1.8.0-pp39-pypy39_pp73-any.whl", hash = "sha256:ce58b2b3734c73e68f0e30e4e725264d4d6be95818ec0a0be4bb6bf9a7e79aa8"},
{url = "https://files.pythonhosted.org/packages/f6/71/e0dbe4172135aca4b4f657cf15fefd34247b5392ae42cf2ca2583dfa332f/lazy_object_proxy-1.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:6f593f26c470a379cf7f5bc6db6b5f1722353e7bf937b8d0d0b3fba911998858"},
]
"mccabe 0.7.0" = [
{url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
{url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
]
"platformdirs 2.6.0" = [
{url = "https://files.pythonhosted.org/packages/87/69/cd019a9473bcdfb38983e2d550ccb239264fc4c2fc32c42ac1b1cc2506b6/platformdirs-2.6.0-py3-none-any.whl", hash = "sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca"},
{url = "https://files.pythonhosted.org/packages/ec/4c/9af851448e55c57b30a13a72580306e628c3b431d97fdae9e0b8d4fa3685/platformdirs-2.6.0.tar.gz", hash = "sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e"},
]
"pydantic 1.10.2" = [
{url = "https://files.pythonhosted.org/packages/13/e3/5b83cba317390c9125e049a5328b8e19475098362d398a65936aaab3f00f/pydantic-1.10.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9"},
{url = "https://files.pythonhosted.org/packages/22/53/196c9a5752e30d682e493d7c00ea0a02377446578e577ae5e085010dc0bd/pydantic-1.10.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c"},
@ -251,6 +368,10 @@ content_hash = "sha256:b322cb00d8d7fb1b82a352ce22f81402bfde370223353b1df64cd11ec
{url = "https://files.pythonhosted.org/packages/fe/5b/6f77e6ebc93e5e3c7fd480e1b171a6547407eba901a56a65d2745df24144/pydantic-1.10.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254"},
{url = "https://files.pythonhosted.org/packages/fe/fd/8f7f8271d526378c927babd1229501e576760cef9a509909a3415eec3c0d/pydantic-1.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd"},
]
"pylint 2.15.9" = [
{url = "https://files.pythonhosted.org/packages/68/3a/1e61444eb8276ad962a7f300b6920b7ad391f4fbe551d34443f093a18899/pylint-2.15.9.tar.gz", hash = "sha256:18783cca3cfee5b83c6c5d10b3cdb66c6594520ffae61890858fe8d932e1c6b4"},
{url = "https://files.pythonhosted.org/packages/7d/df/0e50d5640ed4c6a492cdc6df0c281afee3f85d98209e7ec7b31243838b40/pylint-2.15.9-py3-none-any.whl", hash = "sha256:349c8cd36aede4d50a0754a8c0218b43323d13d5d88f4b2952ddfe3e169681eb"},
]
"python-dotenv 0.21.0" = [
{url = "https://files.pythonhosted.org/packages/2d/10/ff4f2f5b2a420fd09e1331d63cc87cf4367c5745c0a4ce99cea92b1cbacb/python_dotenv-0.21.0-py3-none-any.whl", hash = "sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5"},
{url = "https://files.pythonhosted.org/packages/87/8d/ab7352188f605e3f663f34692b2ed7457da5985857e9e4c2335cd12fb3c9/python-dotenv-0.21.0.tar.gz", hash = "sha256:b77d08274639e3d34145dfa6c7008e66df0f04b7be7a75fd0d5292c191d79045"},
@ -305,6 +426,14 @@ content_hash = "sha256:b322cb00d8d7fb1b82a352ce22f81402bfde370223353b1df64cd11ec
{url = "https://files.pythonhosted.org/packages/1d/4e/30eda84159d5b3ad7fe663c40c49b16dd17436abe838f10a56c34bee44e8/starlette-0.22.0-py3-none-any.whl", hash = "sha256:b5eda991ad5f0ee5d8ce4c4540202a573bb6691ecd0c712262d0bc85cf8f2c50"},
{url = "https://files.pythonhosted.org/packages/f7/ff/64bd3362f80e81402f21e0f1ba55f8801cd899ad3f2a1bcc4a94d284c786/starlette-0.22.0.tar.gz", hash = "sha256:b092cbc365bea34dd6840b42861bdabb2f507f8671e642e8272d2442e08ea4ff"},
]
"tomli 2.0.1" = [
{url = "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{url = "https://files.pythonhosted.org/packages/c0/3f/d7af728f075fb08564c5949a9c95e44352e23dee646869fa104a3b2060a3/tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
"tomlkit 0.11.6" = [
{url = "https://files.pythonhosted.org/packages/2b/df/971fa5db3250bb022105d17f340339370f73d502e65e687a94ca1a4c4b1f/tomlkit-0.11.6-py3-none-any.whl", hash = "sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b"},
{url = "https://files.pythonhosted.org/packages/ff/04/58b4c11430ed4b7b8f1723a5e4f20929d59361e9b17f0872d69681fd8ffd/tomlkit-0.11.6.tar.gz", hash = "sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73"},
]
"typing-extensions 4.4.0" = [
{url = "https://files.pythonhosted.org/packages/0b/8e/f1a0a5a76cfef77e1eb6004cb49e5f8d72634da638420b9ea492ce8305e8/typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"},
{url = "https://files.pythonhosted.org/packages/e3/a7/8f4e456ef0adac43f452efc2d0e4b242ab831297f1bac60ac815d37eb9cf/typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"},
@ -436,3 +565,69 @@ content_hash = "sha256:b322cb00d8d7fb1b82a352ce22f81402bfde370223353b1df64cd11ec
{url = "https://files.pythonhosted.org/packages/f9/15/ab0e9155700d3037ffe4a146a719f3e68ee025c9d45d6a39b027e928db52/websockets-10.4-cp38-cp38-win32.whl", hash = "sha256:db3c336f9eda2532ec0fd8ea49fef7a8df8f6c804cdf4f39e5c5c0d4a4ad9a7a"},
{url = "https://files.pythonhosted.org/packages/fd/42/07f31d9f9e142b38cde8d3ea0c8ea1bacf9bc366f2f573eca57086e9f2a6/websockets-10.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:05a7233089f8bd355e8cbe127c2e8ca0b4ea55467861906b80d2ebc7db4d6b72"},
]
"wrapt 1.14.1" = [
{url = "https://files.pythonhosted.org/packages/00/61/04422b7469534650b622d5baa1dd335c4b91d35c8d33548b272f33060519/wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"},
{url = "https://files.pythonhosted.org/packages/03/c6/d864b8da8afa57a638b12596c3a58dfe3471acda900961c02a904010e0e9/wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"},
{url = "https://files.pythonhosted.org/packages/07/06/2b4aaaa4403f766c938f9780c700d7399726bce3dfd94f5a57c4e5b9dc68/wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"},
{url = "https://files.pythonhosted.org/packages/0a/61/330f24065b8f2fc02f94321092a24e0c30aefcbac89ab5c860e180366c9f/wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"},
{url = "https://files.pythonhosted.org/packages/0d/dc/3f588e42e09fb5170349924366587319e1e49d50a1a58dbe78d6046ca812/wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"},
{url = "https://files.pythonhosted.org/packages/11/eb/e06e77394d6cf09977d92bff310cb0392930c08a338f99af6066a5a98f92/wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"},
{url = "https://files.pythonhosted.org/packages/12/cd/da6611401655ac2b8496b316ad9e21a3fd4f8e62e2c3b3e3c50207770517/wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"},
{url = "https://files.pythonhosted.org/packages/1b/77/9f3660dca3d6b7079c3b1b64ad0795db3603cb9345fba3ca580ccdc3fef5/wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"},
{url = "https://files.pythonhosted.org/packages/21/55/42ff84a671415db8fc87a1c301c6c7f52b978669324059bdb8dbd7d3f0ce/wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"},
{url = "https://files.pythonhosted.org/packages/23/8b/e4de40ac2fa6d53e694310c576e160bec3db8a282fbdcd5596544f6bc69e/wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"},
{url = "https://files.pythonhosted.org/packages/2a/86/c9ef2fa4899ec069c8efe43fc92ca2ba0c5a7921cfaf83090030cf7b1487/wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"},
{url = "https://files.pythonhosted.org/packages/30/31/c3f80ed75bec31fc3b4e3193f660b96da8fef70811f0ed67a4dc873412bc/wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"},
{url = "https://files.pythonhosted.org/packages/33/cd/7335d8b82ff0a442581ab37a8d275ad76b4c1f33ace63c1a4d7c23791eee/wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"},
{url = "https://files.pythonhosted.org/packages/36/ee/944dc7e5462662270e8a379755bcc543fc8f09029866288060dc163ed5b4/wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"},
{url = "https://files.pythonhosted.org/packages/38/38/5b338163b3b4f1ab718306984678c3d180b85a25d72654ea4c61aa6b0968/wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"},
{url = "https://files.pythonhosted.org/packages/39/4d/34599a47c8a41b3ea4986e14f728c293a8a96cd6c23663fe33657c607d34/wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"},
{url = "https://files.pythonhosted.org/packages/39/a1/9b4d07b6836a62c6999e8bb5cefced5b34a26fb03941a19c27af98eecec0/wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"},
{url = "https://files.pythonhosted.org/packages/40/f4/7be7124a06c14b92be53912f93c8dc84247f1cb93b4003bed460a430d1de/wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"},
{url = "https://files.pythonhosted.org/packages/49/a8/528295a24655f901148177355edb6a22b84abb2abfadacc1675643c1434a/wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"},
{url = "https://files.pythonhosted.org/packages/4b/07/782463e367a7c6b418af231ded753e4b2dd3293a157d9b0bb010806fc0c0/wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"},
{url = "https://files.pythonhosted.org/packages/4b/5b/3cf79a5fce7a91c0c10275835199fafdf30c1b8c7008fa671af3c4e8046c/wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"},
{url = "https://files.pythonhosted.org/packages/4f/83/2669bf2cb4cc2b346c40799478d29749ccd17078cb4f69b4a9f95921ff6d/wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"},
{url = "https://files.pythonhosted.org/packages/50/d5/bf619c4d204fe8888460f65222b465c7ecfa43590fdb31864fe0e266da29/wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"},
{url = "https://files.pythonhosted.org/packages/5b/02/5ac7ea3b6722c84a2882d349ac581a9711b4047fe7a58475903832caa295/wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"},
{url = "https://files.pythonhosted.org/packages/5c/46/b91791db2ac7cc4c186408b7aed37b994463970f2397d0548f38b2b47aca/wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"},
{url = "https://files.pythonhosted.org/packages/5e/d3/bd44864e0274b7e162e2a68c71fffbd8b3a7b620efd23320fd0f70333cff/wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"},
{url = "https://files.pythonhosted.org/packages/67/b4/b5504dddcb2ff9486f8569953938591e0013cca09c912b28747d1d9cb04f/wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"},
{url = "https://files.pythonhosted.org/packages/6a/12/76bbe26dc39d05f1a7be8d570d91c87bb79297e08e885148ed670ed17b7b/wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"},
{url = "https://files.pythonhosted.org/packages/72/24/490a0bbc67135f737d2eb4b270bfc91e54cc3f0b5e97b4ceec91a44bb898/wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"},
{url = "https://files.pythonhosted.org/packages/79/9c/f5d1209c8e4e091e250eb3ed099056e7e1ad0ec1e9ca46f6d88389e2d6d4/wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"},
{url = "https://files.pythonhosted.org/packages/82/27/1eac9e63b9ef0e0929e00e17872d45de9d7d965c7f49b933e2daa22c7896/wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"},
{url = "https://files.pythonhosted.org/packages/88/ef/05655df7648752ae0a57fe2b9820e340ff025cecec9341aad7936c589a2f/wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"},
{url = "https://files.pythonhosted.org/packages/92/b5/788b92550804405424e0d0b1a95250137cbf0e050bb5c461e8ad0fefdc86/wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"},
{url = "https://files.pythonhosted.org/packages/93/12/b20ae4dbefa94ef5d667ba71324763d870b86064a944c8ec9533042a41fc/wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"},
{url = "https://files.pythonhosted.org/packages/93/8c/1bbba9357142e6f9bcf55c79e2aa6fd5f4066c331e731376705777a0077f/wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"},
{url = "https://files.pythonhosted.org/packages/93/b1/007fd8d5c8c366ee1c1b93a99962de5fd34f81dae679ee2bf6a6e0ffc8f0/wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"},
{url = "https://files.pythonhosted.org/packages/94/4b/ff8d58aee32ed91744f1ff4970e590f0c8fdda3fa6d702dc82281e0309bd/wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"},
{url = "https://files.pythonhosted.org/packages/94/56/fd707fb8e1ea86e72503d823549fb002a0f16cb4909619748996daeb3a82/wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"},
{url = "https://files.pythonhosted.org/packages/94/59/60b2fe919ffb190cf8cae0307bafdaf1695eac8655921f59768ce3bf1084/wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"},
{url = "https://files.pythonhosted.org/packages/98/0f/3db7e01896b726e68fa2ba918ed0d79f3cc2da2ce928799282264d14c6f6/wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"},
{url = "https://files.pythonhosted.org/packages/a2/a7/dd6e91c68d76328d09dd61a7aadac19d49ec509a07e853173036dc05fb79/wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"},
{url = "https://files.pythonhosted.org/packages/a7/0d/a52a0268c98a687785c5452324e10f9462d289e850066e281aa327505aa7/wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"},
{url = "https://files.pythonhosted.org/packages/b1/ca/ec539e402932bb64814a039f471d327d0deb4612199506094ca60821b94c/wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"},
{url = "https://files.pythonhosted.org/packages/bb/70/73c54e24ea69a8b06ae9649e61d5e64f2b4bdfc6f202fc7794abeac1ed20/wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"},
{url = "https://files.pythonhosted.org/packages/c0/1e/e5a5ac09e92fd112d50e1793e5b9982dc9e510311ed89dacd2e801f82967/wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"},
{url = "https://files.pythonhosted.org/packages/c7/1b/0cdff572d22600fcf47353e8eb1077d83cab3f161ebfb4843565c6e07e66/wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"},
{url = "https://files.pythonhosted.org/packages/c8/03/b36a48dcb6f6332d754017b2dd617757687984a6c433e44ca59bb7fefd4c/wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"},
{url = "https://files.pythonhosted.org/packages/ca/16/e79e786d930b69a20481174c7bc97e989fb67d2a181a5043e1d3c70c9b21/wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"},
{url = "https://files.pythonhosted.org/packages/cd/ec/383d9552df0641e9915454b03139571e0c6e055f5d414d8f3d04f3892f38/wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"},
{url = "https://files.pythonhosted.org/packages/d9/3b/f6b760bf04d13e5ddb70d019779466c22952637cf0f606a26d5f784f27ff/wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"},
{url = "https://files.pythonhosted.org/packages/d9/ab/3ba5816dd466ffd7242913708771d258569825ab76fd29d7fd85b9361311/wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"},
{url = "https://files.pythonhosted.org/packages/da/f4/7af9e01b6c1126b2daef72d5ba2cbf59a7229fd57c5b23166f694d758a8f/wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"},
{url = "https://files.pythonhosted.org/packages/e0/20/9716fb522d17a726364c4d032c8806ffe312268773dd46a394436b2787cc/wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"},
{url = "https://files.pythonhosted.org/packages/e0/6a/3c660fa34c8106aa9719f2a6636c1c3ea7afd5931ae665eb197fdf4def84/wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"},
{url = "https://files.pythonhosted.org/packages/e0/80/af9da7379ee6df583875d0aeb80f9d5f0bd5f081dd1ee5ce06587d8bfec7/wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"},
{url = "https://files.pythonhosted.org/packages/e6/57/d5673f5201ccbc287e70a574868319267735de3041e496e1e26b48d8f653/wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"},
{url = "https://files.pythonhosted.org/packages/e7/a1/a9596c5858c4a58be8cdd5e8b0e5f53f9c1c17f0616b47edde8de1a356fe/wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"},
{url = "https://files.pythonhosted.org/packages/e8/f6/7e30a8c53d27ef8c1ff872dc4fb75247c99eb73d834c91a49a55d046c127/wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"},
{url = "https://files.pythonhosted.org/packages/f0/db/2a9ea49cd8bdde87a85262e517563d42b9e5b760473597b9da511fcbd54d/wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"},
{url = "https://files.pythonhosted.org/packages/f1/96/d22461ba08d61a859c45cda5064b878f2baa61f142d3acfa8adabd82bf07/wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"},
{url = "https://files.pythonhosted.org/packages/f7/92/121147bb2f9ed1aa35a8780c636d5da9c167545f97737f0860b4c6c92086/wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"},
{url = "https://files.pythonhosted.org/packages/f8/c4/3f8130d646bfc89382966adfb3d6428f26d0f752543a7e2fd92c1e493be6/wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"},
{url = "https://files.pythonhosted.org/packages/f9/3c/110e52b9da396a4ef3a0521552a1af9c7875a762361f48678c1ac272fd7e/wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"},
{url = "https://files.pythonhosted.org/packages/fd/70/8a133c88a394394dd57159083b86a564247399440b63f2da0ad727593570/wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"},
]

View File

@ -3,11 +3,11 @@ name = "webfinger"
version = "1"
description = "Simple WebFinger server that returns static resources. Written with Python and FastAPI."
authors = [
{name = "a", email = "a@trwnh.com"},
{name = "a", email = "a@trwnh.com"},
]
dependencies = [
"fastapi>=0.88.0",
"uvicorn[standard]>=0.20.0",
"fastapi>=0.88.0",
"uvicorn[standard]>=0.20.0",
]
requires-python = ">=3.10"
license = {text = "AGPL"}
@ -19,3 +19,18 @@ build-backend = "pdm.pep517.api"
[tool.pdm.scripts]
dev = "uvicorn webfinger:app --port 7033 --reload"
start = "python -m webfinger"
lint = "pylint webfinger"
[tool.pdm.dev-dependencies]
dev = [
"pylint>=2.15.9",
]
[tool.pylint.format]
indent-string = "\t"
[tool.pylint.messages_control]
extension-pkg-whitelist = "pydantic"
disable = [
"missing-final-newline"
]

View File

@ -1,3 +1,7 @@
"""
Initialize the WebFinger server with FastAPI
"""
from fastapi import FastAPI
import webfinger.lookup
@ -9,9 +13,9 @@ app = FastAPI(
app.include_router(webfinger.lookup.router)
def cleanup_openapi(app: FastAPI) -> None:
def cleanup_openapi(fastapi_app: FastAPI) -> None:
"""Cleanup inconsistencies in the OpenAPI documentation"""
openapi_schema = app.openapi()
openapi_schema = fastapi_app.openapi()
# Remove 422 Validation Error from Webfinger lookup (RFC7033 uses 400 Bad Request)
webfinger_responses: dict = openapi_schema["paths"]["/.well-known/webfinger"]["get"]["responses"]

View File

@ -1,4 +1,7 @@
from webfinger import app
"""
Run the FastAPI app with Uvicorn if the package is run as a module
"""
import uvicorn
from webfinger import app
uvicorn.run(app=app, port=7033)

View File

@ -1,11 +1,21 @@
"""
Functions that perform read-write operations
"""
import json
from os import environ as env
from urllib.parse import quote_plus as url_encode
from pathlib import Path
def get_jrd(resource: str) -> dict[str, str | list[str] | dict[str,str]]:
from webfinger.models import JRD, Link
DictJRD = dict[str, str | list[str] | dict[str,str] | list[dict[str,str]]]
def get_jrd_as_dict(resource: str) -> DictJRD:
"""
Obtain a JSON Resource Descriptor (JRD)
Obtain a JSON Resource Descriptor (JRD) as a dictionary
A JRD is a JSON object containing the following:
- subject (string value; SHOULD be present)
@ -15,9 +25,9 @@ def get_jrd(resource: str) -> dict[str, str | list[str] | dict[str,str]]:
Parameters:
resource (str): The URI of the resouce
Returns:
jrd (dict): Parsed JRD
jrd (dict): Parsed JRD dictionary
Raises:
FileNotFoundError: No JRD file exists in the resource directory
@ -25,30 +35,69 @@ def get_jrd(resource: str) -> dict[str, str | list[str] | dict[str,str]]:
json.JSONDecodeError: A file exists, but does not contain a valid JSON object
"""
# Get a filename for the resource document.
dir = env.get("RESOURCE_DIR") or "resource"
directory = env.get("RESOURCE_DIR") or "resource"
filename = resource
path = f"{dir}/{filename}.jrd"
path = f"{directory}/{filename}.jrd"
# If we can't get a file, try percent-encoding
if not Path(path).is_file():
filename = url_encode(resource)
path = f"{dir}/{filename}.jrd"
path = f"{directory}/{filename}.jrd"
# Try plain JSON
if not Path(path).is_file():
filename = resource
path = f"{dir}/{filename}.json"
path = f"{directory}/{filename}.json"
# Try plain JSON and percent-encoding
if not Path(path).is_file():
filename = resource
path = f"{dir}/{filename}.json"
path = f"{directory}/{filename}.json"
# Open the file and load the JSON as a dictionary.
try:
with open(path, "r") as file:
jrd: dict[str, str | list[str] | dict[str,str]] = json.loads(file.read())
except:
raise
# This may fail and raise an exception.
with open(path, mode="r", encoding="utf-8") as file:
jrd: DictJRD = json.loads(file.read())
return jrd
return {k: v for k, v in jrd.items() if v} # remove null values
def get_jrd(resource: str) -> JRD:
"""
Obtain a JSON Resource Descriptor (JRD) as a Pydantic model
A JRD is a JSON object containing the following:
- subject (string value; SHOULD be present)
- aliases (array of string values; OPTIONAL)
- properties (object containing key-value pairs as strings; OPTIONAL)
- links (array of objects containing link relation information; OPTIONAL)
Parameters:
resource (str): The URI of the resouce
Returns:
jrd (JRD): Parsed and valid JRD model
Raises:
FileNotFoundError: No JRD file exists in the resource directory
OSError: A file may exist, but it is not readable
json.JSONDecodeError: A file exists, but does not contain a valid JSON object
pydantic.ValidationError: A file exists and was parsed, but some properties failed validation
"""
jrd = get_jrd_as_dict(resource)
return JRD(
subject = jrd.get('subject') or resource,
aliases = jrd.get('aliases'),
properties = jrd.get('properties'),
links = [
Link(
rel = link.get('rel'),
type = link.get('type'),
href = link.get('href'),
titles = link.get('titles'),
properties = link.get('properties'),
)
for link in jrd.get('links', [])
],
)

View File

@ -1,11 +1,17 @@
from fastapi import APIRouter, Response
"""
Perform WebFinger lookups according to RFC7033
"""
import json
from fastapi import APIRouter
from fastapi.responses import PlainTextResponse, JSONResponse
from fastapi.params import Query
from pydantic import BaseModel
import json
from pydantic import ValidationError
from webfinger.io import get_jrd
from webfinger.models import JRD, JRDResponse
router = APIRouter(
@ -21,62 +27,23 @@ HEADERS = {'Access-Control-Allow-Origin': '*'}
# headers.update({'Access-Control-Allow-Origin': allowed_origins})
## Pydantic models for OpenAPI schema
class Link(BaseModel):
rel: str
type: str | None
href: str | None
titles: str | None
properties: str | None
class Config:
schema_extra = {
"example": r'''{
"rel": "https://webfinger.net/rel/profile-page/",
"type": "text/html",
"href": "https://trwnh.com/~a"
}'''
}
class JRD(BaseModel):
subject: str
aliases: list[str] | None
links: list[Link] | None
class Config:
schema_extra = {
"example": r'''{
"subject": "acct:a@trwnh.com",
"aliases": ["https://ap.trwnh.com/actors/7057bc10-db1c-4ebe-9e00-22cf04be4e5e", "https://trwnh.com/~a", "acct:trwnh@ap.trwnh.com"],
"links": [
{
"rel": "self",
"type": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
"href": "https://trwnh.com/actors/7057bc10-db1c-4ebe-9e00-22cf04be4e5e"
},
{
"rel": "https://webfinger.net/rel/profile-page/",
"type": "text/html",
"href": "https://trwnh.com/~a"
}
]
}'''
}
class JRDResponse(Response):
media_type = "application/jrd+json"
## Example responses for OpenAPI schema
RESOURCE_NOT_PROVIDED = "Query ?resource={resource} to obtain information about that resource."
RESOURCE_NOT_FOUND = "Resource not found"
RESOURCE_NOT_PARSED = "Resource contains invalid JSON or was unreadable"
RESOURCE_NOT_PROVIDED = \
"Query ?resource={resource} to obtain information about that resource."
RESOURCE_NOT_FOUND = \
"Resource not found"
RESOURCE_NOT_PARSED = \
"Resource contains invalid JSON or was unreadable"
RESOURCE_NOT_VALID = \
"Some errors occurred while validating the JRD"
RESPONSES = {
200: {
"description": "OK: The resource you requested was found, and has the returned resource document.",
"description": (
"OK:"
" The resource you requested was found,"
" and has the returned resource document."),
"model": JRD,
"content": {
"application/jrd+json": {
@ -85,7 +52,9 @@ RESPONSES = {
},
},
400: {
"description": "Bad Request: No resource was provided.",
"description": (
"Bad Request:"
" No resource was provided."),
"content": {
"text/plain": {
"example": RESOURCE_NOT_PROVIDED
@ -93,7 +62,9 @@ RESPONSES = {
},
},
404: {
"description": "Not Found: Could not find a document for the provided resource.",
"description": (
"Not Found:"
" Could not find a document for the provided resource."),
"content": {
"text/plain": {
"example": RESOURCE_NOT_FOUND
@ -101,7 +72,10 @@ RESPONSES = {
},
},
500: {
"description": "Server Error: The resource exists, but contains invalid JSON or was unreadable.",
"description": (
"Server Error:"
" The resource exists,"
" but contains invalid JSON or was unreadable."),
"content": {
"text/plain": {
"example": RESOURCE_NOT_PARSED
@ -116,7 +90,9 @@ RESPONSES = {
@router.get(
"",
summary = "Lookup a Webfinger resource",
description = "Query the Webfinger service for a resource URI and obtain its associated resource document",
description = (
"Query the Webfinger service for a resource URI"
" and obtain its associated resource document"),
tags = ["RFC7033"],
response_model = JRD,
response_class = JRDResponse,
@ -126,12 +102,18 @@ async def lookup(
resource: str = Query(
None,
title = "Resource URI",
description = "The subject whose document you are looking up. If not provided, you will get a 400 Bad Request."
description = (
"The subject whose document you are looking up."
" If not provided, you will get a 400 Bad Request."
),
),
rel: list[str] = Query(
None,
title = "Link relation",
description = "If provided, filter the links array for this rel-value only. This parameter may be included multiple times."
description = (
"If provided, filter the links array for this rel-value only."
" This parameter may be included multiple times."
),
)
):
"""
@ -144,43 +126,43 @@ async def lookup(
status_code=400,
headers = HEADERS,
)
# Otherwise, try to read the resource document.
try:
jrd = get_jrd(resource)
except FileNotFoundError: # JRD file does not exist
except FileNotFoundError:
# JRD file does not exist
return PlainTextResponse(
content = RESOURCE_NOT_FOUND,
status_code = 404,
headers = HEADERS,
)
except: # JRD file could not be read or parsed
except (OSError, json.JSONDecodeError):
# JRD file could not be read or parsed
return PlainTextResponse(
content = RESOURCE_NOT_PARSED,
status_code = 500,
headers = HEADERS,
)
# Obtain values from the resource document.
subject: str = jrd.get('subject', None) or resource
aliases: list[str] = [alias for alias in jrd.get('aliases', [])]
properties: dict[str, str] = jrd.get('properties', {})
links: list[dict[str,str]] = [link for link in jrd.get('links', [])]
except ValidationError as exception:
# JRD was parsed but contained type or value errors
errors = [RESOURCE_NOT_VALID]
for error in exception.errors():
print(error)
errors.append(f"{error['msg']} for {error['loc'][0]}")
return PlainTextResponse(
content = '\n- '.join(errors),
status_code=500,
headers = HEADERS,
)
# If optional rel is provided, then filter only for links with that rel-type.
if rel:
links = [link for link in links if link.get('rel', '') in rel]
jrd.links = [link for link in jrd.links if link.rel in rel]
# Construct JSON response and return it.
content = {
"subject": subject,
"aliases": aliases,
"properties": properties,
"links": links,
}
content = {k: v for k, v in content.items() if v} # remove null values
return JSONResponse(
content = content,
content = jrd.dict(exclude_none=True),
media_type = "application/jrd+json",
headers = HEADERS,
)

66
webfinger/models.py Normal file
View File

@ -0,0 +1,66 @@
"""
Models used for validation and OpenAPI responses
"""
from pydantic import BaseModel, StrictStr
from fastapi import Response
class Link(BaseModel):
"""
Describes an entry in the JRD `link` array
"""
rel: StrictStr
type: StrictStr | None
href: StrictStr | None
titles: StrictStr | None
properties: dict[StrictStr, StrictStr] | None
class Config: # pylint: disable=too-few-public-methods
"""
Provide an example `link` item
"""
schema_extra = {
"example": r'''{
"rel": "https://webfinger.net/rel/profile-page/",
"type": "text/html",
"href": "https://trwnh.com/~a"
}'''
}
class JRD(BaseModel):
"""
Describes a JSON Resource Descriptor document
"""
subject: StrictStr
aliases: list[StrictStr] | None
links: list[Link] | None
properties: dict[StrictStr, StrictStr] | None
class Config: # pylint: disable=too-few-public-methods
"""
Provide an example JRD document
"""
schema_extra = {
"example": r'''{
"subject": "acct:a@trwnh.com",
"aliases": ["https://ap.trwnh.com/actors/7057bc10-db1c-4ebe-9e00-22cf04be4e5e", "https://trwnh.com/~a", "acct:trwnh@ap.trwnh.com"],
"links": [
{
"rel": "self",
"type": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
"href": "https://trwnh.com/actors/7057bc10-db1c-4ebe-9e00-22cf04be4e5e"
},
{
"rel": "https://webfinger.net/rel/profile-page/",
"type": "text/html",
"href": "https://trwnh.com/~a"
}
]
}'''
}
class JRDResponse(Response):
"""
Specify JRD Content-Type for responding to WebFinger lookup requests
"""
media_type = "application/jrd+json"