From d9c7389fd3e6d8ac9d847f75cadea9a10c295e5e Mon Sep 17 00:00:00 2001 From: Benjamin Wingfield Date: Tue, 20 Feb 2024 16:19:58 +0000 Subject: [PATCH 1/2] update vulnerable dependencies --- poetry.lock | 305 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 194 insertions(+), 111 deletions(-) diff --git a/poetry.lock b/poetry.lock index 05b2c77..a5c92f1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "anyio" @@ -753,59 +753,67 @@ typing = ["typing-extensions (>=4.8)"] [[package]] name = "fonttools" -version = "4.42.1" +version = "4.49.0" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" files = [ - {file = "fonttools-4.42.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ed1a13a27f59d1fc1920394a7f596792e9d546c9ca5a044419dca70c37815d7c"}, - {file = "fonttools-4.42.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c9b1ce7a45978b821a06d375b83763b27a3a5e8a2e4570b3065abad240a18760"}, - {file = "fonttools-4.42.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f720fa82a11c0f9042376fd509b5ed88dab7e3cd602eee63a1af08883b37342b"}, - {file = "fonttools-4.42.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db55cbaea02a20b49fefbd8e9d62bd481aaabe1f2301dabc575acc6b358874fa"}, - {file = "fonttools-4.42.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a35981d90feebeaef05e46e33e6b9e5b5e618504672ca9cd0ff96b171e4bfff"}, - {file = "fonttools-4.42.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:68a02bbe020dc22ee0540e040117535f06df9358106d3775e8817d826047f3fd"}, - {file = "fonttools-4.42.1-cp310-cp310-win32.whl", hash = "sha256:12a7c247d1b946829bfa2f331107a629ea77dc5391dfd34fdcd78efa61f354ca"}, - {file = "fonttools-4.42.1-cp310-cp310-win_amd64.whl", hash = "sha256:a398bdadb055f8de69f62b0fc70625f7cbdab436bbb31eef5816e28cab083ee8"}, - {file = "fonttools-4.42.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:689508b918332fb40ce117131633647731d098b1b10d092234aa959b4251add5"}, - {file = "fonttools-4.42.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e36344e48af3e3bde867a1ca54f97c308735dd8697005c2d24a86054a114a71"}, - {file = "fonttools-4.42.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19b7db825c8adee96fac0692e6e1ecd858cae9affb3b4812cdb9d934a898b29e"}, - {file = "fonttools-4.42.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:113337c2d29665839b7d90b39f99b3cac731f72a0eda9306165a305c7c31d341"}, - {file = "fonttools-4.42.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:37983b6bdab42c501202500a2be3a572f50d4efe3237e0686ee9d5f794d76b35"}, - {file = "fonttools-4.42.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6ed2662a3d9c832afa36405f8748c250be94ae5dfc5283d668308391f2102861"}, - {file = "fonttools-4.42.1-cp311-cp311-win32.whl", hash = "sha256:179737095eb98332a2744e8f12037b2977f22948cf23ff96656928923ddf560a"}, - {file = "fonttools-4.42.1-cp311-cp311-win_amd64.whl", hash = "sha256:f2b82f46917d8722e6b5eafeefb4fb585d23babd15d8246c664cd88a5bddd19c"}, - {file = "fonttools-4.42.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:62f481ac772fd68901573956231aea3e4b1ad87b9b1089a61613a91e2b50bb9b"}, - {file = "fonttools-4.42.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f2f806990160d1ce42d287aa419df3ffc42dfefe60d473695fb048355fe0c6a0"}, - {file = "fonttools-4.42.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db372213d39fa33af667c2aa586a0c1235e88e9c850f5dd5c8e1f17515861868"}, - {file = "fonttools-4.42.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d18fc642fd0ac29236ff88ecfccff229ec0386090a839dd3f1162e9a7944a40"}, - {file = "fonttools-4.42.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8708b98c278012ad267ee8a7433baeb809948855e81922878118464b274c909d"}, - {file = "fonttools-4.42.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c95b0724a6deea2c8c5d3222191783ced0a2f09bd6d33f93e563f6f1a4b3b3a4"}, - {file = "fonttools-4.42.1-cp38-cp38-win32.whl", hash = "sha256:4aa79366e442dbca6e2c8595645a3a605d9eeabdb7a094d745ed6106816bef5d"}, - {file = "fonttools-4.42.1-cp38-cp38-win_amd64.whl", hash = "sha256:acb47f6f8680de24c1ab65ebde39dd035768e2a9b571a07c7b8da95f6c8815fd"}, - {file = "fonttools-4.42.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fb289b7a815638a7613d46bcf324c9106804725b2bb8ad913c12b6958ffc4ec"}, - {file = "fonttools-4.42.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:53eb5091ddc8b1199330bb7b4a8a2e7995ad5d43376cadce84523d8223ef3136"}, - {file = "fonttools-4.42.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46a0ec8adbc6ff13494eb0c9c2e643b6f009ce7320cf640de106fb614e4d4360"}, - {file = "fonttools-4.42.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cc7d685b8eeca7ae69dc6416833fbfea61660684b7089bca666067cb2937dcf"}, - {file = "fonttools-4.42.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:be24fcb80493b2c94eae21df70017351851652a37de514de553435b256b2f249"}, - {file = "fonttools-4.42.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:515607ec756d7865f23070682622c49d922901943697871fc292277cf1e71967"}, - {file = "fonttools-4.42.1-cp39-cp39-win32.whl", hash = "sha256:0eb79a2da5eb6457a6f8ab904838454accc7d4cccdaff1fd2bd3a0679ea33d64"}, - {file = "fonttools-4.42.1-cp39-cp39-win_amd64.whl", hash = "sha256:7286aed4ea271df9eab8d7a9b29e507094b51397812f7ce051ecd77915a6e26b"}, - {file = "fonttools-4.42.1-py3-none-any.whl", hash = "sha256:9398f244e28e0596e2ee6024f808b06060109e33ed38dcc9bded452fd9bbb853"}, - {file = "fonttools-4.42.1.tar.gz", hash = "sha256:c391cd5af88aacaf41dd7cfb96eeedfad297b5899a39e12f4c2c3706d0a3329d"}, -] - -[package.extras] -all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.0.0)", "xattr", "zopfli (>=0.1.4)"] + {file = "fonttools-4.49.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d970ecca0aac90d399e458f0b7a8a597e08f95de021f17785fb68e2dc0b99717"}, + {file = "fonttools-4.49.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac9a745b7609f489faa65e1dc842168c18530874a5f5b742ac3dd79e26bca8bc"}, + {file = "fonttools-4.49.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ba0e00620ca28d4ca11fc700806fd69144b463aa3275e1b36e56c7c09915559"}, + {file = "fonttools-4.49.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdee3ab220283057e7840d5fb768ad4c2ebe65bdba6f75d5d7bf47f4e0ed7d29"}, + {file = "fonttools-4.49.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ce7033cb61f2bb65d8849658d3786188afd80f53dad8366a7232654804529532"}, + {file = "fonttools-4.49.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:07bc5ea02bb7bc3aa40a1eb0481ce20e8d9b9642a9536cde0218290dd6085828"}, + {file = "fonttools-4.49.0-cp310-cp310-win32.whl", hash = "sha256:86eef6aab7fd7c6c8545f3ebd00fd1d6729ca1f63b0cb4d621bccb7d1d1c852b"}, + {file = "fonttools-4.49.0-cp310-cp310-win_amd64.whl", hash = "sha256:1fac1b7eebfce75ea663e860e7c5b4a8831b858c17acd68263bc156125201abf"}, + {file = "fonttools-4.49.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:edc0cce355984bb3c1d1e89d6a661934d39586bb32191ebff98c600f8957c63e"}, + {file = "fonttools-4.49.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:83a0d9336de2cba86d886507dd6e0153df333ac787377325a39a2797ec529814"}, + {file = "fonttools-4.49.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36c8865bdb5cfeec88f5028e7e592370a0657b676c6f1d84a2108e0564f90e22"}, + {file = "fonttools-4.49.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33037d9e56e2562c710c8954d0f20d25b8386b397250d65581e544edc9d6b942"}, + {file = "fonttools-4.49.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8fb022d799b96df3eaa27263e9eea306bd3d437cc9aa981820850281a02b6c9a"}, + {file = "fonttools-4.49.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:33c584c0ef7dc54f5dd4f84082eabd8d09d1871a3d8ca2986b0c0c98165f8e86"}, + {file = "fonttools-4.49.0-cp311-cp311-win32.whl", hash = "sha256:cbe61b158deb09cffdd8540dc4a948d6e8f4d5b4f3bf5cd7db09bd6a61fee64e"}, + {file = "fonttools-4.49.0-cp311-cp311-win_amd64.whl", hash = "sha256:fc11e5114f3f978d0cea7e9853627935b30d451742eeb4239a81a677bdee6bf6"}, + {file = "fonttools-4.49.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d647a0e697e5daa98c87993726da8281c7233d9d4ffe410812a4896c7c57c075"}, + {file = "fonttools-4.49.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f3bbe672df03563d1f3a691ae531f2e31f84061724c319652039e5a70927167e"}, + {file = "fonttools-4.49.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bebd91041dda0d511b0d303180ed36e31f4f54b106b1259b69fade68413aa7ff"}, + {file = "fonttools-4.49.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4145f91531fd43c50f9eb893faa08399816bb0b13c425667c48475c9f3a2b9b5"}, + {file = "fonttools-4.49.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ea329dafb9670ffbdf4dbc3b0e5c264104abcd8441d56de77f06967f032943cb"}, + {file = "fonttools-4.49.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c076a9e548521ecc13d944b1d261ff3d7825048c338722a4bd126d22316087b7"}, + {file = "fonttools-4.49.0-cp312-cp312-win32.whl", hash = "sha256:b607ea1e96768d13be26d2b400d10d3ebd1456343eb5eaddd2f47d1c4bd00880"}, + {file = "fonttools-4.49.0-cp312-cp312-win_amd64.whl", hash = "sha256:a974c49a981e187381b9cc2c07c6b902d0079b88ff01aed34695ec5360767034"}, + {file = "fonttools-4.49.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b85ec0bdd7bdaa5c1946398cbb541e90a6dfc51df76dfa88e0aaa41b335940cb"}, + {file = "fonttools-4.49.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:af20acbe198a8a790618ee42db192eb128afcdcc4e96d99993aca0b60d1faeb4"}, + {file = "fonttools-4.49.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d418b1fee41a1d14931f7ab4b92dc0bc323b490e41d7a333eec82c9f1780c75"}, + {file = "fonttools-4.49.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b44a52b8e6244b6548851b03b2b377a9702b88ddc21dcaf56a15a0393d425cb9"}, + {file = "fonttools-4.49.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7c7125068e04a70739dad11857a4d47626f2b0bd54de39e8622e89701836eabd"}, + {file = "fonttools-4.49.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:29e89d0e1a7f18bc30f197cfadcbef5a13d99806447c7e245f5667579a808036"}, + {file = "fonttools-4.49.0-cp38-cp38-win32.whl", hash = "sha256:9d95fa0d22bf4f12d2fb7b07a46070cdfc19ef5a7b1c98bc172bfab5bf0d6844"}, + {file = "fonttools-4.49.0-cp38-cp38-win_amd64.whl", hash = "sha256:768947008b4dc552d02772e5ebd49e71430a466e2373008ce905f953afea755a"}, + {file = "fonttools-4.49.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:08877e355d3dde1c11973bb58d4acad1981e6d1140711230a4bfb40b2b937ccc"}, + {file = "fonttools-4.49.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fdb54b076f25d6b0f0298dc706acee5052de20c83530fa165b60d1f2e9cbe3cb"}, + {file = "fonttools-4.49.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0af65c720520710cc01c293f9c70bd69684365c6015cc3671db2b7d807fe51f2"}, + {file = "fonttools-4.49.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f255ce8ed7556658f6d23f6afd22a6d9bbc3edb9b96c96682124dc487e1bf42"}, + {file = "fonttools-4.49.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d00af0884c0e65f60dfaf9340e26658836b935052fdd0439952ae42e44fdd2be"}, + {file = "fonttools-4.49.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:263832fae27481d48dfafcc43174644b6706639661e242902ceb30553557e16c"}, + {file = "fonttools-4.49.0-cp39-cp39-win32.whl", hash = "sha256:0404faea044577a01bb82d47a8fa4bc7a54067fa7e324785dd65d200d6dd1133"}, + {file = "fonttools-4.49.0-cp39-cp39-win_amd64.whl", hash = "sha256:b050d362df50fc6e38ae3954d8c29bf2da52be384649ee8245fdb5186b620836"}, + {file = "fonttools-4.49.0-py3-none-any.whl", hash = "sha256:af281525e5dd7fa0b39fb1667b8d5ca0e2a9079967e14c4bfe90fd1cd13e0f18"}, + {file = "fonttools-4.49.0.tar.gz", hash = "sha256:ebf46e7f01b7af7861310417d7c49591a85d99146fc23a5ba82fdb28af156321"}, +] + +[package.extras] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] graphite = ["lz4 (>=1.7.4.2)"] -interpolatable = ["munkres", "scipy"] -lxml = ["lxml (>=4.0,<5)"] +interpolatable = ["munkres", "pycairo", "scipy"] +lxml = ["lxml (>=4.0)"] pathops = ["skia-pathops (>=0.5.0)"] plot = ["matplotlib"] repacker = ["uharfbuzz (>=0.23.0)"] symfont = ["sympy"] type1 = ["xattr"] ufo = ["fs (>=2.2.0,<3)"] -unicode = ["unicodedata2 (>=15.0.0)"] +unicode = ["unicodedata2 (>=15.1.0)"] woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] [[package]] @@ -819,6 +827,62 @@ files = [ {file = "fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f"}, ] +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "httpcore" +version = "1.0.3" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, + {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<0.24.0)"] + +[[package]] +name = "httpx" +version = "0.26.0" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, + {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + [[package]] name = "identify" version = "2.5.31" @@ -1284,13 +1348,13 @@ test = ["click", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (>=0.19.0)", "p [[package]] name = "jupyter-lsp" -version = "2.2.0" +version = "2.2.2" description = "Multi-Language Server WebSocket proxy for Jupyter Notebook/Lab server" optional = false python-versions = ">=3.8" files = [ - {file = "jupyter-lsp-2.2.0.tar.gz", hash = "sha256:8ebbcb533adb41e5d635eb8fe82956b0aafbf0fd443b6c4bfa906edeeb8635a1"}, - {file = "jupyter_lsp-2.2.0-py3-none-any.whl", hash = "sha256:9e06b8b4f7dd50300b70dd1a78c0c3b0c3d8fa68e0f2d8a5d1fbab62072aca3f"}, + {file = "jupyter-lsp-2.2.2.tar.gz", hash = "sha256:256d24620542ae4bba04a50fc1f6ffe208093a07d8e697fea0a8d1b8ca1b7e5b"}, + {file = "jupyter_lsp-2.2.2-py3-none-any.whl", hash = "sha256:3b95229e4168355a8c91928057c1621ac3510ba98b2a925e82ebd77f078b1aa5"}, ] [package.dependencies] @@ -1353,17 +1417,18 @@ test = ["coverage", "jupyter-server (>=2.0.0)", "pytest (>=7.0)", "pytest-cov", [[package]] name = "jupyterlab" -version = "4.0.6" +version = "4.1.2" description = "JupyterLab computational environment" optional = false python-versions = ">=3.8" files = [ - {file = "jupyterlab-4.0.6-py3-none-any.whl", hash = "sha256:7d9dacad1e3f30fe4d6d4efc97fda25fbb5012012b8f27cc03a2283abcdee708"}, - {file = "jupyterlab-4.0.6.tar.gz", hash = "sha256:6c43ae5a6a1fd2fdfafcb3454004958bde6da76331abb44cffc6f9e436b19ba1"}, + {file = "jupyterlab-4.1.2-py3-none-any.whl", hash = "sha256:aa88193f03cf4d3555f6712f04d74112b5eb85edd7d222c588c7603a26d33c5b"}, + {file = "jupyterlab-4.1.2.tar.gz", hash = "sha256:5d6348b3ed4085181499f621b7dfb6eb0b1f57f3586857aadfc8e3bf4c4885f9"}, ] [package.dependencies] async-lru = ">=1.0.0" +httpx = ">=0.25.0" ipykernel = "*" jinja2 = ">=3.0.3" jupyter-core = "*" @@ -1377,9 +1442,9 @@ tornado = ">=6.2.0" traitlets = "*" [package.extras] -dev = ["black[jupyter] (==23.7.0)", "build", "bump2version", "coverage", "hatch", "pre-commit", "pytest-cov", "ruff (==0.0.286)"] -docs = ["jsx-lexer", "myst-parser", "pydata-sphinx-theme (>=0.13.0)", "pytest", "pytest-check-links", "pytest-tornasync", "sphinx (>=1.8,<7.2.0)", "sphinx-copybutton"] -docs-screenshots = ["altair (==5.0.1)", "ipython (==8.14.0)", "ipywidgets (==8.0.6)", "jupyterlab-geojson (==3.4.0)", "jupyterlab-language-pack-zh-cn (==4.0.post0)", "matplotlib (==3.7.1)", "nbconvert (>=7.0.0)", "pandas (==2.0.2)", "scipy (==1.10.1)", "vega-datasets (==0.9.0)"] +dev = ["build", "bump2version", "coverage", "hatch", "pre-commit", "pytest-cov", "ruff (==0.2.0)"] +docs = ["jsx-lexer", "myst-parser", "pydata-sphinx-theme (>=0.13.0)", "pytest", "pytest-check-links", "pytest-jupyter", "sphinx (>=1.8,<7.3.0)", "sphinx-copybutton"] +docs-screenshots = ["altair (==5.2.0)", "ipython (==8.16.1)", "ipywidgets (==8.1.1)", "jupyterlab-geojson (==3.4.0)", "jupyterlab-language-pack-zh-cn (==4.0.post6)", "matplotlib (==3.8.2)", "nbconvert (>=7.0.0)", "pandas (==2.2.0)", "scipy (==1.12.0)", "vega-datasets (==0.9.0)"] test = ["coverage", "pytest (>=7.0)", "pytest-check-links (>=0.7)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter (>=0.5.3)", "pytest-timeout", "pytest-tornasync", "requests", "requests-cache", "virtualenv"] [[package]] @@ -1805,18 +1870,18 @@ setuptools = "*" [[package]] name = "notebook" -version = "7.0.4" +version = "7.1.0" description = "Jupyter Notebook - A web-based notebook environment for interactive computing" optional = false python-versions = ">=3.8" files = [ - {file = "notebook-7.0.4-py3-none-any.whl", hash = "sha256:ee738414ac01773c1ad6834cf76cc6f1ce140ac8197fd13b3e2d44d89e257f72"}, - {file = "notebook-7.0.4.tar.gz", hash = "sha256:0c1b458f72ce8774445c8ef9ed2492bd0b9ce9605ac996e2b066114f69795e71"}, + {file = "notebook-7.1.0-py3-none-any.whl", hash = "sha256:a8fa4ccb5e5fe220f29d9900337efd7752bc6f2efe004d6f320db01f7743adc9"}, + {file = "notebook-7.1.0.tar.gz", hash = "sha256:99caf01ff166b1cc86355c9b37c1ba9bf566c1d7fc4ab57bb6f8f24e36c4260e"}, ] [package.dependencies] jupyter-server = ">=2.4.0,<3" -jupyterlab = ">=4.0.2,<5" +jupyterlab = ">=4.1.1,<4.2" jupyterlab-server = ">=2.22.1,<3" notebook-shim = ">=0.2,<0.3" tornado = ">=6.2.0" @@ -2026,70 +2091,88 @@ files = [ [[package]] name = "pillow" -version = "10.0.1" +version = "10.2.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.8" files = [ - {file = "Pillow-10.0.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:8f06be50669087250f319b706decf69ca71fdecd829091a37cc89398ca4dc17a"}, - {file = "Pillow-10.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:50bd5f1ebafe9362ad622072a1d2f5850ecfa44303531ff14353a4059113b12d"}, - {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6a90167bcca1216606223a05e2cf991bb25b14695c518bc65639463d7db722d"}, - {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f11c9102c56ffb9ca87134bd025a43d2aba3f1155f508eff88f694b33a9c6d19"}, - {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:186f7e04248103482ea6354af6d5bcedb62941ee08f7f788a1c7707bc720c66f"}, - {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:0462b1496505a3462d0f35dc1c4d7b54069747d65d00ef48e736acda2c8cbdff"}, - {file = "Pillow-10.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d889b53ae2f030f756e61a7bff13684dcd77e9af8b10c6048fb2c559d6ed6eaf"}, - {file = "Pillow-10.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:552912dbca585b74d75279a7570dd29fa43b6d93594abb494ebb31ac19ace6bd"}, - {file = "Pillow-10.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:787bb0169d2385a798888e1122c980c6eff26bf941a8ea79747d35d8f9210ca0"}, - {file = "Pillow-10.0.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:fd2a5403a75b54661182b75ec6132437a181209b901446ee5724b589af8edef1"}, - {file = "Pillow-10.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2d7e91b4379f7a76b31c2dda84ab9e20c6220488e50f7822e59dac36b0cd92b1"}, - {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19e9adb3f22d4c416e7cd79b01375b17159d6990003633ff1d8377e21b7f1b21"}, - {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93139acd8109edcdeffd85e3af8ae7d88b258b3a1e13a038f542b79b6d255c54"}, - {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:92a23b0431941a33242b1f0ce6c88a952e09feeea9af4e8be48236a68ffe2205"}, - {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:cbe68deb8580462ca0d9eb56a81912f59eb4542e1ef8f987405e35a0179f4ea2"}, - {file = "Pillow-10.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:522ff4ac3aaf839242c6f4e5b406634bfea002469656ae8358644fc6c4856a3b"}, - {file = "Pillow-10.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:84efb46e8d881bb06b35d1d541aa87f574b58e87f781cbba8d200daa835b42e1"}, - {file = "Pillow-10.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:898f1d306298ff40dc1b9ca24824f0488f6f039bc0e25cfb549d3195ffa17088"}, - {file = "Pillow-10.0.1-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:bcf1207e2f2385a576832af02702de104be71301c2696d0012b1b93fe34aaa5b"}, - {file = "Pillow-10.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5d6c9049c6274c1bb565021367431ad04481ebb54872edecfcd6088d27edd6ed"}, - {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28444cb6ad49726127d6b340217f0627abc8732f1194fd5352dec5e6a0105635"}, - {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de596695a75496deb3b499c8c4f8e60376e0516e1a774e7bc046f0f48cd620ad"}, - {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:2872f2d7846cf39b3dbff64bc1104cc48c76145854256451d33c5faa55c04d1a"}, - {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4ce90f8a24e1c15465048959f1e94309dfef93af272633e8f37361b824532e91"}, - {file = "Pillow-10.0.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ee7810cf7c83fa227ba9125de6084e5e8b08c59038a7b2c9045ef4dde61663b4"}, - {file = "Pillow-10.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b1be1c872b9b5fcc229adeadbeb51422a9633abd847c0ff87dc4ef9bb184ae08"}, - {file = "Pillow-10.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:98533fd7fa764e5f85eebe56c8e4094db912ccbe6fbf3a58778d543cadd0db08"}, - {file = "Pillow-10.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:764d2c0daf9c4d40ad12fbc0abd5da3af7f8aa11daf87e4fa1b834000f4b6b0a"}, - {file = "Pillow-10.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fcb59711009b0168d6ee0bd8fb5eb259c4ab1717b2f538bbf36bacf207ef7a68"}, - {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:697a06bdcedd473b35e50a7e7506b1d8ceb832dc238a336bd6f4f5aa91a4b500"}, - {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f665d1e6474af9f9da5e86c2a3a2d2d6204e04d5af9c06b9d42afa6ebde3f21"}, - {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:2fa6dd2661838c66f1a5473f3b49ab610c98a128fc08afbe81b91a1f0bf8c51d"}, - {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:3a04359f308ebee571a3127fdb1bd01f88ba6f6fb6d087f8dd2e0d9bff43f2a7"}, - {file = "Pillow-10.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:723bd25051454cea9990203405fa6b74e043ea76d4968166dfd2569b0210886a"}, - {file = "Pillow-10.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:71671503e3015da1b50bd18951e2f9daf5b6ffe36d16f1eb2c45711a301521a7"}, - {file = "Pillow-10.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:44e7e4587392953e5e251190a964675f61e4dae88d1e6edbe9f36d6243547ff3"}, - {file = "Pillow-10.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:3855447d98cced8670aaa63683808df905e956f00348732448b5a6df67ee5849"}, - {file = "Pillow-10.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ed2d9c0704f2dc4fa980b99d565c0c9a543fe5101c25b3d60488b8ba80f0cce1"}, - {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5bb289bb835f9fe1a1e9300d011eef4d69661bb9b34d5e196e5e82c4cb09b37"}, - {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a0d3e54ab1df9df51b914b2233cf779a5a10dfd1ce339d0421748232cea9876"}, - {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:2cc6b86ece42a11f16f55fe8903595eff2b25e0358dec635d0a701ac9586588f"}, - {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:ca26ba5767888c84bf5a0c1a32f069e8204ce8c21d00a49c90dabeba00ce0145"}, - {file = "Pillow-10.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f0b4b06da13275bc02adfeb82643c4a6385bd08d26f03068c2796f60d125f6f2"}, - {file = "Pillow-10.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bc2e3069569ea9dbe88d6b8ea38f439a6aad8f6e7a6283a38edf61ddefb3a9bf"}, - {file = "Pillow-10.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:8b451d6ead6e3500b6ce5c7916a43d8d8d25ad74b9102a629baccc0808c54971"}, - {file = "Pillow-10.0.1-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:32bec7423cdf25c9038fef614a853c9d25c07590e1a870ed471f47fb80b244db"}, - {file = "Pillow-10.0.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7cf63d2c6928b51d35dfdbda6f2c1fddbe51a6bc4a9d4ee6ea0e11670dd981e"}, - {file = "Pillow-10.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f6d3d4c905e26354e8f9d82548475c46d8e0889538cb0657aa9c6f0872a37aa4"}, - {file = "Pillow-10.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:847e8d1017c741c735d3cd1883fa7b03ded4f825a6e5fcb9378fd813edee995f"}, - {file = "Pillow-10.0.1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7f771e7219ff04b79e231d099c0a28ed83aa82af91fd5fa9fdb28f5b8d5addaf"}, - {file = "Pillow-10.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:459307cacdd4138edee3875bbe22a2492519e060660eaf378ba3b405d1c66317"}, - {file = "Pillow-10.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b059ac2c4c7a97daafa7dc850b43b2d3667def858a4f112d1aa082e5c3d6cf7d"}, - {file = "Pillow-10.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6caf3cd38449ec3cd8a68b375e0c6fe4b6fd04edb6c9766b55ef84a6e8ddf2d"}, - {file = "Pillow-10.0.1.tar.gz", hash = "sha256:d72967b06be9300fed5cfbc8b5bafceec48bf7cdc7dab66b1d2549035287191d"}, + {file = "pillow-10.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:7823bdd049099efa16e4246bdf15e5a13dbb18a51b68fa06d6c1d4d8b99a796e"}, + {file = "pillow-10.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:83b2021f2ade7d1ed556bc50a399127d7fb245e725aa0113ebd05cfe88aaf588"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fad5ff2f13d69b7e74ce5b4ecd12cc0ec530fcee76356cac6742785ff71c452"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2b52b37dad6d9ec64e653637a096905b258d2fc2b984c41ae7d08b938a67e4"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:47c0995fc4e7f79b5cfcab1fc437ff2890b770440f7696a3ba065ee0fd496563"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:322bdf3c9b556e9ffb18f93462e5f749d3444ce081290352c6070d014c93feb2"}, + {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:51f1a1bffc50e2e9492e87d8e09a17c5eea8409cda8d3f277eb6edc82813c17c"}, + {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69ffdd6120a4737710a9eee73e1d2e37db89b620f702754b8f6e62594471dee0"}, + {file = "pillow-10.2.0-cp310-cp310-win32.whl", hash = "sha256:c6dafac9e0f2b3c78df97e79af707cdc5ef8e88208d686a4847bab8266870023"}, + {file = "pillow-10.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:aebb6044806f2e16ecc07b2a2637ee1ef67a11840a66752751714a0d924adf72"}, + {file = "pillow-10.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:7049e301399273a0136ff39b84c3678e314f2158f50f517bc50285fb5ec847ad"}, + {file = "pillow-10.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35bb52c37f256f662abdfa49d2dfa6ce5d93281d323a9af377a120e89a9eafb5"}, + {file = "pillow-10.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c23f307202661071d94b5e384e1e1dc7dfb972a28a2310e4ee16103e66ddb67"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:773efe0603db30c281521a7c0214cad7836c03b8ccff897beae9b47c0b657d61"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11fa2e5984b949b0dd6d7a94d967743d87c577ff0b83392f17cb3990d0d2fd6e"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:716d30ed977be8b37d3ef185fecb9e5a1d62d110dfbdcd1e2a122ab46fddb03f"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a086c2af425c5f62a65e12fbf385f7c9fcb8f107d0849dba5839461a129cf311"}, + {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c8de2789052ed501dd829e9cae8d3dcce7acb4777ea4a479c14521c942d395b1"}, + {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:609448742444d9290fd687940ac0b57fb35e6fd92bdb65386e08e99af60bf757"}, + {file = "pillow-10.2.0-cp311-cp311-win32.whl", hash = "sha256:823ef7a27cf86df6597fa0671066c1b596f69eba53efa3d1e1cb8b30f3533068"}, + {file = "pillow-10.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1da3b2703afd040cf65ec97efea81cfba59cdbed9c11d8efc5ab09df9509fc56"}, + {file = "pillow-10.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:edca80cbfb2b68d7b56930b84a0e45ae1694aeba0541f798e908a49d66b837f1"}, + {file = "pillow-10.2.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:1b5e1b74d1bd1b78bc3477528919414874748dd363e6272efd5abf7654e68bef"}, + {file = "pillow-10.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0eae2073305f451d8ecacb5474997c08569fb4eb4ac231ffa4ad7d342fdc25ac"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7c2286c23cd350b80d2fc9d424fc797575fb16f854b831d16fd47ceec078f2c"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e23412b5c41e58cec602f1135c57dfcf15482013ce6e5f093a86db69646a5aa"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:52a50aa3fb3acb9cf7213573ef55d31d6eca37f5709c69e6858fe3bc04a5c2a2"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:127cee571038f252a552760076407f9cff79761c3d436a12af6000cd182a9d04"}, + {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8d12251f02d69d8310b046e82572ed486685c38f02176bd08baf216746eb947f"}, + {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54f1852cd531aa981bc0965b7d609f5f6cc8ce8c41b1139f6ed6b3c54ab82bfb"}, + {file = "pillow-10.2.0-cp312-cp312-win32.whl", hash = "sha256:257d8788df5ca62c980314053197f4d46eefedf4e6175bc9412f14412ec4ea2f"}, + {file = "pillow-10.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:154e939c5f0053a383de4fd3d3da48d9427a7e985f58af8e94d0b3c9fcfcf4f9"}, + {file = "pillow-10.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:f379abd2f1e3dddb2b61bc67977a6b5a0a3f7485538bcc6f39ec76163891ee48"}, + {file = "pillow-10.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8373c6c251f7ef8bda6675dd6d2b3a0fcc31edf1201266b5cf608b62a37407f9"}, + {file = "pillow-10.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:870ea1ada0899fd0b79643990809323b389d4d1d46c192f97342eeb6ee0b8483"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4b6b1e20608493548b1f32bce8cca185bf0480983890403d3b8753e44077129"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3031709084b6e7852d00479fd1d310b07d0ba82765f973b543c8af5061cf990e"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:3ff074fc97dd4e80543a3e91f69d58889baf2002b6be64347ea8cf5533188213"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:cb4c38abeef13c61d6916f264d4845fab99d7b711be96c326b84df9e3e0ff62d"}, + {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b1b3020d90c2d8e1dae29cf3ce54f8094f7938460fb5ce8bc5c01450b01fbaf6"}, + {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:170aeb00224ab3dc54230c797f8404507240dd868cf52066f66a41b33169bdbe"}, + {file = "pillow-10.2.0-cp38-cp38-win32.whl", hash = "sha256:c4225f5220f46b2fde568c74fca27ae9771536c2e29d7c04f4fb62c83275ac4e"}, + {file = "pillow-10.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:0689b5a8c5288bc0504d9fcee48f61a6a586b9b98514d7d29b840143d6734f39"}, + {file = "pillow-10.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b792a349405fbc0163190fde0dc7b3fef3c9268292586cf5645598b48e63dc67"}, + {file = "pillow-10.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c570f24be1e468e3f0ce7ef56a89a60f0e05b30a3669a459e419c6eac2c35364"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8ecd059fdaf60c1963c58ceb8997b32e9dc1b911f5da5307aab614f1ce5c2fb"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c365fd1703040de1ec284b176d6af5abe21b427cb3a5ff68e0759e1e313a5e7e"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:70c61d4c475835a19b3a5aa42492409878bbca7438554a1f89d20d58a7c75c01"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b6f491cdf80ae540738859d9766783e3b3c8e5bd37f5dfa0b76abdecc5081f13"}, + {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d189550615b4948f45252d7f005e53c2040cea1af5b60d6f79491a6e147eef7"}, + {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:49d9ba1ed0ef3e061088cd1e7538a0759aab559e2e0a80a36f9fd9d8c0c21591"}, + {file = "pillow-10.2.0-cp39-cp39-win32.whl", hash = "sha256:babf5acfede515f176833ed6028754cbcd0d206f7f614ea3447d67c33be12516"}, + {file = "pillow-10.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:0304004f8067386b477d20a518b50f3fa658a28d44e4116970abfcd94fac34a8"}, + {file = "pillow-10.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:0fb3e7fc88a14eacd303e90481ad983fd5b69c761e9e6ef94c983f91025da869"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:322209c642aabdd6207517e9739c704dc9f9db943015535783239022002f054a"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eedd52442c0a5ff4f887fab0c1c0bb164d8635b32c894bc1faf4c618dd89df2"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb28c753fd5eb3dd859b4ee95de66cc62af91bcff5db5f2571d32a520baf1f04"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33870dc4653c5017bf4c8873e5488d8f8d5f8935e2f1fb9a2208c47cdd66efd2"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3c31822339516fb3c82d03f30e22b1d038da87ef27b6a78c9549888f8ceda39a"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a2b56ba36e05f973d450582fb015594aaa78834fefe8dfb8fcd79b93e64ba4c6"}, + {file = "pillow-10.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d8e6aeb9201e655354b3ad049cb77d19813ad4ece0df1249d3c793de3774f8c7"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:2247178effb34a77c11c0e8ac355c7a741ceca0a732b27bf11e747bbc950722f"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15587643b9e5eb26c48e49a7b33659790d28f190fc514a322d55da2fb5c2950e"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753cd8f2086b2b80180d9b3010dd4ed147efc167c90d3bf593fe2af21265e5a5"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7c8f97e8e7a9009bcacbe3766a36175056c12f9a44e6e6f2d5caad06dcfbf03b"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d1b35bcd6c5543b9cb547dee3150c93008f8dd0f1fef78fc0cd2b141c5baf58a"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe4c15f6c9285dc54ce6553a3ce908ed37c8f3825b5a51a15c91442bb955b868"}, + {file = "pillow-10.2.0.tar.gz", hash = "sha256:e87f0b2c78157e12d7686b27d63c070fd65d994e8ddae6f328e0dcf4a0cd007e"}, ] [package.extras] docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +typing = ["typing-extensions"] +xmp = ["defusedxml"] [[package]] name = "platformdirs" From a4df14dbbb96379edc05c93e95751ce111c98633 Mon Sep 17 00:00:00 2001 From: Benjamin Wingfield Date: Wed, 21 Feb 2024 12:34:22 +0000 Subject: [PATCH 2/2] Update aggregation to match ancestry output (#79) * match ancestry aggregation output * bump version * fix column name (accession -> PGS) * fix column name * add aggregate tests * fix not respecting outdir * read new version of pgs * drop onlySUM parameter * Make sure it only reads SUM and provides the correct column names back Signed-off-by: smlmbrt * drop deprecated parameter --------- Signed-off-by: smlmbrt Co-authored-by: smlmbrt --- .../aggregate/aggregate_scores.py | 114 +++++-- .../ancestry/ancestry_analysis.py | 281 +++++++++++++----- pgscatalog_utils/ancestry/read.py | 72 +++-- pyproject.toml | 2 +- tests/data/cineca_22_additive_0.sscore.zst | Bin 0 -> 33464 bytes tests/test_aggregate.py | 22 ++ 6 files changed, 346 insertions(+), 145 deletions(-) create mode 100644 tests/data/cineca_22_additive_0.sscore.zst create mode 100644 tests/test_aggregate.py diff --git a/pgscatalog_utils/aggregate/aggregate_scores.py b/pgscatalog_utils/aggregate/aggregate_scores.py index d57943d..d30a783 100644 --- a/pgscatalog_utils/aggregate/aggregate_scores.py +++ b/pgscatalog_utils/aggregate/aggregate_scores.py @@ -1,5 +1,6 @@ import argparse import logging +import pathlib import textwrap import pandas as pd @@ -16,14 +17,14 @@ def aggregate_scores(): if args.split: logger.debug("Splitting aggregated scores by sampleset") - for sampleset, group in df.groupby('sampleset'): - fout = f"{sampleset}_pgs.txt.gz" + for sampleset, group in df.groupby("sampleset"): + fout = pathlib.Path(args.outdir) / f"{sampleset}_pgs.txt.gz" logger.debug(f"Compressing sampleset {sampleset}, writing to {fout}") - group.to_csv(fout, sep='\t', compression='gzip') + group.to_csv(fout, sep="\t", compression="gzip") else: - fout = "aggregated_scores.txt.gz" + fout = pathlib.Path(args.outdir) / "aggregated_scores.txt.gz" logger.info(f"Compressing all samplesets and writing combined scores to {fout}") - df.to_csv(fout, sep='\t', compression='gzip') + df.to_csv(fout, sep="\t", compression="gzip") def aggregate(scorefiles: list[str]): @@ -33,11 +34,13 @@ def aggregate(scorefiles: list[str]): for i, path in enumerate(scorefiles): logger.debug(f"Reading {path}") # pandas can automatically detect zst compression, neat! - df = (pd.read_table(path, converters={"#IID": str}, header=0) - .assign(sampleset=path.split('_')[0]) - .set_index(['sampleset', '#IID'])) + df = ( + pd.read_table(path, converters={"#IID": str}, header=0) + .assign(sampleset=path.split("_")[0]) + .set_index(["sampleset", "#IID"]) + ) - df.index.names = ['sampleset', 'IID'] + df.index.names = ["sampleset", "IID"] # Subset to aggregatable columns df = df[_select_agg_cols(df.columns)] @@ -45,31 +48,57 @@ def aggregate(scorefiles: list[str]): # Combine DFs if i == 0: - logger.debug('Initialising combined DF') + logger.debug("Initialising combined DF") combined = df.copy() else: - logger.debug('Adding to combined DF') + logger.debug("Adding to combined DF") combined = combined.add(df, fill_value=0) - assert all([x in combined.columns for x in aggcols]), "All Aggregatable Columns are present in the final DF" + assert all( + [x in combined.columns for x in aggcols] + ), "All Aggregatable Columns are present in the final DF" - return combined.pipe(_calculate_average) + sum_df, avg_df = combined.pipe(_calculate_average) + # need to melt sum and avg separately to give correct value_Name to melt + dfs = [_melt(x, y) for x, y in zip([sum_df, avg_df], ["SUM", "AVG"])] + # add melted average back + combined = pd.concat([dfs[0], dfs[1]["AVG"]], axis=1) + return combined[["PGS", "SUM", "DENOM", "AVG"]] + + +def _melt(df, value_name): + df = df.melt( + id_vars=["DENOM"], + value_name=value_name, + var_name="PGS", + ignore_index=False, + ) + df["PGS"] = df["PGS"].str.replace(f"_{value_name}", "") + return df def _calculate_average(combined: pd.DataFrame): logger.debug("Averaging data") - avgs = combined.loc[:, combined.columns.str.endswith('_SUM')].divide(combined['DENOM'], axis=0) - avgs.columns = avgs.columns.str.replace('_SUM', '_AVG') - return pd.concat([combined, avgs], axis=1) + avgs = combined.loc[:, combined.columns.str.endswith("_SUM")].divide( + combined["DENOM"], axis=0 + ) + avgs.columns = avgs.columns.str.replace("_SUM", "_AVG") + avgs["DENOM"] = combined["DENOM"] + return combined, avgs def _select_agg_cols(cols): - keep_cols = ['DENOM'] - return [x for x in cols if (x.endswith('_SUM') and (x != 'NAMED_ALLELE_DOSAGE_SUM')) or (x in keep_cols)] + keep_cols = ["DENOM"] + return [ + x + for x in cols + if (x.endswith("_SUM") and (x != "NAMED_ALLELE_DOSAGE_SUM")) or (x in keep_cols) + ] def _description_text() -> str: - return textwrap.dedent(''' + return textwrap.dedent( + """ Aggregate plink .sscore files into a combined TSV table. This aggregation sums scores that were calculated from plink @@ -80,20 +109,45 @@ def _description_text() -> str: Input .sscore files can be optionally compressed with zstd or gzip. The aggregated output scores are compressed with gzip. - ''') + """ + ) def _parse_args(args=None) -> argparse.Namespace: - parser = argparse.ArgumentParser(description=_description_text(), - formatter_class=argparse.RawDescriptionHelpFormatter) - parser.add_argument('-s', '--scores', dest='scores', required=True, nargs='+', - help=' List of scorefile paths. Use a wildcard (*) to select multiple files.') - parser.add_argument('-o', '--outdir', dest='outdir', required=True, - default='scores/', help=' Output directory to store downloaded files') - parser.add_argument('--split', dest='split', required=False, action=argparse.BooleanOptionalAction, - help=' Make one aggregated file per sampleset') - parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', - help=' Extra logging information') + parser = argparse.ArgumentParser( + description=_description_text(), + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument( + "-s", + "--scores", + dest="scores", + required=True, + nargs="+", + help=" List of scorefile paths. Use a wildcard (*) to select multiple files.", + ) + parser.add_argument( + "-o", + "--outdir", + dest="outdir", + required=True, + default="scores/", + help=" Output directory to store downloaded files", + ) + parser.add_argument( + "--split", + dest="split", + required=False, + action=argparse.BooleanOptionalAction, + help=" Make one aggregated file per sampleset", + ) + parser.add_argument( + "-v", + "--verbose", + dest="verbose", + action="store_true", + help=" Extra logging information", + ) return parser.parse_args(args) diff --git a/pgscatalog_utils/ancestry/ancestry_analysis.py b/pgscatalog_utils/ancestry/ancestry_analysis.py index 2371c91..a7dbd8e 100644 --- a/pgscatalog_utils/ancestry/ancestry_analysis.py +++ b/pgscatalog_utils/ancestry/ancestry_analysis.py @@ -8,8 +8,14 @@ import pgscatalog_utils.config as config from pgscatalog_utils.ancestry.read import read_pcs, read_pgs, extract_ref_psam_cols -from pgscatalog_utils.ancestry.tools import compare_ancestry, comparison_method_threshold, choose_pval_threshold, \ - pgs_adjust, normalization_methods, write_model +from pgscatalog_utils.ancestry.tools import ( + compare_ancestry, + comparison_method_threshold, + choose_pval_threshold, + pgs_adjust, + normalization_methods, + write_model, +) logger = logging.getLogger(__name__) @@ -20,53 +26,76 @@ def ancestry_analysis(): config.OUTDIR = args.outdir # Load PCA data - maxPCs = max([10, args.nPCs_popcomp, args.nPCs_normalization]) # save memory by not using all PCs + maxPCs = max( + [10, args.nPCs_popcomp, args.nPCs_normalization] + ) # save memory by not using all PCs loc_ref_pcs = args.ref_pcs - reference_df = read_pcs(loc_pcs=loc_ref_pcs, dataset=args.d_ref, - loc_related_ids=args.ref_related, nPCs=maxPCs) + reference_df = read_pcs( + loc_pcs=loc_ref_pcs, + dataset=args.d_ref, + loc_related_ids=args.ref_related, + nPCs=maxPCs, + ) loc_ref_psam = args.psam - reference_df = extract_ref_psam_cols(loc_ref_psam, args.d_ref, reference_df, keepcols=[args.ref_label]) - assert reference_df.shape[0] > 100, "Error: too few reference panel samples. This is an arbitrary threshold " \ - "for input QC; however, it is inadvisable to run this analysis with limited " \ - "reference panel diversity as empirical percentiles are calculated." + reference_df = extract_ref_psam_cols( + loc_ref_psam, args.d_ref, reference_df, keepcols=[args.ref_label] + ) + assert reference_df.shape[0] > 100, ( + "Error: too few reference panel samples. This is an arbitrary threshold " + "for input QC; however, it is inadvisable to run this analysis with limited " + "reference panel diversity as empirical percentiles are calculated." + ) loc_target_sscores = args.target_pcs target_df = read_pcs(loc_pcs=loc_target_sscores, dataset=args.d_target, nPCs=maxPCs) assert target_df.shape[0] >= 1, "Error: NO target samples found in PCs file." # Load PGS data & merge with PCA data - pgs = read_pgs(args.scorefile, onlySUM=True) + pgs = read_pgs(args.scorefile) scorecols = list(pgs.columns) ## There should be perfect target sample overlap - assert all([x in pgs.loc['reference'].index for x in reference_df.index.get_level_values(1)]), \ - "Error: PGS data missing for reference samples with PCA data." + assert all( + [ + x in pgs.loc["reference"].index + for x in reference_df.index.get_level_values(1) + ] + ), "Error: PGS data missing for reference samples with PCA data." reference_df = pd.merge(reference_df, pgs, left_index=True, right_index=True) - assert all([x in pgs.loc[args.d_target].index for x in target_df.index.get_level_values(1)]), \ - "Error: PGS data missing for reference samples with PCA data." + assert all( + [x in pgs.loc[args.d_target].index for x in target_df.index.get_level_values(1)] + ), "Error: PGS data missing for reference samples with PCA data." target_df = pd.merge(target_df, pgs, left_index=True, right_index=True) del pgs # clear raw PGS from memory # Compare target sample ancestry/PCs to reference panel assignment_threshold_p = choose_pval_threshold(args) - ancestry_ref, ancestry_target, compare_info = compare_ancestry(ref_df=reference_df, - ref_pop_col=args.ref_label, ref_train_col='Unrelated', - target_df=target_df, - n_pcs=args.nPCs_popcomp, - method=args.method_compare, - p_threshold=assignment_threshold_p) + ancestry_ref, ancestry_target, compare_info = compare_ancestry( + ref_df=reference_df, + ref_pop_col=args.ref_label, + ref_train_col="Unrelated", + target_df=target_df, + n_pcs=args.nPCs_popcomp, + method=args.method_compare, + p_threshold=assignment_threshold_p, + ) reference_df = pd.concat([reference_df, ancestry_ref], axis=1) target_df = pd.concat([target_df, ancestry_target], axis=1) del ancestry_ref, ancestry_target # Adjust PGS values - adjpgs_ref, adjpgs_target, adjpgs_models = pgs_adjust(reference_df, target_df, scorecols, - args.ref_label, 'MostSimilarPop', - use_method=args.method_normalization, - ref_train_col='Unrelated', - n_pcs=args.nPCs_normalization) + adjpgs_ref, adjpgs_target, adjpgs_models = pgs_adjust( + reference_df, + target_df, + scorecols, + args.ref_label, + "MostSimilarPop", + use_method=args.method_normalization, + ref_train_col="Unrelated", + n_pcs=args.nPCs_normalization, + ) adjpgs = pd.concat([adjpgs_ref, adjpgs_target], axis=0) del adjpgs_ref, adjpgs_target @@ -74,86 +103,176 @@ def ancestry_analysis(): dout = os.path.abspath(config.OUTDIR) if os.path.isdir(dout) is False: os.mkdir(dout) - reference_df['REFERENCE'] = True - target_df['REFERENCE'] = False + reference_df["REFERENCE"] = True + target_df["REFERENCE"] = False final_df = pd.concat([target_df, reference_df], axis=0) del reference_df, target_df # Write Models - write_model({'pgs': adjpgs_models, 'compare_pcs': compare_info}, os.path.join(dout, f"{args.d_target}_info.json.gz")) + write_model( + {"pgs": adjpgs_models, "compare_pcs": compare_info}, + os.path.join(dout, f"{args.d_target}_info.json.gz"), + ) # Melt & write PGS # Currently each PGS will have it's own row... but it might be more optimal for each normalization method # to be on separate rows? My logic is that you might want to check correaltion between methods and it is easiest # in this format. loc_pgs_out = os.path.join(dout, f"{args.d_target}_pgs.txt.gz") - with gzip.open(loc_pgs_out, 'wt') as outf: - logger.debug('Writing adjusted PGS values (long format) to: {}'.format(loc_pgs_out)) + with gzip.open(loc_pgs_out, "wt") as outf: + logger.debug( + "Writing adjusted PGS values (long format) to: {}".format(loc_pgs_out) + ) for i, pgs_id in enumerate(scorecols): - df_pgs = adjpgs.loc[:, adjpgs.columns.str.endswith(pgs_id)].melt(ignore_index=False) # filter to 1 PGS - df_pgs[['method', 'PGS']] = df_pgs.variable.str.split("|", expand=True) - df_pgs = df_pgs.drop('variable', axis=1).reset_index().pivot(index=['sampleset', 'IID', 'PGS'], - columns='method', values='value') + df_pgs = adjpgs.loc[:, adjpgs.columns.str.endswith(pgs_id)].melt( + ignore_index=False + ) # filter to 1 PGS + df_pgs[["method", "PGS"]] = df_pgs.variable.str.split("|", expand=True) + df_pgs = ( + df_pgs.drop("variable", axis=1) + .reset_index() + .pivot( + index=["sampleset", "IID", "PGS"], columns="method", values="value" + ) + ) if i == 0: - logger.debug('{}/{}: Writing {}'.format(i+1, len(scorecols), pgs_id)) + logger.debug("{}/{}: Writing {}".format(i + 1, len(scorecols), pgs_id)) colorder = list(df_pgs.columns) # to ensure sort order - df_pgs.to_csv(outf, sep='\t') + df_pgs.to_csv(outf, sep="\t") else: - logger.debug('{}/{}: Appending {}'.format(i+1, len(scorecols), pgs_id)) - df_pgs[colorder].to_csv(outf, sep='\t', header=False) + logger.debug( + "{}/{}: Appending {}".format(i + 1, len(scorecols), pgs_id) + ) + df_pgs[colorder].to_csv(outf, sep="\t", header=False) # Write results of PCA & population similarity loc_popsim_out = os.path.join(dout, f"{args.d_target}_popsimilarity.txt.gz") - logger.debug('Writing PCA and popsim results to: {}'.format(loc_popsim_out)) - final_df.drop(scorecols, axis=1).to_csv(loc_popsim_out, sep='\t') + logger.debug("Writing PCA and popsim results to: {}".format(loc_popsim_out)) + final_df.drop(scorecols, axis=1).to_csv(loc_popsim_out, sep="\t") logger.info("Finished ancestry analysis") def _description_text() -> str: - return textwrap.dedent('Program to analyze ancestry outputs of the pgscatalog/pgsc_calc pipeline. Current inputs: ' - '\n - PCA projections from reference and target datasets (*.pcs)' - '\n - calculated polygenic scores (e.g. aggregated_scores.txt.gz), ' - '\n - information about related samples in the reference dataset (e.g. ' - 'deg2_hg38.king.cutoff.out.id).') + return textwrap.dedent( + "Program to analyze ancestry outputs of the pgscatalog/pgsc_calc pipeline. Current inputs: " + "\n - PCA projections from reference and target datasets (*.pcs)" + "\n - calculated polygenic scores (e.g. aggregated_scores.txt.gz), " + "\n - information about related samples in the reference dataset (e.g. " + "deg2_hg38.king.cutoff.out.id)." + ) def _parse_args(args=None): - parser = argparse.ArgumentParser(description=_description_text(), - formatter_class=argparse.RawDescriptionHelpFormatter) - parser.add_argument('-d', '--dataset', dest='d_target', required=True, - help=' Label of the TARGET genomic dataset') - parser.add_argument('-r', '--reference', dest='d_ref', required=True, - help=' Label of the REFERENCE genomic dataset') - parser.add_argument('--ref_pcs', dest='ref_pcs', required=True, nargs='+', - help=' Principal components path (output from fraposa_pgsc)') - parser.add_argument('--target_pcs', dest='target_pcs', required=True, nargs='+', - help=' Principal components path (output from fraposa_pgsc)') - parser.add_argument('--psam', dest='psam', required=True, - help=' Reference sample information file path in plink2 psam format)') - parser.add_argument('-x', '--reference_related', dest='ref_related', - help='File of related sample IDs (excluded from training ancestry assignments)') - parser.add_argument('-p', '--pop_label', dest='ref_label', default='SuperPop', - help='Population labels in REFERENCE psam to use for assignment') - parser.add_argument('-s', '--agg_scores', dest='scorefile', default='aggregated_scores.txt.gz', - help='Aggregated scores in PGS Catalog format ([sampleset, IID] indexed)') - parser.add_argument('-a', '--ancestry_method', dest='method_compare', - choices=comparison_method_threshold.keys(), default='RandomForest', - help='Method used for population/ancestry assignment') - parser.add_argument('--n_popcomp', dest='nPCs_popcomp', type=int, metavar="[1-20]", choices=range(1, 21), - default=5, - help='Number of PCs used for population comparison (default = 5)') - parser.add_argument('-t', '--pval_threshold', dest='pThreshold', type=float, - help='p-value threshold used to identify low-confidence ancestry similarities') - parser.add_argument('-n', '--normalization_method', nargs='+', dest='method_normalization', - choices=normalization_methods, default=["empirical", "mean", "mean+var"], - help='Method used for adjustment of PGS using genetic ancestry') - parser.add_argument('--n_normalization', dest='nPCs_normalization', type=int, metavar="[1-20]", - choices=range(1, 21), default=4, - help='Number of PCs used for population NORMALIZATION (default = 4)') - parser.add_argument('--outdir', dest='outdir', required=True, - help=' Output directory') - parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', - help=' Extra logging information') + parser = argparse.ArgumentParser( + description=_description_text(), + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument( + "-d", + "--dataset", + dest="d_target", + required=True, + help=" Label of the TARGET genomic dataset", + ) + parser.add_argument( + "-r", + "--reference", + dest="d_ref", + required=True, + help=" Label of the REFERENCE genomic dataset", + ) + parser.add_argument( + "--ref_pcs", + dest="ref_pcs", + required=True, + nargs="+", + help=" Principal components path (output from fraposa_pgsc)", + ) + parser.add_argument( + "--target_pcs", + dest="target_pcs", + required=True, + nargs="+", + help=" Principal components path (output from fraposa_pgsc)", + ) + parser.add_argument( + "--psam", + dest="psam", + required=True, + help=" Reference sample information file path in plink2 psam format)", + ) + parser.add_argument( + "-x", + "--reference_related", + dest="ref_related", + help="File of related sample IDs (excluded from training ancestry assignments)", + ) + parser.add_argument( + "-p", + "--pop_label", + dest="ref_label", + default="SuperPop", + help="Population labels in REFERENCE psam to use for assignment", + ) + parser.add_argument( + "-s", + "--agg_scores", + dest="scorefile", + default="aggregated_scores.txt.gz", + help="Aggregated scores in PGS Catalog format ([sampleset, IID] indexed)", + ) + parser.add_argument( + "-a", + "--ancestry_method", + dest="method_compare", + choices=comparison_method_threshold.keys(), + default="RandomForest", + help="Method used for population/ancestry assignment", + ) + parser.add_argument( + "--n_popcomp", + dest="nPCs_popcomp", + type=int, + metavar="[1-20]", + choices=range(1, 21), + default=5, + help="Number of PCs used for population comparison (default = 5)", + ) + parser.add_argument( + "-t", + "--pval_threshold", + dest="pThreshold", + type=float, + help="p-value threshold used to identify low-confidence ancestry similarities", + ) + parser.add_argument( + "-n", + "--normalization_method", + nargs="+", + dest="method_normalization", + choices=normalization_methods, + default=["empirical", "mean", "mean+var"], + help="Method used for adjustment of PGS using genetic ancestry", + ) + parser.add_argument( + "--n_normalization", + dest="nPCs_normalization", + type=int, + metavar="[1-20]", + choices=range(1, 21), + default=4, + help="Number of PCs used for population NORMALIZATION (default = 4)", + ) + parser.add_argument( + "--outdir", dest="outdir", required=True, help=" Output directory" + ) + parser.add_argument( + "-v", + "--verbose", + dest="verbose", + action="store_true", + help=" Extra logging information", + ) return parser.parse_args(args) diff --git a/pgscatalog_utils/ancestry/read.py b/pgscatalog_utils/ancestry/read.py index 8763e07..bb83273 100644 --- a/pgscatalog_utils/ancestry/read.py +++ b/pgscatalog_utils/ancestry/read.py @@ -1,12 +1,10 @@ import logging import pandas as pd -import numpy as np -import os logger = logging.getLogger(__name__) -def read_pcs(loc_pcs: list[str],dataset: str, loc_related_ids=None, nPCs=None): +def read_pcs(loc_pcs: list[str], dataset: str, loc_related_ids=None, nPCs=None): """ Read the .pc file outputs of the fraposa_pgsc projection :param loc_pcs: list of locations for .pcs files @@ -18,20 +16,20 @@ def read_pcs(loc_pcs: list[str],dataset: str, loc_related_ids=None, nPCs=None): for i, path in enumerate(loc_pcs): logger.debug("Reading PCA projection: {}".format(path)) - df = pd.read_csv(path, sep='\t', converters={"IID": str}, header=0) - df['sampleset'] = dataset - df.set_index(['sampleset', 'IID'], inplace=True) + df = pd.read_csv(path, sep="\t", converters={"IID": str}, header=0) + df["sampleset"] = dataset + df.set_index(["sampleset", "IID"], inplace=True) if i == 0: - logger.debug('Initialising combined DF') + logger.debug("Initialising combined DF") proj = df.copy() else: - logger.debug('Appending to combined DF') + logger.debug("Appending to combined DF") proj = pd.concat([proj, df]) # Drop PCs if nPCs: - logger.debug('Filtering to relevant PCs') + logger.debug("Filtering to relevant PCs") dropcols = [] for x in proj.columns: if int(x[2:]) > nPCs: @@ -41,47 +39,55 @@ def read_pcs(loc_pcs: list[str],dataset: str, loc_related_ids=None, nPCs=None): # Read/process IDs for unrelated samples (usually reference dataset) if loc_related_ids: logger.debug("Flagging related samples with: {}".format(loc_related_ids)) - proj['Unrelated'] = True - with open(loc_related_ids, 'r') as infile: + proj["Unrelated"] = True + with open(loc_related_ids, "r") as infile: IDs_related = [x.strip() for x in infile.readlines()] - proj.loc[proj.index.get_level_values(level=1).isin(IDs_related), 'Unrelated'] = False + proj.loc[ + proj.index.get_level_values(level=1).isin(IDs_related), "Unrelated" + ] = False else: # if unrelated is all nan -> dtype is float64 # if unrelated is only true / false -> dtype is bool # if unrelated contains None, dtype stays bool, and pd.concat warning disappears - proj['Unrelated'] = None + proj["Unrelated"] = None return proj -def extract_ref_psam_cols(loc_psam, dataset: str, df_target, keepcols=['SuperPop', 'Population']): - psam = pd.read_csv(loc_psam, sep='\t', header=0) +def extract_ref_psam_cols( + loc_psam, dataset: str, df_target, keepcols=["SuperPop", "Population"] +): + psam = pd.read_csv(loc_psam, sep="\t", header=0) - match (psam.columns[0]): + match psam.columns[0]: # handle case of #IID -> IID (happens when #FID is present) - case '#IID': - psam.rename({'#IID': 'IID'}, axis=1, inplace=True) - case '#FID': - psam.drop(['#FID'], axis=1, inplace=True) + case "#IID": + psam.rename({"#IID": "IID"}, axis=1, inplace=True) + case "#FID": + psam.drop(["#FID"], axis=1, inplace=True) case _: assert False, "Invalid columns" - psam['sampleset'] = dataset - psam.set_index(['sampleset', 'IID'], inplace=True) + psam["sampleset"] = dataset + psam.set_index(["sampleset", "IID"], inplace=True) return pd.merge(df_target, psam[keepcols], left_index=True, right_index=True) -def read_pgs(loc_aggscore, onlySUM: bool): +def read_pgs(loc_aggscore): """ - Function to read the output of aggreagte_scores + Function to read the PGS SUM from the output of aggreagte_scores :param loc_aggscore: path to aggregated scores output - :param onlySUM: whether to return only _SUM columns (e.g. not _AVG) - :return: + :return: df with PGS SUM indexed by sampleset and IID """ - logger.debug('Reading aggregated score data: {}'.format(loc_aggscore)) - df = pd.read_csv(loc_aggscore, sep='\t', index_col=['sampleset', 'IID'], converters={"IID": str}, header=0) - if onlySUM: - df = df[[x for x in df.columns if x.endswith('_SUM')]] - rn = [x.rstrip('_SUM') for x in df.columns] - df.columns = rn - return df \ No newline at end of file + logger.debug("Reading aggregated score data: {}".format(loc_aggscore)) + df = pd.read_csv( + loc_aggscore, + sep="\t", + index_col=["sampleset", "IID"], + converters={"IID": str}, + header=0, + ).pivot(columns=["PGS"], values=["SUM"]) + # rename to PGS only + df.columns = [f"{j}" for i, j in df.columns] + + return df diff --git a/pyproject.toml b/pyproject.toml index 098283f..88134e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pgscatalog_utils" -version = "0.5.0" +version = "0.5.1" description = "Utilities for working with PGS Catalog API and scoring files" homepage = "https://github.com/PGScatalog/pgscatalog_utils" authors = ["Benjamin Wingfield ", "Samuel Lambert ", "Laurent Gil "] diff --git a/tests/data/cineca_22_additive_0.sscore.zst b/tests/data/cineca_22_additive_0.sscore.zst new file mode 100644 index 0000000000000000000000000000000000000000..1b24681b78086de707a2cfdd4c4175c011a2a595 GIT binary patch literal 33464 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 literal 0 HcmV?d00001 diff --git a/tests/test_aggregate.py b/tests/test_aggregate.py new file mode 100644 index 0000000..76f5d86 --- /dev/null +++ b/tests/test_aggregate.py @@ -0,0 +1,22 @@ +import importlib.resources +import os +import pandas as pd +from unittest.mock import patch + +from pgscatalog_utils.aggregate.aggregate_scores import aggregate_scores +from . import data + + +def test_aggregate(tmp_path_factory): + out_dir = tmp_path_factory.mktemp("aggregated") + score_path = importlib.resources.files(data) / "cineca_22_additive_0.sscore.zst" + + args = ["aggregate_scores", "-s", str(score_path), "-o", str(out_dir)] + + with patch("sys.argv", args): + aggregate_scores() + + assert os.listdir(out_dir) == ["aggregated_scores.txt.gz"] + df = pd.read_csv(out_dir / "aggregated_scores.txt.gz", delimiter="\t") + assert list(df.columns) == ["sampleset", "IID", "PGS", "SUM", "DENOM", "AVG"] + assert df.shape == (2504, 6)