diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 92e5874..c0d3860 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -30,5 +30,7 @@ jobs: POETRY_VIRTUALENVS_CREATE: false - name: Check formatting and linting run: poetry run ruff check + - name: Static type checking + run: poetry run mypy pydpkg/ - name: Test with pytest run: poetry run pytest tests/ diff --git a/poetry.lock b/poetry.lock index c449f60..ae2433c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "arpy" @@ -12,63 +12,78 @@ files = [ [[package]] name = "cffi" -version = "1.16.0" +version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" files = [ - {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, - {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, - {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, - {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, - {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, - {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, - {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, - {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, - {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, - {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, - {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, - {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, - {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, - {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, - {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, + {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, + {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, + {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, + {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, ] [package.dependencies] @@ -87,43 +102,38 @@ files = [ [[package]] name = "cryptography" -version = "42.0.5" +version = "43.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16"}, - {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da"}, - {file = "cryptography-42.0.5-cp37-abi3-win32.whl", hash = "sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74"}, - {file = "cryptography-42.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940"}, - {file = "cryptography-42.0.5-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30"}, - {file = "cryptography-42.0.5-cp39-abi3-win32.whl", hash = "sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413"}, - {file = "cryptography-42.0.5-cp39-abi3-win_amd64.whl", hash = "sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd"}, - {file = "cryptography-42.0.5.tar.gz", hash = "sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1"}, + {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18"}, + {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd"}, + {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73"}, + {file = "cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2"}, + {file = "cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd"}, + {file = "cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405"}, + {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16"}, + {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73"}, + {file = "cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995"}, + {file = "cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff"}, + {file = "cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805"}, ] [package.dependencies] @@ -136,18 +146,18 @@ nox = ["nox"] pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi", "cryptography-vectors (==43.0.3)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] name = "exceptiongroup" -version = "1.2.0" +version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, ] [package.extras] @@ -164,15 +174,79 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "mypy" +version = "1.13.0" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, + {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, + {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, + {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, + {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, + {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, + {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, + {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, + {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, + {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, + {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, + {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, + {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, + {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, + {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, + {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, + {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, + {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, + {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, + {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, + {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, + {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.6.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + [[package]] name = "packaging" -version = "24.0" +version = "24.1" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, - {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] [[package]] @@ -191,13 +265,13 @@ pyasn1 = "*" [[package]] name = "pluggy" -version = "1.4.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] @@ -206,13 +280,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pyasn1" -version = "0.6.0" +version = "0.6.1" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" optional = false python-versions = ">=3.8" files = [ - {file = "pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"}, - {file = "pyasn1-0.6.0.tar.gz", hash = "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c"}, + {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, + {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, ] [[package]] @@ -287,13 +361,24 @@ files = [ [[package]] name = "tomli" -version = "2.0.1" +version = "2.0.2" description = "A lil' TOML parser" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +files = [ + {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, + {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [[package]] @@ -360,4 +445,4 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<4.0" -content-hash = "f97022dc0d780531b61dfc9a0206e372d11729ed134c82d7692de06bd91bc687" +content-hash = "5360ff1341ea2a9470707bd29c01c39e26621d230a98b336288c37664d2f6dcc" diff --git a/pydpkg/base.py b/pydpkg/base.py index a225446..f230911 100644 --- a/pydpkg/base.py +++ b/pydpkg/base.py @@ -2,10 +2,14 @@ Base class to avoid pylint complaining about duplicate code """ +from __future__ import annotations + +from typing import Any + class _Dbase: # pylint: disable=too-few-public-methods - def __getitem__(self, item): + def __getitem__(self, item: str) -> Any: """Overload getitem to treat the message plus our local properties as items. @@ -17,6 +21,6 @@ def __getitem__(self, item): return getattr(self, item) except AttributeError: try: - return self.__getattr__(item) + return self.__getattr__(item) # type: ignore[attr-defined] except AttributeError as ex: raise KeyError(item) from ex diff --git a/pydpkg/dpkg.py b/pydpkg/dpkg.py index 5bddc56..0d7b0d2 100644 --- a/pydpkg/dpkg.py +++ b/pydpkg/dpkg.py @@ -1,5 +1,7 @@ """pydpkg.dpkg.Dpkg: a class to represent dpkg files""" +from __future__ import annotations + # stdlib imports import hashlib import io @@ -7,14 +9,17 @@ import lzma import os import tarfile +from typing import Literal, Any, TypedDict, TYPE_CHECKING, Union, IO from functools import cmp_to_key from email import message_from_string +from email.message import Message from gzip import GzipFile +from itertools import zip_longest # pypi imports import six import zstandard -from arpy import Archive +from arpy import Archive, ArchiveFileData # local imports from pydpkg.exceptions import ( @@ -26,42 +31,58 @@ ) from pydpkg.base import _Dbase +if TYPE_CHECKING: + from _typeshed import SupportsAllComparisons, SupportsRead + REQUIRED_HEADERS = ("package", "version", "architecture") +class FileInfo(TypedDict): + """Type definition for the fileinfo dictionary.""" + + md5: str + sha1: str + sha256: str + filesize: int + + # pylint: disable=too-many-instance-attributes,too-many-public-methods class Dpkg(_Dbase): """Class allowing import and manipulation of a debian package file.""" - def __init__(self, filename=None, ignore_missing=False, logger=None): + def __init__( + self, filename: str | None = None, ignore_missing: bool = False, logger: logging.Logger | None = None + ) -> None: """Constructor for Dpkg object :param filename: string :param ignore_missing: bool :param logger: logging.Logger """ + if not isinstance(filename, six.string_types): + raise DpkgError("filename argument must be a string") + self.filename = os.path.expanduser(filename) self.ignore_missing = ignore_missing - if not isinstance(self.filename, six.string_types): - raise DpkgError("filename argument must be a string") + if not os.path.isfile(self.filename): raise DpkgError(f"filename '{filename}' does not exist") self._log = logger or logging.getLogger(__name__) - self._fileinfo = None - self._control_str = None - self._headers = None - self._message = None - self._upstream_version = None - self._debian_revision = None - self._epoch = None - - def __repr__(self): + self._fileinfo: FileInfo | None = None + self._control_str: str | None = None + self._headers: dict[str, str] | None = None + self._message: Message[str, str] | None = None + self._upstream_version: str | None = None + self._debian_revision: str | None = None + self._epoch: int | None = None + + def __repr__(self) -> str: # type: ignore[explicit-override] return repr(self.control_str) - def __str__(self): - return six.text_type(self.control_str) + def __str__(self) -> str: # type: ignore[explicit-override] + return six.text_type(self.control_str) # type: ignore[no-any-return] - def __getattr__(self, attr): + def __getattr__(self, attr: str) -> str: """Overload getattr to treat control message headers as object attributes (so long as they do not conflict with an existing attribute). @@ -76,7 +97,7 @@ def __getattr__(self, attr): raise AttributeError(f"'Dpkg' object has no attribute '{attr}'") @property - def message(self): + def message(self) -> Message[str, str]: """Return an email.Message object containing the package control structure. @@ -87,7 +108,7 @@ def message(self): return self._message @property - def control_str(self): + def control_str(self) -> str: """Return the control message as a string :returns: string @@ -97,7 +118,7 @@ def control_str(self): return self._control_str @property - def headers(self): + def headers(self) -> dict[str, str]: """Return the control message headers as a dict :returns: dict @@ -107,7 +128,7 @@ def headers(self): return self._headers @property - def fileinfo(self): + def fileinfo(self) -> FileInfo: """Return a dictionary containing md5/sha1/sha256 checksums and the size in bytes of our target file. @@ -131,7 +152,7 @@ def fileinfo(self): return self._fileinfo @property - def md5(self): + def md5(self) -> str: """Return the md5 hash of our target file :returns: string @@ -139,7 +160,7 @@ def md5(self): return self.fileinfo["md5"] @property - def sha1(self): + def sha1(self) -> str: """Return the sha1 hash of our target file :returns: string @@ -147,7 +168,7 @@ def sha1(self): return self.fileinfo["sha1"] @property - def sha256(self): + def sha256(self) -> str: """Return the sha256 hash of our target file :returns: string @@ -155,7 +176,7 @@ def sha256(self): return self.fileinfo["sha256"] @property - def filesize(self): + def filesize(self) -> int: """Return the size of our target file :returns: string @@ -163,7 +184,7 @@ def filesize(self): return self.fileinfo["filesize"] @property - def epoch(self): + def epoch(self) -> int: """Return the epoch portion of the package version string :returns: int @@ -173,7 +194,7 @@ def epoch(self): return self._epoch @property - def upstream_version(self): + def upstream_version(self) -> str: """Return the upstream portion of the package version string :returns: string @@ -183,7 +204,7 @@ def upstream_version(self): return self._upstream_version @property - def debian_revision(self): + def debian_revision(self) -> str: """Return the debian revision portion of the package version string :returns: string @@ -192,7 +213,7 @@ def debian_revision(self): self._debian_revision = self.split_full_version(self.version)[2] return self._debian_revision - def get(self, item, default=None): + def get(self, item: str, default: str | None = None) -> Any | None: """Return an object property, a message header, None or the caller- provided default. @@ -205,26 +226,30 @@ def get(self, item, default=None): except KeyError: return default - def get_header(self, header): + def get_header(self, header: str) -> str | None: """Return an individual control message header :returns: string or None """ return self.message.get(header) - def compare_version_with(self, version_str): + def compare_version_with(self, version_str: str) -> Literal[-1, 0, 1]: """Compare my version to an arbitrary version""" - return Dpkg.compare_versions(self.get_header("version"), version_str) + header_version = self.get_header("version") + if header_version is None: + raise DpkgError("No version header found in control message") + return Dpkg.compare_versions(header_version, version_str) @staticmethod - def _force_encoding(obj, encoding="utf-8"): + def _force_encoding(obj: Any, encoding: str = "utf-8") -> Any: """Enforce uniform text encoding""" if isinstance(obj, six.string_types): if not isinstance(obj, six.text_type): obj = six.text_type(obj, encoding) return obj - def _extract_message(self, ctar): + def _extract_message(self, ctar: tarfile.TarFile) -> Message[str, str]: + """Extract the control file from an opened tar archive as a Message object""" # pathname in the tar could be ./control, or just control # (there would never be two control files...right?) tar_members = [os.path.basename(x.name) for x in ctar.getmembers()] @@ -235,50 +260,67 @@ def _extract_message(self, ctar): self._log.debug("got control index: %s", control_idx) # at last! control_file = ctar.extractfile(ctar.getmembers()[control_idx]) + if control_file is None: + raise DpkgMissingControlFile("Corrupt dpkg file: control file is None") self._log.debug("got control file: %s", control_file) - message_body = control_file.read() + message_body: Union[str, bytes] = control_file.read() # py27 lacks email.message_from_bytes, so... - if isinstance(message_body, bytes): + if not isinstance(message_body, str): message_body = message_body.decode("utf-8") message = message_from_string(message_body) self._log.debug("got control message: %s", message) return message - def _process_dpkg_file(self, filename): - dpkg_archive = Archive(filename) + def _read_archive(self, dpkg_archive: Archive) -> tuple[ArchiveFileData, Literal["gz", "xz", "zst"]]: + """Search an opened archive for a compressed control file and return it plus the compression""" dpkg_archive.read_all_headers() + if b"control.tar.gz" in dpkg_archive.archived_files: control_archive = dpkg_archive.archived_files[b"control.tar.gz"] - control_archive_type = "gz" - elif b"control.tar.xz" in dpkg_archive.archived_files: + return control_archive, "gz" + + if b"control.tar.xz" in dpkg_archive.archived_files: control_archive = dpkg_archive.archived_files[b"control.tar.xz"] - control_archive_type = "xz" - elif b"control.tar.zst" in dpkg_archive.archived_files: + return control_archive, "xz" + + if b"control.tar.zst" in dpkg_archive.archived_files: control_archive = dpkg_archive.archived_files[b"control.tar.zst"] - control_archive_type = "zst" - else: - raise DpkgMissingControlGzipFile("Corrupt dpkg file: no control.tar.gz/xz/zst file in ar archive.") - self._log.debug("found controlgz: %s", control_archive) + return control_archive, "zst" + raise DpkgMissingControlGzipFile("Corrupt dpkg file: no control.tar.gz/xz/zst file in ar archive.") + + def _extract_message_from_tar(self, fd: SupportsRead[bytes], archive_name: str = "undefined") -> Message[str, str]: + """Extract the control file in a tar archive from a decompressed archive fileobj""" + self._log.debug("opened %s control archive: %s", archive_name, fd) + with tarfile.open(fileobj=io.BytesIO(fd.read())) as ctar: + self._log.debug("opened tar file: %s", ctar) + message = self._extract_message(ctar) + return message + + def _extract_message_from_archive( + self, control_archive: IO[bytes], control_archive_type: Literal["gz", "xz", "zst"] + ) -> Message[str, str]: + """Extract the control file from a compressed archive fileobj""" if control_archive_type == "gz": with GzipFile(fileobj=control_archive) as gzf: - self._log.debug("opened gzip control archive: %s", gzf) - with tarfile.open(fileobj=io.BytesIO(gzf.read())) as ctar: - self._log.debug("opened tar file: %s", ctar) - message = self._extract_message(ctar) - elif control_archive_type == "xz": + return self._extract_message_from_tar(gzf, "gzip") + + if control_archive_type == "xz": with lzma.open(control_archive) as xzf: - self._log.debug("opened xz control archive: %s", xzf) - with tarfile.open(fileobj=io.BytesIO(xzf.read())) as ctar: - self._log.debug("opened tar file: %s", ctar) - message = self._extract_message(ctar) - else: + return self._extract_message_from_tar(xzf, "xz") + + if control_archive_type == "zst": zst = zstandard.ZstdDecompressor() with zst.stream_reader(control_archive) as reader: - self._log.debug("opened zst control archive: %s", reader) - with tarfile.open(fileobj=io.BytesIO(reader.read())) as ctar: - self._log.debug("opened tar file: %s", ctar) - message = self._extract_message(ctar) + return self._extract_message_from_tar(reader, "zst") + + raise DpkgError(f"Unknown control archive type: {control_archive_type}") + + def _process_dpkg_file(self, filename: str) -> Message[str, str]: + with Archive(filename) as archive: + control_archive, control_archive_type = self._read_archive(archive) + self._log.debug("found controlgz: %s", control_archive) + message = self._extract_message_from_archive(control_archive, control_archive_type) for req in REQUIRED_HEADERS: if req not in list(map(str.lower, message.keys())): @@ -296,7 +338,7 @@ def _process_dpkg_file(self, filename): return message @staticmethod - def get_epoch(version_str): + def get_epoch(version_str: str) -> tuple[int, str]: """Parse the epoch out of a package version string. Return (epoch, version); epoch is zero if not found.""" try: @@ -318,7 +360,7 @@ def get_epoch(version_str): return epoch, version_str[e_index + 1 :] @staticmethod - def get_upstream(version_str): + def get_upstream(version_str: str) -> tuple[str, str]: """Given a version string that could potentially contain both an upstream revision and a debian revision, return a tuple of both. If there is no debian revision, return 0 as the second tuple element.""" @@ -331,7 +373,7 @@ def get_upstream(version_str): return version_str[0:d_index], version_str[d_index + 1 :] @staticmethod - def split_full_version(version_str): + def split_full_version(version_str: str) -> tuple[int, str, str]: """Split a full version string into epoch, upstream version and debian revision. :param: version_str @@ -341,7 +383,7 @@ def split_full_version(version_str): return epoch, upstream_rev, debian_rev @staticmethod - def get_alphas(revision_str): + def get_alphas(revision_str: str) -> tuple[str, str]: """Return a tuple of the first non-digit characters of a revision (which may be empty) and the remaining characters.""" # get the index of the first digit @@ -354,7 +396,7 @@ def get_alphas(revision_str): return revision_str, "" @staticmethod - def get_digits(revision_str): + def get_digits(revision_str: str) -> tuple[int, str]: """Return a tuple of the first integer characters of a revision (which may be empty) and the remains.""" # If the string is empty, return (0,'') @@ -370,14 +412,14 @@ def get_digits(revision_str): return int(revision_str), "" @staticmethod - def listify(revision_str): + def listify(revision_str: str) -> list[str | int]: """Split a revision string into a list of alternating between strings and numbers, padded on either end to always be "str, int, str, int..." and always be of even length. This allows us to trivially implement the comparison algorithm described at section 5.6.12 in: https://www.debian.org/doc/debian-policy/ch-controlfields.html#version """ - result = [] + result: list[str | int] = [] while revision_str: rev_1, remains = Dpkg.get_alphas(revision_str) rev_2, remains = Dpkg.get_digits(remains) @@ -387,7 +429,7 @@ def listify(revision_str): # pylint: disable=invalid-name,too-many-return-statements @staticmethod - def dstringcmp(a, b): + def dstringcmp(a: str, b: str) -> Literal[-1, 0, 1]: """debian package version string section lexical sort algorithm "The lexical comparison is a comparison of ASCII values modified so @@ -397,40 +439,48 @@ def dstringcmp(a, b): if a == b: return 0 - try: - for i, char in enumerate(a): - if char == b[i]: - continue - # "a tilde sorts before anything, even the end of a part" - # (emptyness) - if char == "~": - return -1 - if b[i] == "~": - return 1 - # "all the letters sort earlier than all the non-letters" - if char.isalpha() and not b[i].isalpha(): - return -1 - if not char.isalpha() and b[i].isalpha(): - return 1 - # otherwise lexical sort - if ord(char) > ord(b[i]): - return 1 - if ord(char) < ord(b[i]): - return -1 - except IndexError: - # a is longer than b but otherwise equal, hence greater - # ...except for goddamn tildes - if char == "~": + + def _tilde_case(longer: str | None) -> Literal[-1, 1]: + if longer is None: + raise DpkgVersionError("Cannot compare None to ~, something has gone horribly awry.") + if longer[0] == "~": return -1 return 1 - # if we get here, a is shorter than b but otherwise equal, hence lesser - # ...except for goddamn tildes - if b[len(a)] == "~": - return 1 - return -1 + + for char_a, char_b in zip_longest(a, b, fillvalue=None): + if char_b is None: + # a is longer than b but otherwise equal, hence greater + # ...except for goddamn tildes + return _tilde_case(char_a) + + if char_a is None: + # if we get here, a is shorter than b but otherwise equal, hence lesser + # ...except for goddamn tildes + return -1 if _tilde_case(char_b) == 1 else 1 + + if char_a == char_b: + continue + + # "a tilde sorts before anything, even the end of a part" + # (emptyness) + if char_a == "~": + return -1 + if char_b == "~": + return 1 + # "all the letters sort earlier than all the non-letters" + if char_a.isalpha() and not char_b.isalpha(): + return -1 + if not char_a.isalpha() and char_b.isalpha(): + return 1 + # otherwise lexical sort + if ord(char_a) > ord(char_b): + return 1 + if ord(char_a) < ord(char_b): + return -1 + raise DpkgVersionError("Should have matched equal in the beginning") @staticmethod - def compare_revision_strings(rev1, rev2): + def compare_revision_strings(rev1: str, rev2: str) -> Literal[-1, 0, 1]: """Compare two debian revision strings as described at https://www.debian.org/doc/debian-policy/ch-controlfields.html#version """ @@ -443,40 +493,49 @@ def compare_revision_strings(rev1, rev2): list2 = Dpkg.listify(rev2) if list1 == list2: return 0 - try: - for i, item in enumerate(list1): - # explicitly raise IndexError if we've fallen off the edge of list2 - if i >= len(list2): - raise IndexError - # just in case - if not isinstance(item, list2[i].__class__): - raise DpkgVersionError(f"Cannot compare '{item}' to {list2[i]}, something has gone horribly awry.") - # if the items are equal, next - if item == list2[i]: - continue - # numeric comparison - if isinstance(item, int): - if item > list2[i]: - return 1 - if item < list2[i]: - return -1 - else: - # string comparison - return Dpkg.dstringcmp(item, list2[i]) - except IndexError: - # rev1 is longer than rev2 but otherwise equal, hence greater - # ...except for goddamn tildes - if list1[len(list2)][0][0] == "~": + + def _tilde_case(elem: str | int | None) -> Literal[-1, 1]: + if isinstance(elem, str): + if elem[0] == "~": + return -1 + return 1 + raise DpkgVersionError(f"Cannot compare '{elem}' to ~, something has gone horribly awry.") + + def _numeric_case(i1: int, i2: int) -> Literal[-1, 0, 1]: + if i1 > i2: + return 1 + if i1 < i2: return -1 - return 1 - # rev1 is shorter than rev2 but otherwise equal, hence lesser - # ...except for goddamn tildes - if list2[len(list1)][0][0] == "~": - return 1 - return -1 + return 0 + + for i1, i2 in zip_longest(list1, list2, fillvalue=None): + if i2 is None: + # rev1 is longer than rev2 but otherwise equal, hence greater + # ...except for goddamn tildes + return _tilde_case(i1) + + if i1 is None: + # rev1 is shorter than rev2 but otherwise equal, hence lesser + # ...except for goddamn tildes + return -1 if _tilde_case(i2) == 1 else 1 + + # if the items are equal, next + if isinstance(i1, int) and isinstance(i2, int): + cmp = _numeric_case(i1, i2) + elif isinstance(i1, str) and isinstance(i2, str): + cmp = Dpkg.dstringcmp(i1, i2) + else: + raise DpkgVersionError(f"Cannot compare '{i1}' to {i2}, something has gone horribly awry.") + + if cmp == 0: + continue + + return cmp + + raise DpkgVersionError("Should have matched equal in the beginning") @staticmethod - def compare_versions(ver1, ver2): + def compare_versions(ver1: str, ver2: str) -> Literal[-1, 0, 1]: """Function to compare two Debian package version strings, suitable for passing to list.sort() and friends.""" if ver1 == ver2: @@ -509,14 +568,14 @@ def compare_versions(ver1, ver2): return 0 @staticmethod - def compare_versions_key(x): + def compare_versions_key(x: str) -> SupportsAllComparisons: """Uses functools.cmp_to_key to convert the compare_versions() function to a function suitable to passing to sorted() and friends as a key.""" return cmp_to_key(Dpkg.compare_versions)(x) @staticmethod - def dstringcmp_key(x): + def dstringcmp_key(x: str) -> SupportsAllComparisons: """Uses functools.cmp_to_key to convert the dstringcmp() function to a function suitable to passing to sorted() and friends as a key.""" diff --git a/pydpkg/dpkg_inspect.py b/pydpkg/dpkg_inspect.py index a7df34b..fa29609 100755 --- a/pydpkg/dpkg_inspect.py +++ b/pydpkg/dpkg_inspect.py @@ -5,6 +5,7 @@ a debian package file using python-dpkg """ +from __future__ import annotations import glob import logging import os @@ -25,14 +26,14 @@ {5}""" -def indent(input_str, prefix): +def indent(input_str: str, prefix: str) -> str: """ Given a multiline string, return it with every line prefixed by "prefix" """ return "\n".join([f"{prefix}{x}" for x in input_str.split("\n")]) -def main(): +def main() -> None: """pylint really wants a docstring :)""" try: file_names = sys.argv[1:] diff --git a/pydpkg/dsc.py b/pydpkg/dsc.py index 67240e7..9ab89ba 100644 --- a/pydpkg/dsc.py +++ b/pydpkg/dsc.py @@ -1,16 +1,20 @@ """pydpkg.dpkg.Dpkg: a class to represent dpkg files""" +from __future__ import annotations + # stdlib imports import hashlib import logging import os from collections import defaultdict from email import message_from_file, message_from_string - -import pgpy +from email.message import Message +from typing import TYPE_CHECKING, Any # pypi imports import six +import pgpy + # local imports from pydpkg.exceptions import ( @@ -19,6 +23,9 @@ ) from pydpkg.base import _Dbase +if TYPE_CHECKING: + from hashlib import _Hash + REQUIRED_HEADERS = ("package", "version", "architecture") @@ -27,25 +34,28 @@ class Dsc(_Dbase): description (dsc) file.""" # pylint: disable=too-many-instance-attributes - def __init__(self, filename=None, logger=None): + def __init__(self, filename: str | None = None, logger: logging.Logger | None = None) -> None: + if not isinstance(filename, six.string_types): + raise TypeError("filename must be a string") + self.filename = os.path.expanduser(filename) self._dirname = os.path.dirname(self.filename) self._log = logger or logging.getLogger(__name__) - self._message = None - self._source_files = None - self._sizes = None - self._message_str = None - self._checksums = None - self._corrected_checksums = None - self._pgp_message = None - - def __repr__(self): + self._message: Message[str, str] | None = None + self._source_files: list[tuple[str, int, bool]] | None = None + self._sizes: set[tuple[str, int]] | None = None + self._message_str: str | None = None + self._checksums: dict[str, dict[str, str]] | None = None + self._corrected_checksums: dict[str, defaultdict[str, str | None]] | None = None + self._pgp_message: pgpy.PGPMessage | None = None + + def __repr__(self) -> str: # type: ignore[explicit-override] return repr(self.message_str) - def __str__(self): - return six.text_type(self.message_str) + def __str__(self) -> str: # type: ignore[explicit-override] + return six.text_type(self.message_str) # type: ignore[no-any-return] - def __getattr__(self, attr): + def __getattr__(self, attr: str) -> Any: """Overload getattr to treat message headers as object attributes (so long as they do not conflict with an existing attribute). @@ -64,7 +74,7 @@ def __getattr__(self, attr): return self.message[munged] raise AttributeError(f"'Dsc' object has no attribute '{attr}'") - def get(self, item, ret=None): + def get(self, item: str, ret: str | None = None) -> Any | None: """Public wrapper for getitem""" try: return self[item] @@ -72,7 +82,7 @@ def get(self, item, ret=None): return ret @property - def message(self): + def message(self) -> Message[str, str]: """Return an email.Message object containing the parsed dsc file""" self._log.debug("accessing message property") if self._message is None: @@ -80,14 +90,14 @@ def message(self): return self._message @property - def headers(self): + def headers(self) -> dict[str, str]: """Return a dictionary of the message items""" if self._message is None: self._message = self._process_dsc_file() return dict(self._message.items()) @property - def pgp_message(self): + def pgp_message(self) -> pgpy.PGPMessage | None: """Return a pgpy.PGPMessage object containing the signed dsc message (or None if the message is unsigned)""" if self._message is None: @@ -95,26 +105,26 @@ def pgp_message(self): return self._pgp_message @property - def source_files(self): + def source_files(self) -> list[str]: """Return a list of source files found in the dsc file""" if self._source_files is None: self._source_files = self._process_source_files() return [x[0] for x in self._source_files] @property - def all_files_present(self): + def all_files_present(self) -> bool: """Return true if all files listed in the dsc have been found""" if self._source_files is None: self._source_files = self._process_source_files() return all(x[2] for x in self._source_files) @property - def all_checksums_correct(self): + def all_checksums_correct(self) -> bool: """Return true if all checksums are correct""" return not self.corrected_checksums @property - def corrected_checksums(self): + def corrected_checksums(self) -> dict[str, defaultdict[str, str | None]]: """Returns a dict of the CORRECT checksums in any case where the ones provided by the dsc file are incorrect.""" if self._corrected_checksums is None: @@ -122,21 +132,21 @@ def corrected_checksums(self): return self._corrected_checksums @property - def missing_files(self): + def missing_files(self) -> list[str]: """Return a list of all files from the dsc that we failed to find""" if self._source_files is None: self._source_files = self._process_source_files() return [x[0] for x in self._source_files if x[2] is False] @property - def sizes(self): + def sizes(self) -> set[tuple[str, int]]: """Return a list of source files found in the dsc file""" if self._source_files is None: self._source_files = self._process_source_files() return {(x[0], x[1]) for x in self._source_files} @property - def message_str(self): + def message_str(self) -> str: """Return the dsc message as a string :returns: string @@ -146,26 +156,28 @@ def message_str(self): return self._message_str @property - def checksums(self): + def checksums(self) -> dict[str, dict[str, str]]: """Return a dictionary of checksums for the source files found in the dsc file, keyed first by hash type and then by filename.""" if self._checksums is None: self._checksums = self._process_checksums() return self._checksums - def validate(self): + def validate(self) -> None: """Raise exceptions if files are missing or checksums are bad.""" if not self.all_files_present: + if self._source_files is None: + raise DscMissingFileError("Source files are not processed") raise DscMissingFileError([x[0] for x in self._source_files if not x[2]]) if not self.all_checksums_correct: raise DscBadChecksumsError(self.corrected_checksums) - def _process_checksums(self): + def _process_checksums(self) -> dict[str, dict[str, str]]: """Walk through the dsc message looking for any keys in the format 'Checksum-hashtype'. Return a nested dictionary in the form {hashtype: {filename: {digest}}}""" self._log.debug("process_checksums()") - sums = {} + sums: dict[str, dict[str, str]] = {} for key in self.message.keys(): if key.lower().startswith("checksums"): hashtype = key.split("-")[1].lower() @@ -183,7 +195,7 @@ def _process_checksums(self): sums[hashtype][pathname] = digest return sums - def _internalize_message(self, msg): + def _internalize_message(self, msg: Message[str, str]) -> Message[str, str]: """Ugh: the dsc message body may not include a Files or Checksums-foo entry for _itself_, which makes for hilarious misadventures up the chain. So, pfeh, we add it.""" @@ -206,7 +218,7 @@ def _internalize_message(self, msg): if base not in files: self._log.debug("dsc file not found in %s: %s", key, base) self._log.debug("getting hasher for %s", hashtype) - hasher = getattr(hashlib, hashtype)() + hasher: _Hash = getattr(hashlib, hashtype)() self._log.debug("hashing file") with open(self.filename, "rb") as fileobj: # pylint: disable=cell-var-from-loop @@ -219,7 +231,7 @@ def _internalize_message(self, msg): msg.replace_header(key, msg[key] + newline) return msg - def _process_dsc_file(self): + def _process_dsc_file(self) -> Message[str, str]: """Extract the dsc message from a file: parse the dsc body and return an email.Message object. Attempt to extract the RFC822 message from an OpenPGP message if necessary.""" @@ -246,7 +258,7 @@ def _process_dsc_file(self): msg = message_from_file(fileobj) return self._internalize_message(msg) - def _process_source_files(self): + def _process_source_files(self) -> list[tuple[str, int, bool]]: """Walk through the list of lines in the 'Files' section of the dsc message, and verify that the file exists in the same location on our filesystem as the dsc file. Return a list @@ -258,7 +270,7 @@ def _process_source_files(self): out the _files dictionary. """ self._log.debug("process_source_files()") - filenames = [] + filenames: list[tuple[str, int, bool]] = [] try: files = self.message["Files"] except KeyError: @@ -271,16 +283,16 @@ def _process_source_files(self): filenames.append((pathname, int(size), os.path.isfile(pathname))) return filenames - def _validate_checksums(self): + def _validate_checksums(self) -> dict[str, defaultdict[str, str | None]]: """Iterate over the dict of asserted checksums from the dsc file. Check each in turn. If any checksum is invalid, append the correct checksum to a similarly structured dict and return them all at the end.""" self._log.debug("validate_checksums()") - bad_hashes = defaultdict(lambda: defaultdict(None)) + bad_hashes: defaultdict[str, defaultdict[str, str | None]] = defaultdict(lambda: defaultdict(None)) for hashtype, filenames in six.iteritems(self.checksums): for filename, digest in six.iteritems(filenames): - hasher = getattr(hashlib, hashtype)() + hasher: _Hash = getattr(hashlib, hashtype)() with open(filename, "rb") as fileobj: # pylint: disable=cell-var-from-loop for chunk in iter(lambda: fileobj.read(128), b""): diff --git a/pydpkg/exceptions.py b/pydpkg/exceptions.py index 39b9e00..d971d32 100644 --- a/pydpkg/exceptions.py +++ b/pydpkg/exceptions.py @@ -1,5 +1,7 @@ """pydpkg.exceptions: what it says on the tin""" +from __future__ import annotations + class DpkgError(Exception): """Base error class for Dpkg errors""" diff --git a/pyproject.toml b/pyproject.toml index 545acf5..0e3d720 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,11 +37,28 @@ cryptography = ">=39.0.1" [tool.poetry.group.dev.dependencies] pytest = "^7.2.0" ruff = "0.3.4" +mypy = "^1.13.0" [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" +[tool.mypy] +# Enable all error messages. +python_version = "3.8" +enable_error_code = '''type-arg,no-untyped-def,redundant-cast,redundant-self,comparison-overlap, + no-untyped-call,no-any-return,no-any-unimported,unreachable,redundant-expr, + possibly-undefined,truthy-bool,truthy-iterable,ignore-without-code, + unused-awaitable,unused-ignore,explicit-override,unimported-reveal''' +disable_error_code = 'import-untyped' +disallow_untyped_calls = true +disallow_untyped_defs = true +warn_unused_ignores = true +warn_redundant_casts = true +warn_return_any = true +warn_unreachable = true +exclude = ["build", "docs", "tests"] + [tool.ruff] # Exclude a variety of commonly ignored directories. exclude = [