From 7670f4b9cbb30394b7a987874afefddd6ba6a0fa Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 1 Mar 2022 10:13:03 +0000 Subject: [PATCH 1/4] Adds 3.10.2 compatible modules to lock file --- .gitignore | 2 + poetry.lock | 118 ++++++++++++++++++++++++++-------------------------- 2 files changed, 61 insertions(+), 59 deletions(-) diff --git a/.gitignore b/.gitignore index 5619d4b..912161e 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ __pycache__ dist output pysnyk.egg-info + +.venv \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index ceadd78..9831801 100644 --- a/poetry.lock +++ b/poetry.lock @@ -69,7 +69,7 @@ unicode_backport = ["unicodedata2"] [[package]] name = "click" -version = "8.0.3" +version = "8.0.4" description = "Composable command line interface toolkit" category = "dev" optional = false @@ -89,7 +89,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "coverage" -version = "6.3.1" +version = "6.3.2" description = "Code coverage measurement for Python" category = "dev" optional = false @@ -122,7 +122,7 @@ packaging = "*" [[package]] name = "filelock" -version = "3.5.1" +version = "3.6.0" description = "A platform independent file lock." category = "dev" optional = false @@ -142,7 +142,7 @@ python-versions = ">=3.5" [[package]] name = "importlib-metadata" -version = "4.11.1" +version = "4.11.2" description = "Read metadata from Python packages" category = "main" optional = false @@ -153,7 +153,7 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] perf = ["ipython"] testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] @@ -446,7 +446,7 @@ python-versions = ">=3.6" [[package]] name = "types-requests" -version = "2.27.10" +version = "2.27.11" description = "Typing stubs for requests" category = "dev" optional = false @@ -457,7 +457,7 @@ types-urllib3 = "<1.27" [[package]] name = "types-urllib3" -version = "1.26.9" +version = "1.26.10" description = "Typing stubs for urllib3" category = "dev" optional = false @@ -507,7 +507,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "af5fbbbd55df71d2f5ffb2a4ecef47a234275734e1a2e48099493ac4804c818f" +content-hash = "a5d96e58f8b910a5f8be8365cc4a6d584c02f0c327a08039162771a5fc05b6fa" [metadata.files] appdirs = [ @@ -535,55 +535,55 @@ charset-normalizer = [ {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, ] click = [ - {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, - {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, + {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"}, + {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] coverage = [ - {file = "coverage-6.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeffd96882d8c06d31b65dddcf51db7c612547babc1c4c5db6a011abe9798525"}, - {file = "coverage-6.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:621f6ea7260ea2ffdaec64fe5cb521669984f567b66f62f81445221d4754df4c"}, - {file = "coverage-6.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84f2436d6742c01136dd940ee158bfc7cf5ced3da7e4c949662b8703b5cd8145"}, - {file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de73fca6fb403dd72d4da517cfc49fcf791f74eee697d3219f6be29adf5af6ce"}, - {file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78fbb2be068a13a5d99dce9e1e7d168db880870f7bc73f876152130575bd6167"}, - {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f5a4551dfd09c3bd12fca8144d47fe7745275adf3229b7223c2f9e29a975ebda"}, - {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7bff3a98f63b47464480de1b5bdd80c8fade0ba2832c9381253c9b74c4153c27"}, - {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a06c358f4aed05fa1099c39decc8022261bb07dfadc127c08cfbd1391b09689e"}, - {file = "coverage-6.3.1-cp310-cp310-win32.whl", hash = "sha256:9fff3ff052922cb99f9e52f63f985d4f7a54f6b94287463bc66b7cdf3eb41217"}, - {file = "coverage-6.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:276b13cc085474e482566c477c25ed66a097b44c6e77132f3304ac0b039f83eb"}, - {file = "coverage-6.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:56c4a409381ddd7bbff134e9756077860d4e8a583d310a6f38a2315b9ce301d0"}, - {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eb494070aa060ceba6e4bbf44c1bc5fa97bfb883a0d9b0c9049415f9e944793"}, - {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e15d424b8153756b7c903bde6d4610be0c3daca3986173c18dd5c1a1625e4cd"}, - {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d47a897c1e91f33f177c21de897267b38fbb45f2cd8e22a710bcef1df09ac1"}, - {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:25e73d4c81efa8ea3785274a2f7f3bfbbeccb6fcba2a0bdd3be9223371c37554"}, - {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fac0bcc5b7e8169bffa87f0dcc24435446d329cbc2b5486d155c2e0f3b493ae1"}, - {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:72128176fea72012063200b7b395ed8a57849282b207321124d7ff14e26988e8"}, - {file = "coverage-6.3.1-cp37-cp37m-win32.whl", hash = "sha256:1bc6d709939ff262fd1432f03f080c5042dc6508b6e0d3d20e61dd045456a1a0"}, - {file = "coverage-6.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:618eeba986cea7f621d8607ee378ecc8c2504b98b3fdc4952b30fe3578304687"}, - {file = "coverage-6.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ed164af5c9078596cfc40b078c3b337911190d3faeac830c3f1274f26b8320"}, - {file = "coverage-6.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:352c68e233409c31048a3725c446a9e48bbff36e39db92774d4f2380d630d8f8"}, - {file = "coverage-6.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:448d7bde7ceb6c69e08474c2ddbc5b4cd13c9e4aa4a717467f716b5fc938a734"}, - {file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9fde6b90889522c220dd56a670102ceef24955d994ff7af2cb786b4ba8fe11e4"}, - {file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e647a0be741edbb529a72644e999acb09f2ad60465f80757da183528941ff975"}, - {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a5cdc3adb4f8bb8d8f5e64c2e9e282bc12980ef055ec6da59db562ee9bdfefa"}, - {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2dd70a167843b4b4b2630c0c56f1b586fe965b4f8ac5da05b6690344fd065c6b"}, - {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9ad0a117b8dc2061ce9461ea4c1b4799e55edceb236522c5b8f958ce9ed8fa9a"}, - {file = "coverage-6.3.1-cp38-cp38-win32.whl", hash = "sha256:e92c7a5f7d62edff50f60a045dc9542bf939758c95b2fcd686175dd10ce0ed10"}, - {file = "coverage-6.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:482fb42eea6164894ff82abbcf33d526362de5d1a7ed25af7ecbdddd28fc124f"}, - {file = "coverage-6.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c5b81fb37db76ebea79aa963b76d96ff854e7662921ce742293463635a87a78d"}, - {file = "coverage-6.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a4f923b9ab265136e57cc14794a15b9dcea07a9c578609cd5dbbfff28a0d15e6"}, - {file = "coverage-6.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56d296cbc8254a7dffdd7bcc2eb70be5a233aae7c01856d2d936f5ac4e8ac1f1"}, - {file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245ab82e8554fa88c4b2ab1e098ae051faac5af829efdcf2ce6b34dccd5567c"}, - {file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f2b05757c92ad96b33dbf8e8ec8d4ccb9af6ae3c9e9bd141c7cc44d20c6bcba"}, - {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9e3dd806f34de38d4c01416344e98eab2437ac450b3ae39c62a0ede2f8b5e4ed"}, - {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d651fde74a4d3122e5562705824507e2f5b2d3d57557f1916c4b27635f8fbe3f"}, - {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:704f89b87c4f4737da2860695a18c852b78ec7279b24eedacab10b29067d3a38"}, - {file = "coverage-6.3.1-cp39-cp39-win32.whl", hash = "sha256:2aed4761809640f02e44e16b8b32c1a5dee5e80ea30a0ff0912158bde9c501f2"}, - {file = "coverage-6.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:9976fb0a5709988778ac9bc44f3d50fccd989987876dfd7716dee28beed0a9fa"}, - {file = "coverage-6.3.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:463e52616ea687fd323888e86bf25e864a3cc6335a043fad6bbb037dbf49bbe2"}, - {file = "coverage-6.3.1.tar.gz", hash = "sha256:6c3f6158b02ac403868eea390930ae64e9a9a2a5bbfafefbb920d29258d9f2f8"}, + {file = "coverage-6.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b27d894748475fa858f9597c0ee1d4829f44683f3813633aaf94b19cb5453cf"}, + {file = "coverage-6.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37d1141ad6b2466a7b53a22e08fe76994c2d35a5b6b469590424a9953155afac"}, + {file = "coverage-6.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9987b0354b06d4df0f4d3e0ec1ae76d7ce7cbca9a2f98c25041eb79eec766f1"}, + {file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:26e2deacd414fc2f97dd9f7676ee3eaecd299ca751412d89f40bc01557a6b1b4"}, + {file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dd8bafa458b5c7d061540f1ee9f18025a68e2d8471b3e858a9dad47c8d41903"}, + {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:46191097ebc381fbf89bdce207a6c107ac4ec0890d8d20f3360345ff5976155c"}, + {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6f89d05e028d274ce4fa1a86887b071ae1755082ef94a6740238cd7a8178804f"}, + {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:58303469e9a272b4abdb9e302a780072c0633cdcc0165db7eec0f9e32f901e05"}, + {file = "coverage-6.3.2-cp310-cp310-win32.whl", hash = "sha256:2fea046bfb455510e05be95e879f0e768d45c10c11509e20e06d8fcaa31d9e39"}, + {file = "coverage-6.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:a2a8b8bcc399edb4347a5ca8b9b87e7524c0967b335fbb08a83c8421489ddee1"}, + {file = "coverage-6.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f1555ea6d6da108e1999b2463ea1003fe03f29213e459145e70edbaf3e004aaa"}, + {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5f4e1edcf57ce94e5475fe09e5afa3e3145081318e5fd1a43a6b4539a97e518"}, + {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a15dc0a14008f1da3d1ebd44bdda3e357dbabdf5a0b5034d38fcde0b5c234b7"}, + {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21b7745788866028adeb1e0eca3bf1101109e2dc58456cb49d2d9b99a8c516e6"}, + {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8ce257cac556cb03be4a248d92ed36904a59a4a5ff55a994e92214cde15c5bad"}, + {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b0be84e5a6209858a1d3e8d1806c46214e867ce1b0fd32e4ea03f4bd8b2e3359"}, + {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:acf53bc2cf7282ab9b8ba346746afe703474004d9e566ad164c91a7a59f188a4"}, + {file = "coverage-6.3.2-cp37-cp37m-win32.whl", hash = "sha256:8bdde1177f2311ee552f47ae6e5aa7750c0e3291ca6b75f71f7ffe1f1dab3dca"}, + {file = "coverage-6.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b31651d018b23ec463e95cf10070d0b2c548aa950a03d0b559eaa11c7e5a6fa3"}, + {file = "coverage-6.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07e6db90cd9686c767dcc593dff16c8c09f9814f5e9c51034066cad3373b914d"}, + {file = "coverage-6.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c6dbb42f3ad25760010c45191e9757e7dce981cbfb90e42feef301d71540059"}, + {file = "coverage-6.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c76aeef1b95aff3905fb2ae2d96e319caca5b76fa41d3470b19d4e4a3a313512"}, + {file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cf5cfcb1521dc3255d845d9dca3ff204b3229401994ef8d1984b32746bb45ca"}, + {file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fbbdc8d55990eac1b0919ca69eb5a988a802b854488c34b8f37f3e2025fa90d"}, + {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ec6bc7fe73a938933d4178c9b23c4e0568e43e220aef9472c4f6044bfc6dd0f0"}, + {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9baff2a45ae1f17c8078452e9e5962e518eab705e50a0aa8083733ea7d45f3a6"}, + {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd9e830e9d8d89b20ab1e5af09b32d33e1a08ef4c4e14411e559556fd788e6b2"}, + {file = "coverage-6.3.2-cp38-cp38-win32.whl", hash = "sha256:f7331dbf301b7289013175087636bbaf5b2405e57259dd2c42fdcc9fcc47325e"}, + {file = "coverage-6.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:68353fe7cdf91f109fc7d474461b46e7f1f14e533e911a2a2cbb8b0fc8613cf1"}, + {file = "coverage-6.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b78e5afb39941572209f71866aa0b206c12f0109835aa0d601e41552f9b3e620"}, + {file = "coverage-6.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4e21876082ed887baed0146fe222f861b5815455ada3b33b890f4105d806128d"}, + {file = "coverage-6.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34626a7eee2a3da12af0507780bb51eb52dca0e1751fd1471d0810539cefb536"}, + {file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ebf730d2381158ecf3dfd4453fbca0613e16eaa547b4170e2450c9707665ce7"}, + {file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd6fe30bd519694b356cbfcaca9bd5c1737cddd20778c6a581ae20dc8c04def2"}, + {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:96f8a1cb43ca1422f36492bebe63312d396491a9165ed3b9231e778d43a7fca4"}, + {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:dd035edafefee4d573140a76fdc785dc38829fe5a455c4bb12bac8c20cfc3d69"}, + {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ca5aeb4344b30d0bec47481536b8ba1181d50dbe783b0e4ad03c95dc1296684"}, + {file = "coverage-6.3.2-cp39-cp39-win32.whl", hash = "sha256:f5fa5803f47e095d7ad8443d28b01d48c0359484fec1b9d8606d0e3282084bc4"}, + {file = "coverage-6.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:9548f10d8be799551eb3a9c74bbf2b4934ddb330e08a73320123c07f95cc2d92"}, + {file = "coverage-6.3.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:18d520c6860515a771708937d2f78f63cc47ab3b80cb78e86573b0a760161faf"}, + {file = "coverage-6.3.2.tar.gz", hash = "sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9"}, ] decorator = [ {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, @@ -594,16 +594,16 @@ deprecation = [ {file = "deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff"}, ] filelock = [ - {file = "filelock-3.5.1-py3-none-any.whl", hash = "sha256:7b23620a293cf3e19924e469cb96672dc72b36c26e8f80f85668310117fcbe4e"}, - {file = "filelock-3.5.1.tar.gz", hash = "sha256:d1eccb164ed020bc84edd9e45bf6cdb177f64749f6b8fe066648832d2e98726d"}, + {file = "filelock-3.6.0-py3-none-any.whl", hash = "sha256:f8314284bfffbdcfa0ff3d7992b023d4c628ced6feb957351d4c48d059f56bc0"}, + {file = "filelock-3.6.0.tar.gz", hash = "sha256:9cd540a9352e432c7246a48fe4e8712b10acb1df2ad1f30e8c070b82ae1fed85"}, ] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.11.1-py3-none-any.whl", hash = "sha256:e0bc84ff355328a4adfc5240c4f211e0ab386f80aa640d1b11f0618a1d282094"}, - {file = "importlib_metadata-4.11.1.tar.gz", hash = "sha256:175f4ee440a0317f6e8d81b7f8d4869f93316170a65ad2b007d2929186c8052c"}, + {file = "importlib_metadata-4.11.2-py3-none-any.whl", hash = "sha256:d16e8c1deb60de41b8e8ed21c1a7b947b0bc62fab7e1d470bcdf331cea2e6735"}, + {file = "importlib_metadata-4.11.2.tar.gz", hash = "sha256:b36ffa925fe3139b2f6ff11d6925ffd4fa7bc47870165e3ac260ac7b4f91e6ac"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, @@ -809,12 +809,12 @@ typed-ast = [ {file = "typed_ast-1.5.2.tar.gz", hash = "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27"}, ] types-requests = [ - {file = "types-requests-2.27.10.tar.gz", hash = "sha256:5dcb088fcaa778efeee6b7fc46967037e983fbfb9fec02594578bd33fd75e555"}, - {file = "types_requests-2.27.10-py3-none-any.whl", hash = "sha256:6cb4fb0bbcbc585c57eeee6ffe5a47638dc89706b8d290ec89a77213fc5bad1a"}, + {file = "types-requests-2.27.11.tar.gz", hash = "sha256:6a7ed24b21780af4a5b5e24c310b2cd885fb612df5fd95584d03d87e5f2a195a"}, + {file = "types_requests-2.27.11-py3-none-any.whl", hash = "sha256:506279bad570c7b4b19ac1f22e50146538befbe0c133b2cea66a9b04a533a859"}, ] types-urllib3 = [ - {file = "types-urllib3-1.26.9.tar.gz", hash = "sha256:abd2d4857837482b1834b4817f0587678dcc531dbc9abe4cde4da28cef3f522c"}, - {file = "types_urllib3-1.26.9-py3-none-any.whl", hash = "sha256:4a54f6274ab1c80968115634a55fb9341a699492b95e32104a7c513db9fe02e9"}, + {file = "types-urllib3-1.26.10.tar.gz", hash = "sha256:a26898f530e6c3f43f25b907f2b884486868ffd56a9faa94cbf9b3eb6e165d6a"}, + {file = "types_urllib3-1.26.10-py3-none-any.whl", hash = "sha256:d755278d5ecd7a7a6479a190e54230f241f1a99c19b81518b756b19dc69e518c"}, ] typing-extensions = [ {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, From 46c3758d16cec321cff4400ac02b418c88e5a58b Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 2 Mar 2022 12:17:12 +0000 Subject: [PATCH 2/4] Adds support for lowlevel GET calls against V3 API Before, one couldn't use the SnykClient to interface with the v3 endpoint. While long term V3 will have a new client, in the interim V3 doesn't have feature parity with V1, so for practical implementations a hybrid approach is required. Because this update required substantially more test data to be used (multiple pages to test get_v3_pages), a new function to enable loading the requests response data directly from json stored in a test_data folder was added. This also makes Black and other code formatters happy. Some housekeeping was required to update testing packages to latest versions to ensure they performed consistently. --- .gitignore | 1 + README.md | 127 ++++++++++----- poetry.lock | 231 +++++++++++++-------------- pyproject.toml | 30 ++-- snyk/client.py | 133 +++++++++++++-- snyk/test_client.py | 113 ++++++++----- snyk/test_data/organizations.json | 21 +++ snyk/test_data/projects.json | 23 +++ snyk/test_data/v3_groups.json | 22 +++ snyk/test_data/v3_targets_page1.json | 120 ++++++++++++++ snyk/test_data/v3_targets_page2.json | 121 ++++++++++++++ snyk/test_data/v3_targets_page3.json | 120 ++++++++++++++ snyk/test_models.py | 9 +- snyk/utils.py | 33 +++- 14 files changed, 874 insertions(+), 230 deletions(-) create mode 100644 snyk/test_data/organizations.json create mode 100644 snyk/test_data/projects.json create mode 100644 snyk/test_data/v3_groups.json create mode 100644 snyk/test_data/v3_targets_page1.json create mode 100644 snyk/test_data/v3_targets_page2.json create mode 100644 snyk/test_data/v3_targets_page3.json diff --git a/.gitignore b/.gitignore index 912161e..1c180bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .coverage .idea .mypy_cache +.pytest_cache __pycache__ *.pyc diff --git a/README.md b/README.md index e872d4a..24ab3f4 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ client = snyk.SnykClient("", tries=4, delay=1, backoff=2) - `tries` - the maximum number of attempts. **Default:** `1` (no retries) - `delay` - initial delay between attempts. **Default:** `1` - `backoff` - multiplier applied to delay between attempts. **Default:** `2` +- `debug` - run client in debug mode, useful for debugging API requests. **Default:** `False` ## Organizations @@ -60,21 +61,21 @@ Most of the API is scoped to organizations, so most other methods are found on t The `snyk.models.Organization` object has the following properties related to the API: -* `entitlements` - returns the set of Snyk features available to this account -* `dependencies`- returns a Manager for packages in use in this organization -* `licenses` - returns a Manager for licenses currently in use by projects in this organisation -* `members` - returns a Manager for members -* `projects` - returns a Manager for associated projects -* `integrations` - returns a Manager for active integrations +- `entitlements` - returns the set of Snyk features available to this account +- `dependencies`- returns a Manager for packages in use in this organization +- `licenses` - returns a Manager for licenses currently in use by projects in this organisation +- `members` - returns a Manager for members +- `projects` - returns a Manager for associated projects +- `integrations` - returns a Manager for active integrations ### A note on Managers Managers provide a consistent API for accessing objects from the Snyk API. Each manager implements the following methods: -* `all()` - return a list of all of the relevant objects -* `get("")` - return a single instance of the object if it exists -* `first()` - grab the first instance of the object if one exists -* `filter(="")` - return a list filtered by one or more key/value pairs +- `all()` - return a list of all of the relevant objects +- `get("")` - return a single instance of the object if it exists +- `first()` - grab the first instance of the object if one exists +- `filter(="")` - return a list filtered by one or more key/value pairs ### Projects @@ -94,20 +95,20 @@ client.projects.all() The `snyk.models.Project` object has the following useful properties and methods: -* `delete()` - deletes the project in question. Be careful as this will delete all associated data too -* `dependencies` - returns a Manager for packages in use in this project -* `dependency_graph` - returns a `snyk.models.DependencyGraph` object which represents the full dependency graph of package dependencies -* `ignores` - returns a Manager for ignore rules set on the project -* `vulnerabilities` - returns a list of `snyk.models.Vulnerability` objects with information about vulnerabilities in this project -* `jira_issues` - returns a Manager with access to any associated Jira issues -* `licenses` - returns a Manager for licenses currently in use by this project -* `settings` - returns a Manager for interacting with the current project settings -* `tags` - returns a Manager for interacting with the current project tags +- `delete()` - deletes the project in question. Be careful as this will delete all associated data too +- `dependencies` - returns a Manager for packages in use in this project +- `dependency_graph` - returns a `snyk.models.DependencyGraph` object which represents the full dependency graph of package dependencies +- `ignores` - returns a Manager for ignore rules set on the project +- `vulnerabilities` - returns a list of `snyk.models.Vulnerability` objects with information about vulnerabilities in this project +- `jira_issues` - returns a Manager with access to any associated Jira issues +- `licenses` - returns a Manager for licenses currently in use by this project +- `settings` - returns a Manager for interacting with the current project settings +- `tags` - returns a Manager for interacting with the current project tags You can add and delete tags using the manager: -* `tags.add(key, value)` - adds a tag with the provided key/value pair to the project -* `tags.delete(key, value)` - deletes a tag with the provided key/value pair from the project +- `tags.add(key, value)` - adds a tag with the provided key/value pair to the project +- `tags.delete(key, value)` - deletes a tag with the provided key/value pair from the project In the case of Projects, as well as filtering by properties (as mentioned above) you can also filter by tag: @@ -139,16 +140,14 @@ org.import_project("github.com/user/project@branch", files=["Gemfile.lock"]) This method currently only supports importing projects from GitHub and Docker Hub. For other integrations you will need to grab the lower-level `snyk.models.Integration` object from the `snyk.models.Organization.integrations` manager noted above. Other services will be added to this API soon. - ### Testing for vulnerabilities The API also exposes methods to discover vulnerability information about individual packages. These methods are found on the Organization object. -* `test_maven(, , )` - returns an IssueSet containing vulnerability information for a Maven artifact -* `test_rubygem(, )` - returns an IssueSet containing vulnerability information for a Ruby Gem -* `test_python(, )` - returns an IssueSet containing vulnerability information for Python package from PyPi -* `test_npm(, )` - returns an IssueSet containing vulnerability information for an NPM package - +- `test_maven(, , )` - returns an IssueSet containing vulnerability information for a Maven artifact +- `test_rubygem(, )` - returns an IssueSet containing vulnerability information for a Ruby Gem +- `test_python(, )` - returns an IssueSet containing vulnerability information for Python package from PyPi +- `test_npm(, )` - returns an IssueSet containing vulnerability information for an NPM package Here's an example of checking a particular Python package. @@ -166,14 +165,14 @@ False As well as testing individual packages you can also test all packages found in various dependency management manifests. The client currently supports the following methods: -* `test_pipfile()` - returns an IssueSet for all Python dependencies in a `Pipfile` -* `test_gemfilelock()` - returns an IssueSet for all Ruby dependencies in a `Gemfile` -* `test_packagejson(, ())` - returns an IssueSet for all Javascript dependencies in a `package.json` file. Optionally takes a `package.lock` file -* `test_gradlefile()` - returns an IssueSet for all dependencies in a `Gradlefile` -* `test_sbt()` - returns an IssueSet for all dependencies defined in a `.sbt` file -* `test_pom()` - returns an IssueSet for all dependencies in a Maven `pom.xml` file -* `test_yarn(, )` - returns an IssueSet for all dependencies in Yarn `package.json` and `yarn.lock` files -* `test_composer(, )` - returns an IssueSet for all dependencies in Composer `composer.json` and `composer.lock` files +- `test_pipfile()` - returns an IssueSet for all Python dependencies in a `Pipfile` +- `test_gemfilelock()` - returns an IssueSet for all Ruby dependencies in a `Gemfile` +- `test_packagejson(, ())` - returns an IssueSet for all Javascript dependencies in a `package.json` file. Optionally takes a `package.lock` file +- `test_gradlefile()` - returns an IssueSet for all dependencies in a `Gradlefile` +- `test_sbt()` - returns an IssueSet for all dependencies defined in a `.sbt` file +- `test_pom()` - returns an IssueSet for all dependencies in a Maven `pom.xml` file +- `test_yarn(, )` - returns an IssueSet for all dependencies in Yarn `package.json` and `yarn.lock` files +- `test_composer(, )` - returns an IssueSet for all dependencies in Composer `composer.json` and `composer.lock` files For example, here we are testing a Python `Pipfile`. @@ -199,7 +198,6 @@ You can also invite new users as administrators: >>> org.invite("example@example.com", admin=True) ``` - ### Low-level client As well as the high-level API of the Snyk client you can use the HTTP methods directly. For these you simply need to pass the path, and optionally a data payload. The full domain, and the authentication details, are already provided by the client. @@ -212,3 +210,60 @@ client.post("", ) ``` Most of the time you shouldn't need to use these. They are mainly useful if new methods are added to the API which are not yet supported in the client. This can also be useful if you want to pass very specific parameters, or to parse the raw JSON output from the API. + +## Experimental V3 Low Level Client + +pysnyk >= 0.9.0 now includes support for basic V3 compatibility. To switch to use a V3 client, pass the V3 API url and version when initializing a client. Right now it supports the `GET` method. Refer to the [V3 API docs](https://apidocs.snyk.io/) for more information and examples. + +Getting the V3 information of an organization: + +```python + +snyk_org = "39ddc762-b1b9-41ce-ab42-defbe4575bd6" + +v3client = SnykClient(snyk_token,version="2022-02-16~experimental",url="https://api.snyk.io/v3") + +print(v3client.get(f"/orgs/{snyk_org}").json()) + +# this supports overriding v3 versions for a specific GET requests: +user = v3client.get(f"orgs/{snyk_org}/users/{snyk_user}", version="2022-02-01~experimental").json() + +# pass parameters such as how many results per page + +params = {"limit": 10} + +targets = v3client.get(f"orgs/{snyk_org}/targets", params=params) +``` + +V1 and V3 can work at the same time by instantiating two clients: + +```python +snyk_org = "39ddc762-b1b9-41ce-ab42-defbe4575bd6" + +v1client = SnykClient(snyk_token) + +v3client = SnykClient(snyk_token,version="2022-02-16~experimental",url="https://api.snyk.io/v3") + +v1_org = v1client.organizations.get(snyk_org) + +v3_org = v3client.get(f"/orgs/{snyk_org}").json() +``` + +The V3 API introduces consistent pagination across all endpoints. The v3 client includes a helper method `.get_v3_pages` which collects the paginated responses and returns a single list combining the contents of the "data" key from all pages. It takes the same values as the get method. + +```python +v3client = SnykClient(snyk_token,version="2022-02-16~experimental",url="https://api.snyk.io/v3") + +params = {"limit": 10} + +targets = v3client.get(f"orgs/{snyk_org}/targets", params=params).json() + +print(len(targets["data"])) +# returns 10 targets + +all_targets = v3client.get_v3_pages(f"orgs/{snyk_org}/targets", params=params) + +print(len(all_targets)) +# returns 33 targets, note we don't have to add .json() to the call or access the "data" key, get_v3_pages does that for us + +``` diff --git a/poetry.lock b/poetry.lock index 9831801..db7937b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,11 +1,3 @@ -[[package]] -name = "appdirs" -version = "1.4.4" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "atomicwrites" version = "1.4.0" @@ -30,23 +22,26 @@ tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (> [[package]] name = "black" -version = "19.10b0" +version = "22.1.0" description = "The uncompromising code formatter." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.6.2" [package.dependencies] -appdirs = "*" -attrs = ">=18.1.0" -click = ">=6.5" -pathspec = ">=0.6,<1" -regex = "*" -toml = ">=0.9.4" -typed-ast = ">=1.4.0" +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = ">=1.1.0" +typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} +typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} [package.extras] -d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" @@ -239,6 +234,18 @@ category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +[[package]] +name = "platformdirs" +version = "2.5.1" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] +test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] + [[package]] name = "pluggy" version = "1.0.0" @@ -325,17 +332,16 @@ testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtuale [[package]] name = "pytest-isort" -version = "1.3.0" +version = "3.0.0" description = "py.test plugin to check import ordering using isort" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.6,<4" [package.dependencies] +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} isort = ">=4.0" - -[package.extras] -tests = ["mock"] +pytest = ">=5.0" [[package]] name = "pytest-mypy" @@ -358,14 +364,6 @@ pytest = [ {version = ">=4.6", markers = "python_version >= \"3.6\" and python_version < \"3.10\""}, ] -[[package]] -name = "regex" -version = "2022.1.18" -description = "Alternative regular expression module, to replace re." -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "requests" version = "2.27.1" @@ -444,6 +442,14 @@ category = "dev" optional = false python-versions = ">=3.6" +[[package]] +name = "types-backports" +version = "0.1.3" +description = "Typing stubs for backports" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "types-requests" version = "2.27.11" @@ -455,6 +461,30 @@ python-versions = "*" [package.dependencies] types-urllib3 = "<1.27" +[[package]] +name = "types-retry" +version = "0.9.5" +description = "Typing stubs for retry" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "types-setuptools" +version = "57.4.9" +description = "Typing stubs for setuptools" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "types-toml" +version = "0.10.4" +description = "Typing stubs for toml" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "types-urllib3" version = "1.26.10" @@ -486,11 +516,11 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "xlsxwriter" -version = "1.4.5" +version = "3.0.3" description = "A Python module for creating Excel XLSX files." category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.4" [[package]] name = "zipp" @@ -507,13 +537,9 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "a5d96e58f8b910a5f8be8365cc4a6d584c02f0c327a08039162771a5fc05b6fa" +content-hash = "e1ed0f4324c29fa3943fdc8b035d4c4aa9858f1d173b4fb2943ba8865d81be1c" [metadata.files] -appdirs = [ - {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, - {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, -] atomicwrites = [ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, @@ -523,8 +549,29 @@ attrs = [ {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, ] black = [ - {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"}, - {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, + {file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"}, + {file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"}, + {file = "black-22.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e21e1f1efa65a50e3960edd068b6ae6d64ad6235bd8bfea116a03b21836af71"}, + {file = "black-22.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f69158a7d120fd641d1fa9a921d898e20d52e44a74a6fbbcc570a62a6bc8ab"}, + {file = "black-22.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:228b5ae2c8e3d6227e4bde5920d2fc66cc3400fde7bcc74f480cb07ef0b570d5"}, + {file = "black-22.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b1a5ed73ab4c482208d20434f700d514f66ffe2840f63a6252ecc43a9bc77e8a"}, + {file = "black-22.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35944b7100af4a985abfcaa860b06af15590deb1f392f06c8683b4381e8eeaf0"}, + {file = "black-22.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7835fee5238fc0a0baf6c9268fb816b5f5cd9b8793423a75e8cd663c48d073ba"}, + {file = "black-22.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dae63f2dbf82882fa3b2a3c49c32bffe144970a573cd68d247af6560fc493ae1"}, + {file = "black-22.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa1db02410b1924b6749c245ab38d30621564e658297484952f3d8a39fce7e8"}, + {file = "black-22.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c8226f50b8c34a14608b848dc23a46e5d08397d009446353dad45e04af0c8e28"}, + {file = "black-22.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2d6f331c02f0f40aa51a22e479c8209d37fcd520c77721c034517d44eecf5912"}, + {file = "black-22.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:742ce9af3086e5bd07e58c8feb09dbb2b047b7f566eb5f5bc63fd455814979f3"}, + {file = "black-22.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fdb8754b453fb15fad3f72cd9cad3e16776f0964d67cf30ebcbf10327a3777a3"}, + {file = "black-22.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5660feab44c2e3cb24b2419b998846cbb01c23c7fe645fee45087efa3da2d61"}, + {file = "black-22.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6f2f01381f91c1efb1451998bd65a129b3ed6f64f79663a55fe0e9b74a5f81fd"}, + {file = "black-22.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:efbadd9b52c060a8fc3b9658744091cb33c31f830b3f074422ed27bad2b18e8f"}, + {file = "black-22.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8871fcb4b447206904932b54b567923e5be802b9b19b744fdff092bd2f3118d0"}, + {file = "black-22.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccad888050f5393f0d6029deea2a33e5ae371fd182a697313bdbd835d3edaf9c"}, + {file = "black-22.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2"}, + {file = "black-22.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:373922fc66676133ddc3e754e4509196a8c392fec3f5ca4486673e685a421321"}, + {file = "black-22.1.0-py3-none-any.whl", hash = "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d"}, + {file = "black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5"}, ] certifi = [ {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, @@ -651,6 +698,10 @@ pathspec = [ {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, ] +platformdirs = [ + {file = "platformdirs-2.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"}, + {file = "platformdirs-2.5.1.tar.gz", hash = "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d"}, +] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, @@ -675,89 +726,13 @@ pytest-cov = [ {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, ] pytest-isort = [ - {file = "pytest-isort-1.3.0.tar.gz", hash = "sha256:46a12331a701e2f21d48548b2828c8b0a7956dbf1cd5347163f537deb24332dd"}, - {file = "pytest_isort-1.3.0-py3-none-any.whl", hash = "sha256:074255ad393088a2daee6ca7f2305b7b86358ff632f62302896d8d4b2b339107"}, + {file = "pytest-isort-3.0.0.tar.gz", hash = "sha256:4fe4b26ead2af776730ec23f5870d7421f35aace22a41c4e938586ef4d8787cb"}, + {file = "pytest_isort-3.0.0-py3-none-any.whl", hash = "sha256:2d96a25a135d6fd084ac36878e7d54f26f27c6987c2c65f0d12809bffade9cb9"}, ] pytest-mypy = [ {file = "pytest-mypy-0.9.1.tar.gz", hash = "sha256:9ffa3bf405c12c5c6be9e92e22bebb6ab2c91b9c32f45b0f0c93af473269ab5c"}, {file = "pytest_mypy-0.9.1-py3-none-any.whl", hash = "sha256:a2505fcf61f1c0c51f950d4623ea8ca2daf6fb2101a5603554bad2e130202083"}, ] -regex = [ - {file = "regex-2022.1.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:34316bf693b1d2d29c087ee7e4bb10cdfa39da5f9c50fa15b07489b4ab93a1b5"}, - {file = "regex-2022.1.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a0b9f6a1a15d494b35f25ed07abda03209fa76c33564c09c9e81d34f4b919d7"}, - {file = "regex-2022.1.18-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f99112aed4fb7cee00c7f77e8b964a9b10f69488cdff626ffd797d02e2e4484f"}, - {file = "regex-2022.1.18-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a2bf98ac92f58777c0fafc772bf0493e67fcf677302e0c0a630ee517a43b949"}, - {file = "regex-2022.1.18-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8618d9213a863c468a865e9d2ec50221015f7abf52221bc927152ef26c484b4c"}, - {file = "regex-2022.1.18-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b52cc45e71657bc4743a5606d9023459de929b2a198d545868e11898ba1c3f59"}, - {file = "regex-2022.1.18-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e12949e5071c20ec49ef00c75121ed2b076972132fc1913ddf5f76cae8d10b4"}, - {file = "regex-2022.1.18-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b02e3e72665cd02afafb933453b0c9f6c59ff6e3708bd28d0d8580450e7e88af"}, - {file = "regex-2022.1.18-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:abfcb0ef78df0ee9df4ea81f03beea41849340ce33a4c4bd4dbb99e23ec781b6"}, - {file = "regex-2022.1.18-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6213713ac743b190ecbf3f316d6e41d099e774812d470422b3a0f137ea635832"}, - {file = "regex-2022.1.18-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:61ebbcd208d78658b09e19c78920f1ad38936a0aa0f9c459c46c197d11c580a0"}, - {file = "regex-2022.1.18-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:b013f759cd69cb0a62de954d6d2096d648bc210034b79b1881406b07ed0a83f9"}, - {file = "regex-2022.1.18-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9187500d83fd0cef4669385cbb0961e227a41c0c9bc39219044e35810793edf7"}, - {file = "regex-2022.1.18-cp310-cp310-win32.whl", hash = "sha256:94c623c331a48a5ccc7d25271399aff29729fa202c737ae3b4b28b89d2b0976d"}, - {file = "regex-2022.1.18-cp310-cp310-win_amd64.whl", hash = "sha256:1a171eaac36a08964d023eeff740b18a415f79aeb212169080c170ec42dd5184"}, - {file = "regex-2022.1.18-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:49810f907dfe6de8da5da7d2b238d343e6add62f01a15d03e2195afc180059ed"}, - {file = "regex-2022.1.18-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d2f5c3f7057530afd7b739ed42eb04f1011203bc5e4663e1e1d01bb50f813e3"}, - {file = "regex-2022.1.18-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:85ffd6b1cb0dfb037ede50ff3bef80d9bf7fa60515d192403af6745524524f3b"}, - {file = "regex-2022.1.18-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ba37f11e1d020969e8a779c06b4af866ffb6b854d7229db63c5fdddfceaa917f"}, - {file = "regex-2022.1.18-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637e27ea1ebe4a561db75a880ac659ff439dec7f55588212e71700bb1ddd5af9"}, - {file = "regex-2022.1.18-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:37978254d9d00cda01acc1997513f786b6b971e57b778fbe7c20e30ae81a97f3"}, - {file = "regex-2022.1.18-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e54a1eb9fd38f2779e973d2f8958fd575b532fe26013405d1afb9ee2374e7ab8"}, - {file = "regex-2022.1.18-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:768632fd8172ae03852e3245f11c8a425d95f65ff444ce46b3e673ae5b057b74"}, - {file = "regex-2022.1.18-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:de2923886b5d3214be951bc2ce3f6b8ac0d6dfd4a0d0e2a4d2e5523d8046fdfb"}, - {file = "regex-2022.1.18-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:1333b3ce73269f986b1fa4d5d395643810074dc2de5b9d262eb258daf37dc98f"}, - {file = "regex-2022.1.18-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:d19a34f8a3429bd536996ad53597b805c10352a8561d8382e05830df389d2b43"}, - {file = "regex-2022.1.18-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d2f355a951f60f0843f2368b39970e4667517e54e86b1508e76f92b44811a8a"}, - {file = "regex-2022.1.18-cp36-cp36m-win32.whl", hash = "sha256:2245441445099411b528379dee83e56eadf449db924648e5feb9b747473f42e3"}, - {file = "regex-2022.1.18-cp36-cp36m-win_amd64.whl", hash = "sha256:25716aa70a0d153cd844fe861d4f3315a6ccafce22b39d8aadbf7fcadff2b633"}, - {file = "regex-2022.1.18-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7e070d3aef50ac3856f2ef5ec7214798453da878bb5e5a16c16a61edf1817cc3"}, - {file = "regex-2022.1.18-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22709d701e7037e64dae2a04855021b62efd64a66c3ceed99dfd684bfef09e38"}, - {file = "regex-2022.1.18-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9099bf89078675c372339011ccfc9ec310310bf6c292b413c013eb90ffdcafc"}, - {file = "regex-2022.1.18-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04611cc0f627fc4a50bc4a9a2e6178a974c6a6a4aa9c1cca921635d2c47b9c87"}, - {file = "regex-2022.1.18-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:552a39987ac6655dad4bf6f17dd2b55c7b0c6e949d933b8846d2e312ee80005a"}, - {file = "regex-2022.1.18-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e031899cb2bc92c0cf4d45389eff5b078d1936860a1be3aa8c94fa25fb46ed8"}, - {file = "regex-2022.1.18-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2dacb3dae6b8cc579637a7b72f008bff50a94cde5e36e432352f4ca57b9e54c4"}, - {file = "regex-2022.1.18-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e5c31d70a478b0ca22a9d2d76d520ae996214019d39ed7dd93af872c7f301e52"}, - {file = "regex-2022.1.18-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bb804c7d0bfbd7e3f33924ff49757de9106c44e27979e2492819c16972ec0da2"}, - {file = "regex-2022.1.18-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:36b2d700a27e168fa96272b42d28c7ac3ff72030c67b32f37c05616ebd22a202"}, - {file = "regex-2022.1.18-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:16f81025bb3556eccb0681d7946e2b35ff254f9f888cff7d2120e8826330315c"}, - {file = "regex-2022.1.18-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:da80047524eac2acf7c04c18ac7a7da05a9136241f642dd2ed94269ef0d0a45a"}, - {file = "regex-2022.1.18-cp37-cp37m-win32.whl", hash = "sha256:6ca45359d7a21644793de0e29de497ef7f1ae7268e346c4faf87b421fea364e6"}, - {file = "regex-2022.1.18-cp37-cp37m-win_amd64.whl", hash = "sha256:38289f1690a7e27aacd049e420769b996826f3728756859420eeee21cc857118"}, - {file = "regex-2022.1.18-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6014038f52b4b2ac1fa41a58d439a8a00f015b5c0735a0cd4b09afe344c94899"}, - {file = "regex-2022.1.18-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0b5d6f9aed3153487252d00a18e53f19b7f52a1651bc1d0c4b5844bc286dfa52"}, - {file = "regex-2022.1.18-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9d24b03daf7415f78abc2d25a208f234e2c585e5e6f92f0204d2ab7b9ab48e3"}, - {file = "regex-2022.1.18-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bf594cc7cc9d528338d66674c10a5b25e3cde7dd75c3e96784df8f371d77a298"}, - {file = "regex-2022.1.18-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd914db437ec25bfa410f8aa0aa2f3ba87cdfc04d9919d608d02330947afaeab"}, - {file = "regex-2022.1.18-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90b6840b6448203228a9d8464a7a0d99aa8fa9f027ef95fe230579abaf8a6ee1"}, - {file = "regex-2022.1.18-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11772be1eb1748e0e197a40ffb82fb8fd0d6914cd147d841d9703e2bef24d288"}, - {file = "regex-2022.1.18-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a602bdc8607c99eb5b391592d58c92618dcd1537fdd87df1813f03fed49957a6"}, - {file = "regex-2022.1.18-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7e26eac9e52e8ce86f915fd33380f1b6896a2b51994e40bb094841e5003429b4"}, - {file = "regex-2022.1.18-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:519c0b3a6fbb68afaa0febf0d28f6c4b0a1074aefc484802ecb9709faf181607"}, - {file = "regex-2022.1.18-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3c7ea86b9ca83e30fa4d4cd0eaf01db3ebcc7b2726a25990966627e39577d729"}, - {file = "regex-2022.1.18-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:51f02ca184518702975b56affde6c573ebad4e411599005ce4468b1014b4786c"}, - {file = "regex-2022.1.18-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:385ccf6d011b97768a640e9d4de25412204fbe8d6b9ae39ff115d4ff03f6fe5d"}, - {file = "regex-2022.1.18-cp38-cp38-win32.whl", hash = "sha256:1f8c0ae0a0de4e19fddaaff036f508db175f6f03db318c80bbc239a1def62d02"}, - {file = "regex-2022.1.18-cp38-cp38-win_amd64.whl", hash = "sha256:760c54ad1b8a9b81951030a7e8e7c3ec0964c1cb9fee585a03ff53d9e531bb8e"}, - {file = "regex-2022.1.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:93c20777a72cae8620203ac11c4010365706062aa13aaedd1a21bb07adbb9d5d"}, - {file = "regex-2022.1.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6aa427c55a0abec450bca10b64446331b5ca8f79b648531138f357569705bc4a"}, - {file = "regex-2022.1.18-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c38baee6bdb7fe1b110b6b3aaa555e6e872d322206b7245aa39572d3fc991ee4"}, - {file = "regex-2022.1.18-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:752e7ddfb743344d447367baa85bccd3629c2c3940f70506eb5f01abce98ee68"}, - {file = "regex-2022.1.18-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8acef4d8a4353f6678fd1035422a937c2170de58a2b29f7da045d5249e934101"}, - {file = "regex-2022.1.18-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c73d2166e4b210b73d1429c4f1ca97cea9cc090e5302df2a7a0a96ce55373f1c"}, - {file = "regex-2022.1.18-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24c89346734a4e4d60ecf9b27cac4c1fee3431a413f7aa00be7c4d7bbacc2c4d"}, - {file = "regex-2022.1.18-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:596f5ae2eeddb79b595583c2e0285312b2783b0ec759930c272dbf02f851ff75"}, - {file = "regex-2022.1.18-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ecfe51abf7f045e0b9cdde71ca9e153d11238679ef7b5da6c82093874adf3338"}, - {file = "regex-2022.1.18-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1d6301f5288e9bdca65fab3de6b7de17362c5016d6bf8ee4ba4cbe833b2eda0f"}, - {file = "regex-2022.1.18-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:93cce7d422a0093cfb3606beae38a8e47a25232eea0f292c878af580a9dc7605"}, - {file = "regex-2022.1.18-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cf0db26a1f76aa6b3aa314a74b8facd586b7a5457d05b64f8082a62c9c49582a"}, - {file = "regex-2022.1.18-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:defa0652696ff0ba48c8aff5a1fac1eef1ca6ac9c660b047fc8e7623c4eb5093"}, - {file = "regex-2022.1.18-cp39-cp39-win32.whl", hash = "sha256:6db1b52c6f2c04fafc8da17ea506608e6be7086715dab498570c3e55e4f8fbd1"}, - {file = "regex-2022.1.18-cp39-cp39-win_amd64.whl", hash = "sha256:ebaeb93f90c0903233b11ce913a7cb8f6ee069158406e056f884854c737d2442"}, - {file = "regex-2022.1.18.tar.gz", hash = "sha256:97f32dc03a8054a4c4a5ab5d761ed4861e828b2c200febd4e46857069a483916"}, -] requests = [ {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, @@ -808,10 +783,26 @@ typed-ast = [ {file = "typed_ast-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:df05aa5b241e2e8045f5f4367a9f6187b09c4cdf8578bb219861c4e27c443db5"}, {file = "typed_ast-1.5.2.tar.gz", hash = "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27"}, ] +types-backports = [ + {file = "types-backports-0.1.3.tar.gz", hash = "sha256:f4b7206c073df88d6200891e3d27506185fd60cda66fb289737b2fa92c0010cf"}, + {file = "types_backports-0.1.3-py2.py3-none-any.whl", hash = "sha256:dafcd61848081503e738a7768872d1dd6c018401b4d2a1cfb608ea87ec9864b9"}, +] types-requests = [ {file = "types-requests-2.27.11.tar.gz", hash = "sha256:6a7ed24b21780af4a5b5e24c310b2cd885fb612df5fd95584d03d87e5f2a195a"}, {file = "types_requests-2.27.11-py3-none-any.whl", hash = "sha256:506279bad570c7b4b19ac1f22e50146538befbe0c133b2cea66a9b04a533a859"}, ] +types-retry = [ + {file = "types-retry-0.9.5.tar.gz", hash = "sha256:35a2ff8b987258154fb9c302a39df9e8691041b785f34a444a8d47660ef9b4cd"}, + {file = "types_retry-0.9.5-py3-none-any.whl", hash = "sha256:591393de4b821e6609914c62e64e4334c1ad7707d84ec9ee47ceae901ce6c23f"}, +] +types-setuptools = [ + {file = "types-setuptools-57.4.9.tar.gz", hash = "sha256:536ef74744f8e1e4be4fc719887f886e74e4cf3c792b4a06984320be4df450b5"}, + {file = "types_setuptools-57.4.9-py3-none-any.whl", hash = "sha256:948dc6863373750e2cd0b223a84f1fb608414cde5e55cf38ea657b93aeb411d2"}, +] +types-toml = [ + {file = "types-toml-0.10.4.tar.gz", hash = "sha256:9340e7c1587715581bb13905b3af30b79fe68afaccfca377665d5e63b694129a"}, + {file = "types_toml-0.10.4-py3-none-any.whl", hash = "sha256:4a9ffd47bbcec49c6fde6351a889b2c1bd3c0ef309fa0eed60dc28e58c8b9ea6"}, +] types-urllib3 = [ {file = "types-urllib3-1.26.10.tar.gz", hash = "sha256:a26898f530e6c3f43f25b907f2b884486868ffd56a9faa94cbf9b3eb6e165d6a"}, {file = "types_urllib3-1.26.10-py3-none-any.whl", hash = "sha256:d755278d5ecd7a7a6479a190e54230f241f1a99c19b81518b756b19dc69e518c"}, @@ -825,8 +816,8 @@ urllib3 = [ {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, ] xlsxwriter = [ - {file = "XlsxWriter-1.4.5-py2.py3-none-any.whl", hash = "sha256:f9335f1736e2c4fd80e940fe1b6d92d967bf454a1e5d639b0b7a4459ade790cc"}, - {file = "XlsxWriter-1.4.5.tar.gz", hash = "sha256:0956747859567ec01907e561a7d8413de18a7aae36860f979f9da52b9d58bc19"}, + {file = "XlsxWriter-3.0.3-py3-none-any.whl", hash = "sha256:df0aefe5137478d206847eccf9f114715e42aaea077e6a48d0e8a2152e983010"}, + {file = "XlsxWriter-3.0.3.tar.gz", hash = "sha256:e89f4a1d2fa2c9ea15cde77de95cd3fd8b0345d0efb3964623f395c8c4988b7f"}, ] zipp = [ {file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"}, diff --git a/pyproject.toml b/pyproject.toml index 943aa30..71b0d18 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pysnyk" -version = "0.8.1" +version = "0.9.0" description = "A Python client for the Snyk API" authors = [ "Gareth Rushgrove ", @@ -14,29 +14,31 @@ repository = "https://github.com/snyk-labs/pysnyk" [tool.poetry.dependencies] python = "^3.7" -requests = "^2.22" +requests = "^2.27.1" mashumaro = "^3" -importlib_metadata = "^4.4" +importlib_metadata = "^4.11.2" retry = "^0.9.2" deprecation = "^2.1.0" [tool.poetry.dev-dependencies] pytest = "^6.2.5" -pytest-black = "^0.3.10" +pytest-black = "^0.3.12" pytest-cov = "^3.0.0" pytest-mypy = "^0.9.1" -requests-mock = "^1.7" -xlsxwriter = "^1.1.8" -pytest-isort = "^1.1.0" -black = "^19.10b0" -types-requests = "^2.27.10" +requests-mock = "^1.9.3" +xlsxwriter = "^3.0.3" +pytest-isort = "^3" +black = "^22.1" +types-requests = "^2.27.11" +coverage = "^6.3.2" +types-backports = "^0.1.3" +types-retry = "^0.9.5" +types-setuptools = "^57.4.9" +types-toml = "^0.10.4" [tool.isort] -multi_line_output = 3 -include_trailing_comma = true -force_grid_wrap = 0 -combine_as_imports = true -line_length = 88 +profile = "black" + [build-system] requires = ["poetry>=0.12"] build-backend = "poetry.masonry.api" diff --git a/snyk/client.py b/snyk/client.py index 389c09b..7b596f5 100644 --- a/snyk/client.py +++ b/snyk/client.py @@ -1,13 +1,16 @@ import logging -from typing import Any, Optional +import urllib.parse +from types import NoneType +from typing import Any, List, Optional import requests -from retry.api import retry_call # type: ignore +from retry.api import retry_call from .__version__ import __version__ from .errors import SnykHTTPError, SnykNotImplementedError from .managers import Manager from .models import Organization, Project +from .utils import cleanup_path logger = logging.getLogger(__name__) @@ -25,6 +28,7 @@ def __init__( tries: int = 1, delay: int = 1, backoff: int = 2, + version: Optional[str] = None, ): self.api_token = token self.api_url = url or self.API_URL @@ -37,16 +41,40 @@ def __init__( self.tries = tries self.backoff = backoff self.delay = delay + self.version = version + + # Ensure we don't have a trailing / + if self.api_url[-1] == "/": + self.api_url = self.api_url.rstrip("/") + + if debug: + logging.basicConfig(level=logging.DEBUG) + + def request( + self, + method, + url: str, + headers: object, + params: object = None, + json: object = None, + ) -> requests.Response: + + if params and json: + resp = method(url, headers=headers, params=params, json=json) + elif params and not json: + resp = method(url, headers=headers, params=params) + elif json and not params: + resp = method(url, headers=headers, json=json) + else: + resp = method(url, headers=headers) - def request(self, method, url: str, headers: object, json={},) -> requests.Response: - resp = method(url, json=json, headers=headers,) if not resp or resp.status_code >= requests.codes.server_error: raise SnykHTTPError(resp) return resp def post(self, path: str, body: Any) -> requests.Response: - url = "%s/%s" % (self.api_url, path) - logger.debug("POST: %s" % url) + url = f"{self.api_url}/{path}" + logger.debug(f"POST: {url}") resp = retry_call( self.request, fargs=[requests.post, url], @@ -76,25 +104,65 @@ def put(self, path: str, body: Any) -> requests.Response: raise SnykHTTPError(resp) return resp - def get(self, path: str) -> requests.Response: - url = "%s/%s" % (self.api_url, path) - logger.debug("GET: %s" % url) + def get( + self, path: str, params: dict = None, version: str = None + ) -> requests.Response: + """ + V3 Compatible Snyk Client, assumes the presence of Version, either set in the client + or called in this method means that we're talking to a V3 endpoint and will ensure the + params are encoded properly with the version. + + Since certain endpoints can exist only in certain versions, being able to override the + client version with each GET is necessary + + Returns a standard requests Response object + """ + + path = cleanup_path(path, logger) + + url = f"{self.api_url}/{path}" + + if params or self.version: + + if not params: + params = {} + + # we use the presence of version to determine if we are v3 or not + if "version" not in params.keys() and self.version: + params["version"] = version or self.version + + # Python Bools are True/False, JS Bools are true/false + # Snyk v3 API is strictly case sensitive at the moment + + for k, v in params.items(): + if isinstance(v, bool): + params[k] = str(v).lower() + + debug_url = f"{url}&{urllib.parse.urlencode(params)}" + fkwargs = {"headers": self.api_headers, "params": params} + else: + debug_url = url + fkwargs = {"headers": self.api_headers} + + logger.debug(f"GET: {debug_url}") + resp = retry_call( self.request, fargs=[requests.get, url], - fkwargs={"headers": self.api_headers}, + fkwargs=fkwargs, tries=self.tries, delay=self.delay, backoff=self.backoff, logger=logger, ) + if not resp: raise SnykHTTPError(resp) return resp def delete(self, path: str) -> requests.Response: - url = "%s/%s" % (self.api_url, path) - logger.debug("DELETE: %s" % url) + url = f"{self.api_url}/{path}" + logger.debug(f"DELETE: {url}") resp = retry_call( self.request, fargs=[requests.delete, url], @@ -108,6 +176,47 @@ def delete(self, path: str) -> requests.Response: raise SnykHTTPError(resp) return resp + def get_v3_pages(self, path: str, params: dict = {}) -> List: + """ + Helper function to collect paginated responses from the V3 API into a single + list. + + This collects the "data" list from the first reponse and then appends the + any further "data" lists if a next link is found in the links field. + """ + + # this is a raw primative but a higher level module might want something that does an + # arbitrary path + origin=foo + limit=100 url construction instead before being sent here + + limit = params["limit"] + + data = list() + + page = self.get(path, params).json() + + data.extend(page["data"]) + + while "next" in page["links"].keys(): + logger.debug(f"GET_V3_PAGES: Another link exists: {page['links']['next']}") + + next_url = urllib.parse.urlsplit(page["links"]["next"]) + query = urllib.parse.parse_qs(next_url.query) + + for k, v in query.items(): + params[k] = v + + params["limit"] = limit + + page = self.get(next_url.path, params).json() + + data.extend(page["data"]) + + logger.debug( + f"GET_V3_PAGES: Added another {len(page['data'])} items to the response" + ) + + return data + @property def organizations(self) -> Manager: return Manager.factory(Organization, self) diff --git a/snyk/test_client.py b/snyk/test_client.py index e9bfce0..bbce5b4 100644 --- a/snyk/test_client.py +++ b/snyk/test_client.py @@ -1,3 +1,4 @@ +import os import re import pytest # type: ignore @@ -6,6 +7,13 @@ from snyk.__version__ import __version__ from snyk.errors import SnykError, SnykNotFoundError from snyk.models import Organization, Project +from snyk.utils import load_test_data + +TEST_DATA = os.path.join(os.path.dirname(__file__), "test_data") + +V3_ORG = "39ddc762-b1b9-41ce-ab42-defbe4575bd6" +V3_URL = "https://api.snyk.io/v3" +V3_VERSION = "2022-02-16~experimental" class TestSnykClient(object): @@ -113,53 +121,11 @@ def test_empty_organizations(self, requests_mock, client): @pytest.fixture def organizations(self): - return { - "orgs": [ - { - "name": "defaultOrg", - "id": "689ce7f9-7943-4a71-b704-2ba575f01089", - "group": None, - "slug": "default-org", - "url": "https://api.snyk.io/org/default-org", - }, - { - "name": "My Other Org", - "id": "a04d9cbd-ae6e-44af-b573-0556b0ad4bd2", - "group": { - "name": "ACME Inc.", - "id": "a060a49f-636e-480f-9e14-38e773b2a97f", - }, - "slug": "my-other-org", - "url": "https://api.snyk.io/org/my-other-org", - }, - ] - } + return load_test_data(TEST_DATA, "organizations") @pytest.fixture def projects(self): - return { - "projects": [ - { - "name": "atokeneduser/goof", - "id": "6d5813be-7e6d-4ab8-80c2-1e3e2a454545", - "created": "2018-10-29T09:50:54.014Z", - "origin": "cli", - "type": "npm", - "readOnly": "false", - "isMonitored": "true", - "testFrequency": "daily", - "totalDependencies": 438, - "issueCountsBySeverity": { - "critical": 1, - "low": 8, - "high": 13, - "medium": 15, - }, - "lastTestedDate": "2019-02-05T06:21:00.000Z", - "browseUrl": "https://app.snyk.io/org/pysnyk-test-org/project/6d5813be-7e6d-4ab8-80c2-1e3e2a454545", - } - ] - } + return load_test_data(TEST_DATA, "projects") def test_loads_organizations(self, requests_mock, client, organizations): requests_mock.get("https://snyk.io/api/v1/orgs", json=organizations) @@ -234,3 +200,62 @@ def test_non_existent_project(self, requests_mock, client, organizations, projec requests_mock.get(matcher, json=projects) with pytest.raises(SnykNotFoundError): client.projects.get("not-present") + + @pytest.fixture + def v3client(self): + return SnykClient( + "token", version="2022-02-16~experimental", url="https://api.snyk.io/v3" + ) + + @pytest.fixture + def v3_groups(self): + return load_test_data(TEST_DATA, "v3_groups") + + @pytest.fixture + def v3_targets_page1(self): + return load_test_data(TEST_DATA, "v3_targets_page1") + + @pytest.fixture + def v3_targets_page2(self): + return load_test_data(TEST_DATA, "v3_targets_page2") + + @pytest.fixture + def v3_targets_page3(self): + return load_test_data(TEST_DATA, "v3_targets_page3") + + def test_v3get(self, requests_mock, v3client, v3_targets_page1): + requests_mock.get( + f"{V3_URL}/orgs/{V3_ORG}/targets?limit=10&version={V3_VERSION}", + json=v3_targets_page1, + ) + t_params = {"limit": 10} + + targets = v3client.get(f"orgs/{V3_ORG}/targets", t_params).json() + + assert len(targets["data"]) == 10 + + def test_get_v3_pages( + self, + requests_mock, + v3client, + v3_targets_page1, + v3_targets_page2, + v3_targets_page3, + ): + requests_mock.get( + f"{V3_URL}/orgs/{V3_ORG}/targets?limit=10&version={V3_VERSION}", + json=v3_targets_page1, + ) + requests_mock.get( + f"{V3_URL}/orgs/{V3_ORG}/targets?limit=10&version={V3_VERSION}&excludeEmpty=true&starting_after=v1.eyJpZCI6IjMyODE4ODAifQ%3D%3D", + json=v3_targets_page2, + ) + requests_mock.get( + f"{V3_URL}/orgs/{V3_ORG}/targets?limit=10&version={V3_VERSION}&excludeEmpty=true&starting_after=v1.eyJpZCI6IjI5MTk1NjgifQ%3D%3D", + json=v3_targets_page3, + ) + t_params = {"limit": 10} + + data = v3client.get_v3_pages(f"orgs/{V3_ORG}/targets", t_params) + + assert len(data) == 30 diff --git a/snyk/test_data/organizations.json b/snyk/test_data/organizations.json new file mode 100644 index 0000000..606e5d2 --- /dev/null +++ b/snyk/test_data/organizations.json @@ -0,0 +1,21 @@ +{ + "orgs": [ + { + "name": "defaultOrg", + "id": "689ce7f9-7943-4a71-b704-2ba575f01089", + "group": null, + "slug": "default-org", + "url": "https://api.snyk.io/org/default-org" + }, + { + "name": "My Other Org", + "id": "a04d9cbd-ae6e-44af-b573-0556b0ad4bd2", + "group": { + "name": "ACME Inc.", + "id": "a060a49f-636e-480f-9e14-38e773b2a97f" + }, + "slug": "my-other-org", + "url": "https://api.snyk.io/org/my-other-org" + } + ] +} \ No newline at end of file diff --git a/snyk/test_data/projects.json b/snyk/test_data/projects.json new file mode 100644 index 0000000..f5779a1 --- /dev/null +++ b/snyk/test_data/projects.json @@ -0,0 +1,23 @@ +{ + "projects": [ + { + "name": "atokeneduser/goof", + "id": "6d5813be-7e6d-4ab8-80c2-1e3e2a454545", + "created": "2018-10-29T09:50:54.014Z", + "origin": "cli", + "type": "npm", + "readOnly": "false", + "isMonitored": "true", + "testFrequency": "daily", + "totalDependencies": 438, + "issueCountsBySeverity": { + "critical": 1, + "low": 8, + "high": 13, + "medium": 15 + }, + "lastTestedDate": "2019-02-05T06:21:00.000Z", + "browseUrl": "https://app.snyk.io/org/pysnyk-test-org/project/6d5813be-7e6d-4ab8-80c2-1e3e2a454545" + } + ] +} \ No newline at end of file diff --git a/snyk/test_data/v3_groups.json b/snyk/test_data/v3_groups.json new file mode 100644 index 0000000..70e0b21 --- /dev/null +++ b/snyk/test_data/v3_groups.json @@ -0,0 +1,22 @@ +{ + "jsonapi": { + "version": "1.0" + }, + "data": [ + { + "type": "group", + "id": "36863d40-ba29-491f-af63-7a1a7d79e411", + "attributes": { + "name": "Customer Success Engineering" + } + }, + { + "type": "group", + "id": "164f8e6d-ea58-45ef-a638-259cc0fe90d4", + "attributes": { + "name": "Goof Ltd (Enterprise)" + } + } + ], + "links": {} +} \ No newline at end of file diff --git a/snyk/test_data/v3_targets_page1.json b/snyk/test_data/v3_targets_page1.json new file mode 100644 index 0000000..67359ef --- /dev/null +++ b/snyk/test_data/v3_targets_page1.json @@ -0,0 +1,120 @@ +{ + "jsonapi": { + "version": "1.0" + }, + "data": [ + { + "type": "target", + "id": "44948be7-561c-4e03-80af-27232eaea006", + "attributes": { + "isPrivate": true, + "origin": "cli", + "displayName": "snyk-playground/maven-multi-with-gh-prevent", + "remoteUrl": "http://github.com/snyk-playground/maven-multi-with-gh-prevent.git" + }, + "relationships": {} + }, + { + "type": "target", + "id": "c3035b3b-9416-4d00-9e49-bdb561560ea8", + "attributes": { + "isPrivate": true, + "origin": "github-enterprise", + "displayName": "snyk-playground/maven-multi-with-gh-prevent", + "remoteUrl": null + }, + "relationships": {} + }, + { + "type": "target", + "id": "b17a13c7-25f4-4360-bba5-d1098efb579c", + "attributes": { + "isPrivate": false, + "origin": "github-enterprise", + "displayName": "snyk-tech-services/snyk-project-tldr", + "remoteUrl": null + }, + "relationships": {} + }, + { + "type": "target", + "id": "d3ce9715-6cd8-4940-b326-5fa7d35988f2", + "attributes": { + "isPrivate": true, + "origin": "cli", + "displayName": "snyk-tech-services/snyk-bulk-yarn", + "remoteUrl": "http://github.com/snyk-tech-services/snyk-bulk-yarn" + }, + "relationships": {} + }, + { + "type": "target", + "id": "3d8fd759-1fdf-4827-91a7-8706d54462de", + "attributes": { + "isPrivate": false, + "origin": "github-enterprise", + "displayName": "snyk-playground/sync-aws-deploy", + "remoteUrl": null + }, + "relationships": {} + }, + { + "type": "target", + "id": "f26b4ae2-59e5-4be9-978d-0b2157d04d7c", + "attributes": { + "isPrivate": false, + "origin": "github-enterprise", + "displayName": "snyk-playground/bazel-examples", + "remoteUrl": null + }, + "relationships": {} + }, + { + "type": "target", + "id": "1aae46bd-13e1-4c6c-9cbe-8849732ec00e", + "attributes": { + "isPrivate": false, + "origin": "github-enterprise", + "displayName": "snyk-playground/example-bazel-monorepo", + "remoteUrl": null + }, + "relationships": {} + }, + { + "type": "target", + "id": "f6017bfd-e441-4482-b060-78e56daff65c", + "attributes": { + "isPrivate": true, + "origin": "github-enterprise", + "displayName": "snyk-playground/docker-goof", + "remoteUrl": null + }, + "relationships": {} + }, + { + "type": "target", + "id": "84519645-6a5a-4b0f-9132-a65e76d3aa03", + "attributes": { + "isPrivate": true, + "origin": "github-enterprise", + "displayName": "snyk-playground/monorepo-kotlin", + "remoteUrl": null + }, + "relationships": {} + }, + { + "type": "target", + "id": "dc2aa733-550d-43dd-9066-e9a35b6f3982", + "attributes": { + "isPrivate": true, + "origin": "github-enterprise", + "displayName": "snyk-playground/org-import-branch-override", + "remoteUrl": null + }, + "relationships": {} + } + ], + "links": { + "next": "/orgs/39ddc762-b1b9-41ce-ab42-defbe4575bd6/targets?limit=10&version=2022-02-16~experimental&excludeEmpty=true&starting_after=v1.eyJpZCI6IjMyODE4ODAifQ%3D%3D" + } +} \ No newline at end of file diff --git a/snyk/test_data/v3_targets_page2.json b/snyk/test_data/v3_targets_page2.json new file mode 100644 index 0000000..0008af9 --- /dev/null +++ b/snyk/test_data/v3_targets_page2.json @@ -0,0 +1,121 @@ +{ + "jsonapi": { + "version": "1.0" + }, + "data": [ + { + "type": "target", + "id": "9a181724-d17d-4a92-a85c-72594c0df5e1", + "attributes": { + "isPrivate": false, + "origin": "github-enterprise", + "displayName": "snyk-playground/org-import-instance", + "remoteUrl": null + }, + "relationships": {} + }, + { + "type": "target", + "id": "9aafc91d-76c1-4279-aa27-6e648130f003", + "attributes": { + "isPrivate": true, + "origin": "github-enterprise", + "displayName": "snyk-playground/pygithub-import-parser", + "remoteUrl": null + }, + "relationships": {} + }, + { + "type": "target", + "id": "bcb30f5f-ae18-4d32-ac62-1459b10050ae", + "attributes": { + "isPrivate": false, + "origin": "github-enterprise", + "displayName": "snyk-playground/org-project-import", + "remoteUrl": null + }, + "relationships": {} + }, + { + "type": "target", + "id": "08beb7e6-ccdf-437a-95dd-9f3c8ad66cd8", + "attributes": { + "isPrivate": false, + "origin": "github-enterprise", + "displayName": "snyk-playground/repo-with-jira-config", + "remoteUrl": null + }, + "relationships": {} + }, + { + "type": "target", + "id": "e29608d3-75dc-46ad-8ef4-3bf3c0331f35", + "attributes": { + "isPrivate": true, + "origin": "github", + "displayName": "snyk-playground/multi-project-code", + "remoteUrl": null + }, + "relationships": {} + }, + { + "type": "target", + "id": "f94abd6f-d436-4aa8-b4d0-51523a7224c5", + "attributes": { + "isPrivate": true, + "origin": "github", + "displayName": "scotte-snyk/example-yarn", + "remoteUrl": null + }, + "relationships": {} + }, + { + "type": "target", + "id": "b248a8b5-98a6-4a08-831c-748776ebc1d9", + "attributes": { + "isPrivate": true, + "origin": "github", + "displayName": "scotte-snyk/test-ruby-project", + "remoteUrl": null + }, + "relationships": {} + }, + { + "type": "target", + "id": "53c9a6b1-a46c-44a3-a11f-296b9bd88e1e", + "attributes": { + "isPrivate": true, + "origin": "github", + "displayName": "scotte-snyk/demo-sonarr-renamed", + "remoteUrl": null + }, + "relationships": {} + }, + { + "type": "target", + "id": "0e8f2413-af32-45a2-92f5-b9f8ada48374", + "attributes": { + "isPrivate": true, + "origin": "github", + "displayName": "scotte-snyk/vulnerable-php-app", + "remoteUrl": null + }, + "relationships": {} + }, + { + "type": "target", + "id": "e52ec5a2-6b11-457e-a8fc-4d86e6fbfab1", + "attributes": { + "isPrivate": true, + "origin": "cli", + "displayName": "kozmer/log4j-shell-poc", + "remoteUrl": "http://github.com/kozmer/log4j-shell-poc.git" + }, + "relationships": {} + } + ], + "links": { + "next": "/orgs/39ddc762-b1b9-41ce-ab42-defbe4575bd6/targets?limit=10&version=2022-02-16~experimental&excludeEmpty=true&starting_after=v1.eyJpZCI6IjI5MTk1NjgifQ%3D%3D", + "prev": "/orgs/39ddc762-b1b9-41ce-ab42-defbe4575bd6/targets?limit=10&version=2022-02-16~experimental&excludeEmpty=true&ending_before=v1.eyJpZCI6IjMyODE4NzkifQ%3D%3D" + } +} \ No newline at end of file diff --git a/snyk/test_data/v3_targets_page3.json b/snyk/test_data/v3_targets_page3.json new file mode 100644 index 0000000..f23acbb --- /dev/null +++ b/snyk/test_data/v3_targets_page3.json @@ -0,0 +1,120 @@ +{ + "jsonapi": { + "version": "1.0" + }, + "data": [ + { + "type": "target", + "id": "44948be7-561c-4e03-80af-27232eaea006", + "attributes": { + "isPrivate": true, + "origin": "cli", + "displayName": "snyk-playground/maven-multi-with-gh-prevent", + "remoteUrl": "http://github.com/snyk-playground/maven-multi-with-gh-prevent.git" + }, + "relationships": {} + }, + { + "type": "target", + "id": "c3035b3b-9416-4d00-9e49-bdb561560ea8", + "attributes": { + "isPrivate": true, + "origin": "github-enterprise", + "displayName": "snyk-playground/maven-multi-with-gh-prevent", + "remoteUrl": null + }, + "relationships": {} + }, + { + "type": "target", + "id": "b17a13c7-25f4-4360-bba5-d1098efb579c", + "attributes": { + "isPrivate": false, + "origin": "github-enterprise", + "displayName": "snyk-tech-services/snyk-project-tldr", + "remoteUrl": null + }, + "relationships": {} + }, + { + "type": "target", + "id": "d3ce9715-6cd8-4940-b326-5fa7d35988f2", + "attributes": { + "isPrivate": true, + "origin": "cli", + "displayName": "snyk-tech-services/snyk-bulk-yarn", + "remoteUrl": "http://github.com/snyk-tech-services/snyk-bulk-yarn" + }, + "relationships": {} + }, + { + "type": "target", + "id": "3d8fd759-1fdf-4827-91a7-8706d54462de", + "attributes": { + "isPrivate": false, + "origin": "github-enterprise", + "displayName": "snyk-playground/sync-aws-deploy", + "remoteUrl": null + }, + "relationships": {} + }, + { + "type": "target", + "id": "f26b4ae2-59e5-4be9-978d-0b2157d04d7c", + "attributes": { + "isPrivate": false, + "origin": "github-enterprise", + "displayName": "snyk-playground/bazel-examples", + "remoteUrl": null + }, + "relationships": {} + }, + { + "type": "target", + "id": "1aae46bd-13e1-4c6c-9cbe-8849732ec00e", + "attributes": { + "isPrivate": false, + "origin": "github-enterprise", + "displayName": "snyk-playground/example-bazel-monorepo", + "remoteUrl": null + }, + "relationships": {} + }, + { + "type": "target", + "id": "f6017bfd-e441-4482-b060-78e56daff65c", + "attributes": { + "isPrivate": true, + "origin": "github-enterprise", + "displayName": "snyk-playground/docker-goof", + "remoteUrl": null + }, + "relationships": {} + }, + { + "type": "target", + "id": "84519645-6a5a-4b0f-9132-a65e76d3aa03", + "attributes": { + "isPrivate": true, + "origin": "github-enterprise", + "displayName": "snyk-playground/monorepo-kotlin", + "remoteUrl": null + }, + "relationships": {} + }, + { + "type": "target", + "id": "dc2aa733-550d-43dd-9066-e9a35b6f3982", + "attributes": { + "isPrivate": true, + "origin": "github-enterprise", + "displayName": "snyk-playground/org-import-branch-override", + "remoteUrl": null + }, + "relationships": {} + } + ], + "links": { + "prev": "/orgs/39ddc762-b1b9-41ce-ab42-defbe4575bd6/targets?limit=10&version=2022-02-16~experimental&excludeEmpty=true&ending_before=v1.eyJpZCI6IjMyODE4ODAifQ%3D%3D" + } +} \ No newline at end of file diff --git a/snyk/test_models.py b/snyk/test_models.py index f8dbd4c..8301d4e 100644 --- a/snyk/test_models.py +++ b/snyk/test_models.py @@ -483,13 +483,15 @@ def test_empty_issues(self, project, project_url, requests_mock): def test_empty_issues_aggregated(self, project, project_url, requests_mock): requests_mock.post( - "%s/aggregated-issues" % project_url, json={"issues": []}, + "%s/aggregated-issues" % project_url, + json={"issues": []}, ) assert [] == project.issueset_aggregated.all().issues def test_empty_vulnerabilities(self, project, project_url, requests_mock): requests_mock.post( - "%s/aggregated-issues" % project_url, json={"issues": []}, + "%s/aggregated-issues" % project_url, + json={"issues": []}, ) assert [] == project.vulnerabilities @@ -644,7 +646,8 @@ def test_filtering_empty_issues_aggregated( self, project, project_url, requests_mock ): requests_mock.post( - "%s/aggregated-issues" % project_url, json={"issues": []}, + "%s/aggregated-issues" % project_url, + json={"issues": []}, ) assert [] == project.issueset_aggregated.filter(ignored=True).issues diff --git a/snyk/utils.py b/snyk/utils.py index fa9cdc1..e5d5db1 100644 --- a/snyk/utils.py +++ b/snyk/utils.py @@ -1,4 +1,5 @@ -import re +import json +import logging from itertools import chain @@ -19,3 +20,33 @@ def flat_map(fn, *args): def format_package(pkg): return "{name}@{version}".format(name=pkg.name, version=pkg.version or "*") + + +def cleanup_path(path: str, logger: logging.Logger) -> str: + """ + Strings '/' from the start and end of strings if present to ensure that a '//' doesn't + occur in an API request due to copy/paste error + """ + + if path[0] == "/": + logger.warn(f"WARNING: removing unneccessary leading / from {path}") + path = path[1:] + if path[-1] == "/": + logger.warn(f"WARNING: removing unneccessary trailing / from {path}") + path = path.rstrip("/") + + return path + + +def load_test_data(test_dir: str, test_name: str) -> dict: + """ + Returns the contents of a json file at location of: + test_dir/test_name.json + + This is meant to keep large amounts of json needed for testing outside of + the tests themselves and as the actual json responses from the API + """ + test_file = f"{test_dir}/{test_name}.json" + with open(test_file, "r") as the_file: + data = the_file.read() + return json.loads(data) From 4641508863b27fe44ee6c0d60569993a1078f4f2 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 2 Mar 2022 16:01:57 +0000 Subject: [PATCH 3/4] resolve suggested formatting fixes / changes --- README.md | 24 ++++++++++++------------ snyk/client.py | 1 - snyk/utils.py | 4 ++-- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 24ab3f4..2a0fafd 100644 --- a/README.md +++ b/README.md @@ -211,17 +211,18 @@ client.post("", ) Most of the time you shouldn't need to use these. They are mainly useful if new methods are added to the API which are not yet supported in the client. This can also be useful if you want to pass very specific parameters, or to parse the raw JSON output from the API. -## Experimental V3 Low Level Client +## Experimental v3 low-level client -pysnyk >= 0.9.0 now includes support for basic V3 compatibility. To switch to use a V3 client, pass the V3 API url and version when initializing a client. Right now it supports the `GET` method. Refer to the [V3 API docs](https://apidocs.snyk.io/) for more information and examples. +pysnyk >= 0.9.0 now includes support for basic v3 compatibility. To switch to use a v3 client, pass the v3 API url and version when initializing a client. Right now it supports the `GET` method. Refer to the [v3 API docs](https://apidocs.snyk.io/) for more information and examples. -Getting the V3 information of an organization: +Getting the v3 information of an organization: ```python +# To get this value, get it from a Snyk organizations settings page +snyk_org = "df734bed-d75c-4f11-bb47-1d119913bcc7" -snyk_org = "39ddc762-b1b9-41ce-ab42-defbe4575bd6" - -v3client = SnykClient(snyk_token,version="2022-02-16~experimental",url="https://api.snyk.io/v3") +# to use the v3 endpoint you MUST include a version value and the url of the v3 api endpoint as shown below +v3client = SnykClient(snyk_token, version="2022-02-16~experimental", url="https://api.snyk.io/v3") print(v3client.get(f"/orgs/{snyk_org}").json()) @@ -229,30 +230,29 @@ print(v3client.get(f"/orgs/{snyk_org}").json()) user = v3client.get(f"orgs/{snyk_org}/users/{snyk_user}", version="2022-02-01~experimental").json() # pass parameters such as how many results per page - params = {"limit": 10} targets = v3client.get(f"orgs/{snyk_org}/targets", params=params) ``` -V1 and V3 can work at the same time by instantiating two clients: +V1 and v3 can work at the same time by instantiating two clients: ```python -snyk_org = "39ddc762-b1b9-41ce-ab42-defbe4575bd6" +snyk_org = "df734bed-d75c-4f11-bb47-1d119913bcc7" v1client = SnykClient(snyk_token) -v3client = SnykClient(snyk_token,version="2022-02-16~experimental",url="https://api.snyk.io/v3") +v3client = SnykClient(snyk_token, version="2022-02-16~experimental", url="https://api.snyk.io/v3") v1_org = v1client.organizations.get(snyk_org) v3_org = v3client.get(f"/orgs/{snyk_org}").json() ``` -The V3 API introduces consistent pagination across all endpoints. The v3 client includes a helper method `.get_v3_pages` which collects the paginated responses and returns a single list combining the contents of the "data" key from all pages. It takes the same values as the get method. +The v3 API introduces consistent pagination across all endpoints. The v3 client includes a helper method `.get_v3_pages` which collects the paginated responses and returns a single list combining the contents of the "data" key from all pages. It takes the same values as the get method. ```python -v3client = SnykClient(snyk_token,version="2022-02-16~experimental",url="https://api.snyk.io/v3") +v3client = SnykClient(snyk_token, version="2022-02-16~experimental", url="https://api.snyk.io/v3") params = {"limit": 10} diff --git a/snyk/client.py b/snyk/client.py index 7b596f5..91feed4 100644 --- a/snyk/client.py +++ b/snyk/client.py @@ -1,6 +1,5 @@ import logging import urllib.parse -from types import NoneType from typing import Any, List, Optional import requests diff --git a/snyk/utils.py b/snyk/utils.py index e5d5db1..4713c29 100644 --- a/snyk/utils.py +++ b/snyk/utils.py @@ -29,10 +29,10 @@ def cleanup_path(path: str, logger: logging.Logger) -> str: """ if path[0] == "/": - logger.warn(f"WARNING: removing unneccessary leading / from {path}") + logger.warn(f"removing unneccessary leading / from {path}") path = path[1:] if path[-1] == "/": - logger.warn(f"WARNING: removing unneccessary trailing / from {path}") + logger.warn(f"removing unneccessary trailing / from {path}") path = path.rstrip("/") return path From 1df9329e1c4bec0b9b154b6236f4aec1b40f922b Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 8 Mar 2022 11:42:15 +0000 Subject: [PATCH 4/4] fixup black formatting errors --- snyk/models.py | 3 ++- snyk/test_models.py | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/snyk/models.py b/snyk/models.py index fd869ba..f854f7d 100644 --- a/snyk/models.py +++ b/snyk/models.py @@ -663,7 +663,8 @@ def _aggregated_issue_to_vulnerabily( try: upgradable_paths = filter( - lambda path: path[0].fixVersion is not None, issue_paths.paths, + lambda path: path[0].fixVersion is not None, + issue_paths.paths, ) first_path = next(upgradable_paths) upgrade_path = list(map(format_package, first_path)) diff --git a/snyk/test_models.py b/snyk/test_models.py index 8301d4e..62e7cec 100644 --- a/snyk/test_models.py +++ b/snyk/test_models.py @@ -575,7 +575,10 @@ def test_vulnerabilities(self, project, project_url, requests_mock): "snapshotId": "bb00717d-4618-4ceb-bebd-ec268a563e98", "paths": [ [ - {"name": "tap", "version": "11.1.5",}, + { + "name": "tap", + "version": "11.1.5", + }, {"name": "nyc", "version": "11.9.0"}, {"name": "istanbul-lib-instrument", "version": "1.10.1"}, {"name": "babel-traverse", "version": "6.26.0"},