From 13e849888ff4587aab4b79a7835e0e83cb8d6f45 Mon Sep 17 00:00:00 2001 From: Jared Murrell Date: Mon, 21 Aug 2023 21:40:11 -0400 Subject: [PATCH] WIP migrating to ghapi for continuous API support --- Pipfile | 2 +- Pipfile.lock | 470 ++++++++++++++++++++++------------------------ githubapp/core.py | 270 ++++++++++++++++++-------- 3 files changed, 421 insertions(+), 321 deletions(-) diff --git a/Pipfile b/Pipfile index fd917b1..5c3bd89 100644 --- a/Pipfile +++ b/Pipfile @@ -16,7 +16,7 @@ tox = "*" tox-gh-actions = "*" [packages] -"github3.py" = "*" +ghapi = "*" flask = "*" pyyaml = "*" ldap3 = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 6232c0a..9ef7ab0 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,22 +1,28 @@ { "_meta": { "hash": { - "sha256": "d0b711fd630097c1e79141ad687786fa047dca222a8b1f1e4dc0347ef80742d0" + "sha256": "f6dc1f1f88c0ead565030d63606795feabd48cbd44c673fda9368032184337f3" }, "pipfile-spec": 6, - "requires": {"python_version": "3.9"}, + "requires": { + "python_version": "3.9" + }, "sources": [ - {"name": "pypi", "url": "https://pypi.org/simple", "verify_ssl": true} - ], + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] }, "default": { "aenum": { "hashes": [ "sha256:27b1710b9d084de6e2e695dab78fe9f269de924b51ae2850170ee7e1ca6288a5", "sha256:8cbd76cd18c4f870ff39b24284d3ea028fbe8731a58df3aa581e434c575b9559", - "sha256:e0dfaeea4c2bd362144b87377e2c61d91958c5ed0b4daf89cb6f45ae23af6288", + "sha256:e0dfaeea4c2bd362144b87377e2c61d91958c5ed0b4daf89cb6f45ae23af6288" ], - "version": "==3.1.15", + "version": "==3.1.15" }, "aiohttp": { "hashes": [ @@ -106,68 +112,68 @@ "sha256:eca4bf3734c541dc4f374ad6010a68ff6c6748f00451707f39857f429ca36ced", "sha256:f83a552443a526ea38d064588613aca983d0ee0038801bc93c0c916428310c28", "sha256:fb1558def481d84f03b45888473fc5a1f35747b5f334ef4e7a571bc0dfcb11f8", - "sha256:fd1ed388ea7fbed22c4968dd64bab0198de60750a25fe8c0c9d4bef5abe13824", + "sha256:fd1ed388ea7fbed22c4968dd64bab0198de60750a25fe8c0c9d4bef5abe13824" ], "markers": "python_version >= '3.6'", - "version": "==3.8.5", + "version": "==3.8.5" }, "aiosignal": { "hashes": [ "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc", - "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17", + "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17" ], "markers": "python_version >= '3.7'", - "version": "==1.3.1", + "version": "==1.3.1" }, "apscheduler": { "hashes": [ - "sha256:1611d207b095ff52d97884e90736c192bdd94dbd44ce27eb92c3f1da24a400d3", - "sha256:3f17fd3915f14f4bfa597f552130a14a9d1cc74a002fa1d95dd671cb48dba70e", + "sha256:e6df071b27d9be898e486bc7940a7be50b4af2e9da7c08f0744a96d4bd4cef4a", + "sha256:fb91e8a768632a4756a585f79ec834e0e27aad5860bac7eaa523d9ccefd87661" ], "index": "pypi", - "version": "==3.10.3", + "version": "==3.10.4" }, "async-timeout": { "hashes": [ "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f", - "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028", + "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028" ], "markers": "python_version >= '3.7'", - "version": "==4.0.3", + "version": "==4.0.3" }, "asyncio": { "hashes": [ "sha256:83360ff8bc97980e4ff25c964c7bd3923d333d177aa4f7fb736b019f26c7cb41", "sha256:b62c9157d36187eca799c378e572c969f0da87cd5fc42ca372d92cdb06e7e1de", "sha256:c46a87b48213d7464f22d9a497b9eef8c1928b68320a2fa94240f969f6fec08c", - "sha256:c4d18b22701821de07bd6aea8b53d21449ec0ec5680645e5317062ea21817d2d", + "sha256:c4d18b22701821de07bd6aea8b53d21449ec0ec5680645e5317062ea21817d2d" ], "index": "pypi", - "version": "==3.4.3", + "version": "==3.4.3" }, "attrs": { "hashes": [ "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04", - "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015", + "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015" ], "markers": "python_version >= '3.7'", - "version": "==23.1.0", + "version": "==23.1.0" }, "blinker": { "hashes": [ "sha256:4afd3de66ef3a9f8067559fb7a1cbe555c17dcbe15971b05d1b625c3e7abe213", - "sha256:c3d739772abb7bc2860abf5f2ec284223d9ad5c76da018234f6f50d6f31ab1f0", + "sha256:c3d739772abb7bc2860abf5f2ec284223d9ad5c76da018234f6f50d6f31ab1f0" ], "markers": "python_version >= '3.7'", - "version": "==1.6.2", + "version": "==1.6.2" }, "certifi": { "hashes": [ "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", - "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9", + "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9" ], "index": "pypi", - "version": "==2023.7.22", + "version": "==2023.7.22" }, "cffi": { "hashes": [ @@ -234,9 +240,9 @@ "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6", "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b", "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01", - "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0", + "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0" ], - "version": "==1.15.1", + "version": "==1.15.1" }, "charset-normalizer": { "hashes": [ @@ -314,18 +320,18 @@ "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1", "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c", "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac", - "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa", + "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa" ], "markers": "python_full_version >= '3.7.0'", - "version": "==3.2.0", + "version": "==3.2.0" }, "click": { "hashes": [ - "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd", - "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5", + "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" ], "markers": "python_version >= '3.7'", - "version": "==8.1.6", + "version": "==8.1.7" }, "cryptography": { "hashes": [ @@ -351,32 +357,40 @@ "sha256:ab8de0d091acbf778f74286f4989cf3d1528336af1b59f3e5d2ebca8b5fe49e1", "sha256:aeb57c421b34af8f9fe830e1955bf493a86a7996cc1338fe41b30047d16e962c", "sha256:ce785cf81a7bdade534297ef9e490ddff800d956625020ab2ec2780a556c313e", - "sha256:d0d651aa754ef58d75cec6edfbd21259d93810b73f6ec246436a21b7841908de", + "sha256:d0d651aa754ef58d75cec6edfbd21259d93810b73f6ec246436a21b7841908de" ], "index": "pypi", - "version": "==41.0.3", + "version": "==41.0.3" }, "ecdsa": { "hashes": [ "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49", - "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd", + "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.18.0" + }, + "fastcore": { + "hashes": [ + "sha256:a7d7e89faf968f2d8584df2deca344c3974f6cf476e1299cd3c067d8fa7440e9", + "sha256:f1a2eb04eb7933f3f9eb4064852817df44dc96e20fab5658c14c035815269a3f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", - "version": "==0.18.0", + "markers": "python_version >= '3.7'", + "version": "==1.5.29" }, "flask": { "hashes": [ - "sha256:77fd4e1249d8c9923de34907236b747ced06e5467ecac1a7bb7115ae0e9670b0", - "sha256:8c2f9abd47a9e8df7f0c3f091ce9497d011dc3b31effcf4c85a6e2b50f4114ef", + "sha256:09c347a92aa7ff4a8e7f3206795f30d826654baf38b873d0744cd571ca609efc", + "sha256:f69fcd559dc907ed196ab9df0e48471709175e696d6e698dd4dbe940f96ce66b" ], "index": "pypi", - "version": "==2.3.2", + "version": "==2.3.3" }, "flatdict": { "hashes": [ "sha256:cd32f08fd31ed21eb09ebc76f06b6bd12046a24f77beb1fd0281917e47f26742" ], - "version": "==4.0.1", + "version": "==4.0.1" }, "frozendict": { "hashes": [ @@ -416,10 +430,10 @@ "sha256:f0f573dc4861dd7ec9e055c8cceaf45355e894e749f621f199aab7b311ac4bdb", "sha256:f2a4e818ac457f6354401dcb631527af25e5a20fcfc81e6b5054b45fc245caca", "sha256:f83fed36497af9562ead5e9fb8443224ba2781786bd3b92b1087cb7d0ff20135", - "sha256:ffc684773de7c88724788fa9787d0016fd75830412d58acbd9ed1a04762c675b", + "sha256:ffc684773de7c88724788fa9787d0016fd75830412d58acbd9ed1a04762c675b" ], "markers": "python_version >= '3.6'", - "version": "==2.3.8", + "version": "==2.3.8" }, "frozenlist": { "hashes": [ @@ -483,50 +497,42 @@ "sha256:e74b0506fa5aa5598ac6a975a12aa8928cbb58e1f5ac8360792ef15de1aa848f", "sha256:f0ed05f5079c708fe74bf9027e95125334b6978bf07fd5ab923e9e55e5fbb9d3", "sha256:f61e2dc5ad442c52b4887f1fdc112f97caeff4d9e6ebe78879364ac59f1663e1", - "sha256:fec520865f42e5c7f050c2a79038897b1c7d1595e907a9e08e3353293ffc948e", + "sha256:fec520865f42e5c7f050c2a79038897b1c7d1595e907a9e08e3353293ffc948e" ], "markers": "python_version >= '3.8'", - "version": "==1.4.0", + "version": "==1.4.0" }, - "github3.py": { + "ghapi": { "hashes": [ - "sha256:30d571076753efc389edc7f9aaef338a4fcb24b54d8968d5f39b1342f45ddd36", - "sha256:a89af7de25650612d1da2f0609622bcdeb07ee8a45a1c06b2d16a05e4234e753", + "sha256:9e7632c762d6f9c288e3b046b2d58c2f7992dda7c925683df435440912b10625", + "sha256:cb5c7008a89c270157adbaf5b2fd6951e9d9fc76131b9bec16118a558a6a4c04" ], "index": "pypi", - "version": "==4.0.1", + "version": "==1.0.4" }, "idna": { "hashes": [ "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", - "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2", + "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" ], "markers": "python_version >= '3.5'", - "version": "==3.4", - }, - "importlib-metadata": { - "hashes": [ - "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb", - "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743", - ], - "markers": "python_version < '3.10'", - "version": "==6.8.0", + "version": "==3.4" }, "itsdangerous": { "hashes": [ "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44", - "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a", + "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a" ], "markers": "python_version >= '3.7'", - "version": "==2.1.2", + "version": "==2.1.2" }, "jinja2": { "hashes": [ "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", - "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", + "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" ], "markers": "python_version >= '3.7'", - "version": "==3.1.2", + "version": "==3.1.2" }, "ldap3": { "hashes": [ @@ -534,10 +540,10 @@ "sha256:5630d1383e09ba94839e253e013f1aa1a2cf7a547628ba1265cb7b9a844b5687", "sha256:5869596fc4948797020d3f03b7939da938778a0f9e2009f7a072ccf92b8e8d70", "sha256:5ab7febc00689181375de40c396dcad4f2659cd260fc5e94c508b6d77c17e9d5", - "sha256:f3e7fc4718e3f09dda568b57100095e0ce58633bcabbed8667ce3f8fbaa4229f", + "sha256:f3e7fc4718e3f09dda568b57100095e0ce58633bcabbed8667ce3f8fbaa4229f" ], "index": "pypi", - "version": "==2.9.1", + "version": "==2.9.1" }, "markupsafe": { "hashes": [ @@ -590,18 +596,18 @@ "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9", "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57", "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc", - "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2", + "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2" ], "markers": "python_version >= '3.7'", - "version": "==2.1.3", + "version": "==2.1.3" }, "msal": { "hashes": [ "sha256:25c9a33acf84301f93d1fdbe9f1a9c60cd38af0d5fffdbfa378138fc7bc1e86b", - "sha256:3342e0837a047007f9d479e814b559c3219767453d57920dc40a31986862048b", + "sha256:3342e0837a047007f9d479e814b559c3219767453d57920dc40a31986862048b" ], "index": "pypi", - "version": "==1.23.0", + "version": "==1.23.0" }, "multidict": { "hashes": [ @@ -678,41 +684,57 @@ "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171", "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf", "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d", - "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba", + "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba" ], "markers": "python_version >= '3.7'", - "version": "==6.0.4", + "version": "==6.0.4" }, "okta": { "hashes": [ "sha256:47e28d04dc99afcc90e2aa151612e9e1f435ecc2b23d9e3a3843f2cc6e6b11de", - "sha256:91bceaae2c9bccdffceafa2fdd0fa41f6963f6994ad46cd63e573f35cfe7585d", + "sha256:91bceaae2c9bccdffceafa2fdd0fa41f6963f6994ad46cd63e573f35cfe7585d" ], "index": "pypi", - "version": "==2.9.2", + "version": "==2.9.2" }, "onelogin": { "hashes": [ "sha256:20228a6e0bb66f4aafe06e43293b6052e7b42f9066ac65babce1f14abb006245", - "sha256:e32e828f66461c8ae82bd7fd6a2d33c53c0f525884d737b759eae4b2a30a239b", + "sha256:e32e828f66461c8ae82bd7fd6a2d33c53c0f525884d737b759eae4b2a30a239b" ], "index": "pypi", - "version": "==3.1.6", + "version": "==3.1.6" + }, + "packaging": { + "hashes": [ + "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", + "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f" + ], + "markers": "python_version >= '3.7'", + "version": "==23.1" + }, + "pip": { + "hashes": [ + "sha256:7ccf472345f20d35bdc9d1841ff5f313260c2c33fe417f48c30ac46cccabf5be", + "sha256:fb0bd5435b3200c602b5bf61d2d43c2f13c02e29c1707567ae7fbc514eb9faf2" + ], + "markers": "python_version >= '3.7'", + "version": "==23.2.1" }, "pyasn1": { "hashes": [ "sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57", - "sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde", + "sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==0.5.0", + "version": "==0.5.0" }, "pycparser": { "hashes": [ "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", - "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206", + "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" ], - "version": "==2.21", + "version": "==2.21" }, "pycryptodomex": { "hashes": [ @@ -747,57 +769,59 @@ "sha256:d84e105787f5e5d36ec6a581ff37a1048d12e638688074b2a00bcf402f9aa1c2", "sha256:e00a4bacb83a2627e8210cb353a2e31f04befc1155db2976e5e239dd66482278", "sha256:f237278836dda412a325e9340ba2e6a84cb0f56b9244781e5b61f10b3905de88", - "sha256:f9ab5ef0718f6a8716695dea16d83b671b22c45e9c0c78fd807c32c0192e54b5", + "sha256:f9ab5ef0718f6a8716695dea16d83b671b22c45e9c0c78fd807c32c0192e54b5" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==3.18.0", + "version": "==3.18.0" }, "pydash": { "hashes": [ "sha256:10e506935953fde4b0d6fe21a88e17783cd1479256ae96f285b5f89063b4efd6", - "sha256:7d9df7e9f36f2bbb08316b609480e7c6468185473a21bdd8e65dda7915565a26", + "sha256:7d9df7e9f36f2bbb08316b609480e7c6468185473a21bdd8e65dda7915565a26" ], "markers": "python_version >= '3.7'", - "version": "==7.0.6", + "version": "==7.0.6" }, "pyjwt": { - "extras": ["crypto"], + "extras": [ + "crypto" + ], "hashes": [ "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de", - "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320", + "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320" ], "markers": "python_version >= '3.7'", - "version": "==2.8.0", + "version": "==2.8.0" }, "python-dateutil": { "hashes": [ "sha256:063df5763652e21de43de7d9e00ccf239f953a832941e37be541614732cdfc93", - "sha256:88f9287c0174266bb0d8cedd395cfba9c58e87e5ad86b2ce58859bc11be3cf02", + "sha256:88f9287c0174266bb0d8cedd395cfba9c58e87e5ad86b2ce58859bc11be3cf02" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", - "version": "==2.7.5", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.7.5" }, "python-dotenv": { "hashes": [ "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba", - "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a", + "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a" ], "index": "pypi", - "version": "==1.0.0", + "version": "==1.0.0" }, "python-jose": { "hashes": [ "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a", - "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a", + "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a" ], - "version": "==3.3.0", + "version": "==3.3.0" }, "pytz": { "hashes": [ "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588", - "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb", + "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb" ], - "version": "==2023.3", + "version": "==2023.3" }, "pyyaml": { "hashes": [ @@ -840,90 +864,82 @@ "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585", "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", - "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f", + "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" ], "index": "pypi", - "version": "==6.0.1", + "version": "==6.0.1" }, "requests": { "hashes": [ "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", - "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1", + "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" ], "markers": "python_version >= '3.7'", - "version": "==2.31.0", + "version": "==2.31.0" }, "rsa": { "hashes": [ "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7", - "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21", + "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21" ], "markers": "python_version >= '3.6' and python_version < '4'", - "version": "==4.9", + "version": "==4.9" }, "setuptools": { "hashes": [ - "sha256:d59c97e7b774979a5ccb96388efc9eb65518004537e85d52e81eaee89ab6dd91", - "sha256:e13e1b0bc760e9b0127eda042845999b2f913e12437046e663b833aa96d89715", + "sha256:3d4dfa6d95f1b101d695a6160a7626e15583af71a5f52176efa5d39a054d475d", + "sha256:3d8083eed2d13afc9426f227b24fd1659489ec107c0e86cec2ffdde5c92e790b" ], "index": "pypi", - "version": "==68.1.0", + "version": "==68.1.2" }, "six": { "hashes": [ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", - "version": "==1.16.0", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.16.0" }, "typing-extensions": { "hashes": [ "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02", - "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6", + "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6" ], "markers": "python_version >= '3.7'", - "version": "==4.3.0", + "version": "==4.3.0" }, "tzlocal": { "hashes": [ "sha256:46eb99ad4bdb71f3f72b7d24f4267753e240944ecfc16f25d2719ba89827a803", - "sha256:f3596e180296aaf2dbd97d124fe76ae3a0e3d32b258447de7b939b3fd4be992f", + "sha256:f3596e180296aaf2dbd97d124fe76ae3a0e3d32b258447de7b939b3fd4be992f" ], "markers": "python_version >= '3.7'", - "version": "==5.0.1", - }, - "uritemplate": { - "hashes": [ - "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0", - "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e", - ], - "markers": "python_version >= '3.6'", - "version": "==4.1.1", + "version": "==5.0.1" }, "urllib3": { "hashes": [ "sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f", - "sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14", + "sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==1.26.16", + "version": "==1.26.16" }, "werkzeug": { "hashes": [ "sha256:2b8c0e447b4b9dbcc85dd97b6eeb4dcbaf6c8b6c3be0bd654e25553e0a2157d8", - "sha256:effc12dba7f3bd72e605ce49807bbe692bd729c3bb122a3b91747a6ae77df528", + "sha256:effc12dba7f3bd72e605ce49807bbe692bd729c3bb122a3b91747a6ae77df528" ], "index": "pypi", - "version": "==2.3.7", + "version": "==2.3.7" }, "xmltodict": { "hashes": [ "sha256:341595a488e3e01a85a9d8911d8912fd922ede5fecc4dce437eb4b6c8d037e56", - "sha256:aa89e8fd76320154a40d19a0df04a4695fb9dc5ba977cbb68ab3e4eb225e7852", + "sha256:aa89e8fd76320154a40d19a0df04a4695fb9dc5ba977cbb68ab3e4eb225e7852" ], "markers": "python_version >= '3.4'", - "version": "==0.13.0", + "version": "==0.13.0" }, "yarl": { "hashes": [ @@ -1000,36 +1016,28 @@ "sha256:f364d3480bffd3aa566e886587eaca7c8c04d74f6e8933f3f2c996b7f09bee1b", "sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7", "sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78", - "sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7", + "sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7" ], "markers": "python_version >= '3.7'", - "version": "==1.9.2", - }, - "zipp": { - "hashes": [ - "sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0", - "sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147", - ], - "markers": "python_version >= '3.8'", - "version": "==3.16.2", - }, + "version": "==1.9.2" + } }, "develop": { "aspy.refactor-imports": { "hashes": [ "sha256:3c7329cdb2613c46fcd757c8e45120efbc3d4b9db805092911eb605c19c5795c", - "sha256:f306037682479945df61b2e6d01bf97256d68f3e704742768deef549e0d61fbb", + "sha256:f306037682479945df61b2e6d01bf97256d68f3e704742768deef549e0d61fbb" ], "markers": "python_version >= '3.7'", - "version": "==3.0.2", + "version": "==3.0.2" }, "astroid": { "hashes": [ "sha256:389656ca57b6108f939cf5d2f9a2a825a3be50ba9d589670f393236e0a03b91c", - "sha256:903f024859b7c7687d7a7f3a3f73b17301f8e42dfd9cc9df9d4418172d3e2dbd", + "sha256:903f024859b7c7687d7a7f3a3f73b17301f8e42dfd9cc9df9d4418172d3e2dbd" ], "markers": "python_full_version >= '3.7.2'", - "version": "==2.15.6", + "version": "==2.15.6" }, "black": { "hashes": [ @@ -1054,50 +1062,50 @@ "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2", "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a", "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f", - "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995", + "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995" ], "index": "pypi", - "version": "==23.7.0", + "version": "==23.7.0" }, "cachetools": { "hashes": [ "sha256:95ef631eeaea14ba2e36f06437f36463aac3a096799e876ee55e5cdccb102590", - "sha256:dce83f2d9b4e1f732a8cd44af8e8fab2dbe46201467fc98b3ef8f269092bf62b", + "sha256:dce83f2d9b4e1f732a8cd44af8e8fab2dbe46201467fc98b3ef8f269092bf62b" ], "markers": "python_version >= '3.7'", - "version": "==5.3.1", + "version": "==5.3.1" }, "cfgv": { "hashes": [ "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", - "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", + "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560" ], "markers": "python_version >= '3.8'", - "version": "==3.4.0", + "version": "==3.4.0" }, "chardet": { "hashes": [ "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", - "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", + "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970" ], "markers": "python_version >= '3.7'", - "version": "==5.2.0", + "version": "==5.2.0" }, "click": { "hashes": [ - "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd", - "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5", + "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" ], "markers": "python_version >= '3.7'", - "version": "==8.1.6", + "version": "==8.1.7" }, "colorama": { "hashes": [ "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", - "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", + "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", - "version": "==0.4.6", + "version": "==0.4.6" }, "coverage": { "hashes": [ @@ -1152,72 +1160,64 @@ "sha256:e2ac9a1de294773b9fa77447ab7e529cf4fe3910f6a0832816e5f3d538cfea9a", "sha256:e61260ec93f99f2c2d93d264b564ba912bec502f679793c56f678ba5251f0393", "sha256:fac440c43e9b479d1241fe9d768645e7ccec3fb65dc3a5f6e90675e75c3f3e3a", - "sha256:fc0ed8d310afe013db1eedd37176d0839dc66c96bcfcce8f6607a73ffea2d6ba", + "sha256:fc0ed8d310afe013db1eedd37176d0839dc66c96bcfcce8f6607a73ffea2d6ba" ], "markers": "python_version >= '3.8'", - "version": "==7.3.0", + "version": "==7.3.0" }, "dill": { "hashes": [ "sha256:76b122c08ef4ce2eedcd4d1abd8e641114bfc6c2867f49f3c41facf65bf19f5e", - "sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03", + "sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03" ], - "markers": "python_version < '3.11'", - "version": "==0.3.7", + "markers": "python_version >= '3.11'", + "version": "==0.3.7" }, "distlib": { "hashes": [ "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057", - "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8", + "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8" ], - "version": "==0.3.7", - }, - "exceptiongroup": { - "hashes": [ - "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9", - "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3", - ], - "markers": "python_version < '3.11'", - "version": "==1.1.3", + "version": "==0.3.7" }, "filelock": { "hashes": [ "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81", - "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec", + "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec" ], "markers": "python_version >= '3.7'", - "version": "==3.12.2", + "version": "==3.12.2" }, "green": { "hashes": [ "sha256:8865d0b77b5cb13851dd601a58ad2c82bcb52da7ca106f0538c5787f127368a6" ], "index": "pypi", - "version": "==3.4.3", + "version": "==3.4.3" }, "identify": { "hashes": [ - "sha256:7243800bce2f58404ed41b7c002e53d4d22bcf3ae1b7900c2d7aefd95394bf7f", - "sha256:c22a8ead0d4ca11f1edd6c9418c3220669b3b7533ada0a0ffa6cc0ef85cf9b54", + "sha256:287b75b04a0e22d727bc9a41f0d4f3c1bcada97490fa6eabb5b28f0e9097e733", + "sha256:fdb527b2dfe24602809b2201e033c2a113d7bdf716db3ca8e3243f735dcecaba" ], "markers": "python_version >= '3.8'", - "version": "==2.5.26", + "version": "==2.5.27" }, "iniconfig": { "hashes": [ "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", - "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", + "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" ], "markers": "python_version >= '3.7'", - "version": "==2.0.0", + "version": "==2.0.0" }, "isort": { "hashes": [ "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504", - "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6", + "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6" ], "index": "pypi", - "version": "==5.12.0", + "version": "==5.12.0" }, "lazy-object-proxy": { "hashes": [ @@ -1256,10 +1256,10 @@ "sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455", "sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734", "sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb", - "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59", + "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59" ], "markers": "python_version >= '3.7'", - "version": "==1.9.0", + "version": "==1.9.0" }, "lxml": { "hashes": [ @@ -1354,106 +1354,106 @@ "sha256:fbf521479bcac1e25a663df882c46a641a9bff6b56dc8b0fafaebd2f66fb231b", "sha256:fc9b106a1bf918db68619fdcd6d5ad4f972fdd19c01d19bdb6bf63f3589a9ec5", "sha256:fcdd00edfd0a3001e0181eab3e63bd5c74ad3e67152c84f93f13769a40e073a7", - "sha256:fe4bda6bd4340caa6e5cf95e73f8fea5c4bfc55763dd42f1b50a94c1b4a2fbd4", + "sha256:fe4bda6bd4340caa6e5cf95e73f8fea5c4bfc55763dd42f1b50a94c1b4a2fbd4" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==4.9.3", + "version": "==4.9.3" }, "mccabe": { "hashes": [ "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", - "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", + "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e" ], "markers": "python_version >= '3.6'", - "version": "==0.7.0", + "version": "==0.7.0" }, "mypy-extensions": { "hashes": [ "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", - "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", + "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" ], "markers": "python_version >= '3.5'", - "version": "==1.0.0", + "version": "==1.0.0" }, "nodeenv": { "hashes": [ "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2", - "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec", + "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", - "version": "==1.8.0", + "version": "==1.8.0" }, "packaging": { "hashes": [ "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", - "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f", + "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f" ], "markers": "python_version >= '3.7'", - "version": "==23.1", + "version": "==23.1" }, "pathspec": { "hashes": [ "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20", - "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3", + "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3" ], "markers": "python_version >= '3.7'", - "version": "==0.11.2", + "version": "==0.11.2" }, "platformdirs": { "hashes": [ "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d", - "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d", + "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d" ], "markers": "python_version >= '3.7'", - "version": "==3.10.0", + "version": "==3.10.0" }, "pluggy": { "hashes": [ "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849", - "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3", + "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3" ], "markers": "python_version >= '3.7'", - "version": "==1.2.0", + "version": "==1.2.0" }, "pre-commit": { "hashes": [ "sha256:10badb65d6a38caff29703362271d7dca483d01da88f9d7e05d0b97171c136cb", - "sha256:a2256f489cd913d575c145132ae196fe335da32d91a8294b7afe6622335dd023", + "sha256:a2256f489cd913d575c145132ae196fe335da32d91a8294b7afe6622335dd023" ], "index": "pypi", - "version": "==3.3.3", + "version": "==3.3.3" }, "pylint": { "hashes": [ "sha256:73995fb8216d3bed149c8d51bba25b2c52a8251a2c8ac846ec668ce38fab5413", - "sha256:f7b601cbc06fef7e62a754e2b41294c2aa31f1cb659624b9a85bcba29eaf8252", + "sha256:f7b601cbc06fef7e62a754e2b41294c2aa31f1cb659624b9a85bcba29eaf8252" ], "index": "pypi", - "version": "==2.17.5", + "version": "==2.17.5" }, "pyproject-api": { "hashes": [ "sha256:8d41f3f0c04f0f6a830c27b1c425fa66699715ae06d8a054a1c5eeaaf8bfb145", - "sha256:ca462d457880340ceada078678a296ac500061cef77a040e1143004470ab0046", + "sha256:ca462d457880340ceada078678a296ac500061cef77a040e1143004470ab0046" ], "markers": "python_version >= '3.8'", - "version": "==1.5.4", + "version": "==1.5.4" }, "pytest": { "hashes": [ "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32", - "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a", + "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a" ], "index": "pypi", - "version": "==7.4.0", + "version": "==7.4.0" }, "pytest-mock": { "hashes": [ "sha256:21c279fff83d70763b05f8874cc9cfb3fcacd6d354247a976f9529d19f9acf39", - "sha256:7f6b125602ac6d743e523ae0bfa71e1a697a2f5534064528c6ff84c2f7c2fc7f", + "sha256:7f6b125602ac6d743e523ae0bfa71e1a697a2f5534064528c6ff84c2f7c2fc7f" ], "index": "pypi", - "version": "==3.11.1", + "version": "==3.11.1" }, "pyyaml": { "hashes": [ @@ -1496,82 +1496,66 @@ "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585", "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", - "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f", + "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" ], "index": "pypi", - "version": "==6.0.1", + "version": "==6.0.1" }, "seed-isort-config": { "hashes": [ "sha256:8601fb715a5a4aac39256bbf73c2da6a81f964da9c9d9897ab9074db3663526f", - "sha256:be4cfef8f9a3fe8ea1817069c6b624538ac0b429636ec746edeb27e98ed628c8", + "sha256:be4cfef8f9a3fe8ea1817069c6b624538ac0b429636ec746edeb27e98ed628c8" ], "index": "pypi", - "version": "==2.2.0", + "version": "==2.2.0" }, "setuptools": { "hashes": [ - "sha256:d59c97e7b774979a5ccb96388efc9eb65518004537e85d52e81eaee89ab6dd91", - "sha256:e13e1b0bc760e9b0127eda042845999b2f913e12437046e663b833aa96d89715", + "sha256:3d4dfa6d95f1b101d695a6160a7626e15583af71a5f52176efa5d39a054d475d", + "sha256:3d8083eed2d13afc9426f227b24fd1659489ec107c0e86cec2ffdde5c92e790b" ], "index": "pypi", - "version": "==68.1.0", - }, - "tomli": { - "hashes": [ - "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", - "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f", - ], - "markers": "python_version < '3.11'", - "version": "==2.0.1", + "version": "==68.1.2" }, "tomlkit": { "hashes": [ "sha256:38e1ff8edb991273ec9f6181244a6a391ac30e9f5098e7535640ea6be97a7c86", - "sha256:712cbd236609acc6a3e2e97253dfc52d4c2082982a88f61b640ecf0817eab899", + "sha256:712cbd236609acc6a3e2e97253dfc52d4c2082982a88f61b640ecf0817eab899" ], "markers": "python_version >= '3.7'", - "version": "==0.12.1", + "version": "==0.12.1" }, "tox": { "hashes": [ - "sha256:9b6d38fe422599d084afd89375b4803f4bc1f8f16573c77c8fd8ffcc6938f1ff", - "sha256:c80de60fe26f9a009b0a763888bf2099ccfbef50a0279a6b9f6de40eb4eb7457", + "sha256:e041b2165375be690aca0ec4d96360c6906451380520e4665bf274f66112be35", + "sha256:e4a1b1438955a6da548d69a52350054350cf6a126658c20943261c48ed6d4c92" ], "index": "pypi", - "version": "==4.9.0", + "version": "==4.10.0" }, "tox-gh-actions": { "hashes": [ "sha256:5954766fe2ed0e284f3cdc87535dfdf68d0f803f1011b17ff8cf52ed3156e6c1", - "sha256:ffd4151fe8b62c6f401a2fc5a01317835d7ab380923f6e0d063c300750308328", + "sha256:ffd4151fe8b62c6f401a2fc5a01317835d7ab380923f6e0d063c300750308328" ], "index": "pypi", - "version": "==3.1.3", - }, - "typing-extensions": { - "hashes": [ - "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02", - "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6", - ], - "markers": "python_version >= '3.7'", - "version": "==4.3.0", + "version": "==3.1.3" }, "unidecode": { "hashes": [ "sha256:547d7c479e4f377b430dd91ac1275d593308dce0fc464fb2ab7d41f82ec653be", - "sha256:fed09cf0be8cf415b391642c2a5addfc72194407caee4f98719e40ec2a72b830", + "sha256:fed09cf0be8cf415b391642c2a5addfc72194407caee4f98719e40ec2a72b830" ], "markers": "python_version >= '3.5'", - "version": "==1.3.6", + "version": "==1.3.6" }, "virtualenv": { "hashes": [ "sha256:95a6e9398b4967fbcb5fef2acec5efaf9aa4972049d9ae41f95e0972a683fd02", - "sha256:e5c3b4ce817b0b328af041506a2a299418c98747c4b1e68cb7527e74ced23efc", + "sha256:e5c3b4ce817b0b328af041506a2a299418c98747c4b1e68cb7527e74ced23efc" ], "markers": "python_version >= '3.7'", - "version": "==20.24.3", + "version": "==20.24.3" }, "wrapt": { "hashes": [ @@ -1649,10 +1633,10 @@ "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034", "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09", "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559", - "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639", + "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639" ], - "markers": "python_version < '3.11'", - "version": "==1.15.0", - }, - }, + "markers": "python_version >= '3.11'", + "version": "==1.15.0" + } + } } diff --git a/githubapp/core.py b/githubapp/core.py index 07fced3..c67794c 100644 --- a/githubapp/core.py +++ b/githubapp/core.py @@ -1,13 +1,17 @@ """ Flask extension for rapid GitHub app development """ -import os.path import hmac +import time import logging -import distutils +import requests +import jwt + +from flask import abort, current_app, jsonify, make_response, request, _app_ctx_stack +from distutils.util import strtobool +from os import environ +from ghapi.all import GhApi -from flask import abort, current_app, jsonify, request, _app_ctx_stack -from github3 import GitHub, GitHubEnterprise LOG = logging.getLogger(__name__) @@ -15,9 +19,49 @@ STATUS_NO_FUNC_CALLED = "MISS" -class GitHubApp(object): +class GitHubAppError(Exception): + pass + + +class GitHubAppValidationError(Exception): + pass + + +class GitHubAppBadCredentials(Exception): + pass + + +class GithubUnauthorized(Exception): + pass + + +class GithubAppUnkownObject(Exception): + pass + + +class InstallationAuthorization: """ - The GitHubApp object provides the central interface for interacting GitHub hooks + This class represents InstallationAuthorizations + """ + + def __init__(self, token, expires_at): + self.token = token + self.expires_at = expires_at + + def token(self): + return self._token + + def expires_at(self): + return self._expires_at + + def expired(self): + if self.expires_at: + return time.time() > self.expires_at + return False + + +class GitHubApp(object): + """The GitHubApp object provides the central interface for interacting GitHub hooks and creating GitHub app clients. GitHubApp object allows using the "on" decorator to make GitHub hooks to functions @@ -29,24 +73,24 @@ class GitHubApp(object): def __init__(self, app=None): self._hook_mappings = {} + self._access_token = None if app is not None: self.init_app(app) @staticmethod def load_env(app): - app.config["GITHUBAPP_ID"] = int(os.environ["APP_ID"]) - app.config["GITHUBAPP_SECRET"] = os.environ["WEBHOOK_SECRET"] - if "GHE_HOST" in os.environ: - app.config["GITHUBAPP_URL"] = "https://{}".format(os.environ["GHE_HOST"]) + app.config["GITHUBAPP_ID"] = int(environ["APP_ID"]) + app.config["GITHUBAPP_SECRET"] = environ["WEBHOOK_SECRET"] + if "GHE_HOST" in environ: + app.config["GITHUBAPP_URL"] = "https://{}".format(environ["GHE_HOST"]) app.config["VERIFY_SSL"] = bool( - distutils.util.strtobool(os.environ.get("VERIFY_SSL", "false")) + strtobool(environ.get("VERIFY_SSL", "false")) ) - with open(os.environ["PRIVATE_KEY_PATH"], "rb") as key_file: + with open(environ["PRIVATE_KEY_PATH"], "rb") as key_file: app.config["GITHUBAPP_KEY"] = key_file.read() def init_app(self, app): - """ - Initializes GitHubApp app by setting configuration variables. + """Initializes GitHubApp app by setting configuration variables. The GitHubApp instance is given the following configuration variables by calling on Flask's configuration: @@ -62,7 +106,8 @@ def init_app(self, app): `GITHUBAPP_SECRET`: - Secret used to secure webhooks as bytes or utf-8 encoded string (required). + Secret used to secure webhooks as bytes or utf-8 encoded string (required). set to `False` to disable + verification (not recommended for production). Default: None `GITHUBAPP_URL`: @@ -78,11 +123,17 @@ def init_app(self, app): self.load_env(app) required_settings = ["GITHUBAPP_ID", "GITHUBAPP_KEY", "GITHUBAPP_SECRET"] for setting in required_settings: - if not app.config.get(setting): + if not setting in app.config: raise RuntimeError( - "Flask-GitHubApp requires the '%s' config var to be set" % setting + "Flask-GitHubApplication requires the '%s' config var to be set" + % setting ) + if app.config.get("GITHUBAPP_URL"): + self.base_url = app.config.get("GITHUBAPP_URL") + else: + self.base_url = "https://api.github.com" + app.add_url_rule( app.config.get("GITHUBAPP_ROUTE", "/"), view_func=self._flask_view_func, @@ -111,16 +162,6 @@ def secret(self): def _api_url(self): return current_app.config["GITHUBAPP_URL"] - @property - def client(self): - """Unauthenticated GitHub client""" - if current_app.config.get("GITHUBAPP_URL"): - return GitHubEnterprise( - current_app.config["GITHUBAPP_URL"], - verify=current_app.config["VERIFY_SSL"], - ) - return GitHub() - @property def payload(self): """GitHub hook payload""" @@ -132,58 +173,97 @@ def payload(self): ) @property - def installation_client(self): + def installation_token(self): + return self._access_token + + def client(self, installation_id=None): """GitHub client authenticated as GitHub app installation""" ctx = _app_ctx_stack.top if ctx is not None: if not hasattr(ctx, "githubapp_installation"): - client = self.client - client.login_as_app_installation( - self.key, self.id, self.payload["installation"]["id"] - ) - ctx.githubapp_installation = client + if installation_id is None: + installation_id = self.payload["installation"]["id"] + self._access_token = self.get_access_token(installation_id).token + ctx.githubapp_installation = GhApi(token=self._access_token) return ctx.githubapp_installation - @property - def app_client(self): - """GitHub client authenticated as GitHub app""" - ctx = _app_ctx_stack.top - if ctx is not None: - if not hasattr(ctx, "githubapp_app"): - client = self.client - client.login_as_app(self.key, self.id) - ctx.githubapp_app = client - return ctx.githubapp_app - - @property - def installation_token(self): + def _create_jwt(self, expiration=60): """ + Creates a signed JWT, valid for 60 seconds by default. + The expiration can be extended beyond this, to a maximum of 600 seconds. + :param expiration: int + :return string: + """ + now = int(time.time()) + payload = {"iat": now, "exp": now + expiration, "iss": self.id} + encrypted = jwt.encode(payload, key=self.key, algorithm="RS256") + + if isinstance(encrypted, bytes): + encrypted = encrypted.decode("utf-8") + return encrypted - :return: + def get_access_token(self, installation_id, user_id=None): """ - return self.installation_client.session.auth.token + Get an access token for the given installation id. + POSTs https://api.github.com/app/installations//access_tokens + :param user_id: int + :param installation_id: int + :return: :class:`github.InstallationAuthorization.InstallationAuthorization` + """ + body = {} + if user_id: + body = {"user_id": user_id} + response = requests.post( + f"{self.base_url}/app/installations/{installation_id}/access_tokens", + headers={ + "Authorization": f"Bearer {self._create_jwt()}", + "Accept": "application/vnd.github.v3+json", + "User-Agent": "Flask-GithubApplication/Python", + }, + json=body, + ) + if response.status_code == 201: + return InstallationAuthorization( + token=response.json()["token"], expires_at=response.json()["expires_at"] + ) + elif response.status_code == 403: + raise GitHubAppBadCredentials( + status=response.status_code, data=response.text + ) + elif response.status_code == 404: + raise GithubAppUnkownObject(status=response.status_code, data=response.text) + raise Exception(status=response.status_code, data=response.text) - def app_installation(self, installation_id=None): + def list_installations(self, per_page=30, page=1): """ - Login as installation when triggered on a non-webhook event. - This is necessary for scheduling tasks - :param installation_id: - :return: + GETs https://api.github.com/app/installations + :return: :obj: `list` of installations """ - """GitHub client authenticated as GitHub app installation""" - ctx = _app_ctx_stack.top - if installation_id is None: - raise RuntimeError("Installation ID is not specified.") - if ctx is not None: - if not hasattr(ctx, "githubapp_installation"): - client = self.client - client.login_as_app_installation(self.key, self.id, installation_id) - ctx.githubapp_installation = client - return ctx.githubapp_installation + params = {"page": page, "per_page": per_page} + + response = requests.get( + f"{self.base_url}/app/installations", + headers={ + "Authorization": f"Bearer {self._create_jwt()}", + "Accept": "application/vnd.github.v3+json", + "User-Agent": "Flask-GithubApplication/python", + }, + params=params, + ) + if response.status_code == 200: + return response.json() + elif response.status_code == 401: + raise GithubUnauthorized(status=response.status_code, data=response.text) + elif response.status_code == 403: + raise GitHubAppBadCredentials( + status=response.status_code, data=response.text + ) + elif response.status_code == 404: + raise GithubAppUnkownObject(status=response.status_code, data=response.text) + raise Exception(status=response.status_code, data=response.text) def on(self, event_action): - """ - Decorator routes a GitHub hook to the wrapped function. + """Decorator routes a GitHub hook to the wrapped function. Functions decorated as a hook recipient are registered as the function for the given GitHub event. @@ -192,7 +272,7 @@ def cruel_closer(): owner = github_app.payload['repository']['owner']['login'] repo = github_app.payload['repository']['name'] num = github_app.payload['issue']['id'] - issue = github_app.installation_client.issue(owner, repo, num) + issue = github_app.client.issue(owner, repo, num) issue.create_comment('Could not replicate.') issue.close() @@ -213,14 +293,41 @@ def decorator(f): return decorator + def _validate_request(self): + if not request.is_json: + raise GitHubAppValidationError( + "Invalid HTTP Content-Type header for JSON body " + "(must be application/json or application/*+json)." + ) + try: + request.json + except BadRequest: + raise GitHubAppValidationError("Invalid HTTP body (must be JSON).") + + event = request.headers.get("X-GitHub-Event") + + if event is None: + raise GitHubAppValidationError("Missing X-GitHub-Event HTTP header.") + + action = request.json.get("action") + + return event, action + def _flask_view_func(self): functions_to_call = [] calls = {} - event = request.headers["X-GitHub-Event"] - action = request.json.get("action") + try: + event, action = self._validate_request() + except GitHubAppValidationError as e: + LOG.error(e) + error_response = make_response( + jsonify(status="ERROR", description=str(e)), 400 + ) + return abort(error_response) - self._verify_webhook() + if current_app.config["GITHUBAPP_SECRET"] is not False: + self._verify_webhook() if event in self._hook_mappings: functions_to_call += self._hook_mappings[event] @@ -239,15 +346,24 @@ def _flask_view_func(self): return jsonify({"status": status, "calls": calls}) def _verify_webhook(self): - hub_signature = "X-HUB-SIGNATURE" - if hub_signature not in request.headers: - LOG.warning("Github Hook Signature not found.") - abort(400) - - signature = request.headers[hub_signature].split("=")[1] + signature_header = "X-Hub-Signature-256" + signature_header_legacy = "X-Hub-Signature" + + if request.headers.get(signature_header): + signature = request.headers[signature_header].split("=")[1] + digestmod = "sha256" + elif request.headers.get(signature_header_legacy): + signature = request.headers[signature_header_legacy].split("=")[1] + digestmod = "sha1" + else: + LOG.warning( + "Signature header missing. Configure your GitHub App with a secret or set GITHUBAPP_SECRET" + "to False to disable verification." + ) + return abort(400) - mac = hmac.new(self.secret, msg=request.data, digestmod="sha1") + mac = hmac.new(self.secret, msg=request.data, digestmod=digestmod) if not hmac.compare_digest(mac.hexdigest(), signature): LOG.warning("GitHub hook signature verification failed.") - abort(400) + return abort(400)