diff --git a/pdm.lock b/pdm.lock index a870b15..07f9975 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "test"] strategy = ["inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:e6e33028e9f72bcccb06a995a7d32d343eacab382b94d05b852ff74791f68716" +content_hash = "sha256:2dacf4eae515d031fb142cacc6764ef24059d4f3437ff105bdfabab9571c9c99" [[metadata.targets]] requires_python = ">=3.10" @@ -52,6 +52,66 @@ files = [ {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, ] +[[package]] +name = "cffi" +version = "1.17.1" +requires_python = ">=3.8" +summary = "Foreign Function Interface for Python calling C code." +groups = ["default"] +marker = "platform_python_implementation == \"PyPy\"" +dependencies = [ + "pycparser", +] +files = [ + {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.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, +] + [[package]] name = "click" version = "8.1.8" @@ -202,6 +262,18 @@ files = [ {file = "prometheus_fastapi_instrumentator-7.0.0.tar.gz", hash = "sha256:5ba67c9212719f244ad7942d75ded80693b26331ee5dfc1e7571e4794a9ccbed"}, ] +[[package]] +name = "pycparser" +version = "2.22" +requires_python = ">=3.8" +summary = "C parser in Python" +groups = ["default"] +marker = "platform_python_implementation == \"PyPy\"" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + [[package]] name = "pydantic" version = "2.10.4" @@ -551,3 +623,80 @@ dependencies = [ "sentry-sdk[fastapi]>=2.13.0", "typer>=0.12.4", ] + +[[package]] +name = "zstandard" +version = "0.23.0" +requires_python = ">=3.8" +summary = "Zstandard bindings for Python" +groups = ["default"] +dependencies = [ + "cffi>=1.17; platform_python_implementation == \"PyPy\"", +] +files = [ + {file = "zstandard-0.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bf0a05b6059c0528477fba9054d09179beb63744355cab9f38059548fedd46a9"}, + {file = "zstandard-0.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fc9ca1c9718cb3b06634c7c8dec57d24e9438b2aa9a0f02b8bb36bf478538880"}, + {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77da4c6bfa20dd5ea25cbf12c76f181a8e8cd7ea231c673828d0386b1740b8dc"}, + {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2170c7e0367dde86a2647ed5b6f57394ea7f53545746104c6b09fc1f4223573"}, + {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c16842b846a8d2a145223f520b7e18b57c8f476924bda92aeee3a88d11cfc391"}, + {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:157e89ceb4054029a289fb504c98c6a9fe8010f1680de0201b3eb5dc20aa6d9e"}, + {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:203d236f4c94cd8379d1ea61db2fce20730b4c38d7f1c34506a31b34edc87bdd"}, + {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dc5d1a49d3f8262be192589a4b72f0d03b72dcf46c51ad5852a4fdc67be7b9e4"}, + {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:752bf8a74412b9892f4e5b58f2f890a039f57037f52c89a740757ebd807f33ea"}, + {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80080816b4f52a9d886e67f1f96912891074903238fe54f2de8b786f86baded2"}, + {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:84433dddea68571a6d6bd4fbf8ff398236031149116a7fff6f777ff95cad3df9"}, + {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ab19a2d91963ed9e42b4e8d77cd847ae8381576585bad79dbd0a8837a9f6620a"}, + {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:59556bf80a7094d0cfb9f5e50bb2db27fefb75d5138bb16fb052b61b0e0eeeb0"}, + {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:27d3ef2252d2e62476389ca8f9b0cf2bbafb082a3b6bfe9d90cbcbb5529ecf7c"}, + {file = "zstandard-0.23.0-cp310-cp310-win32.whl", hash = "sha256:5d41d5e025f1e0bccae4928981e71b2334c60f580bdc8345f824e7c0a4c2a813"}, + {file = "zstandard-0.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:519fbf169dfac1222a76ba8861ef4ac7f0530c35dd79ba5727014613f91613d4"}, + {file = "zstandard-0.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:34895a41273ad33347b2fc70e1bff4240556de3c46c6ea430a7ed91f9042aa4e"}, + {file = "zstandard-0.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:77ea385f7dd5b5676d7fd943292ffa18fbf5c72ba98f7d09fc1fb9e819b34c23"}, + {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:983b6efd649723474f29ed42e1467f90a35a74793437d0bc64a5bf482bedfa0a"}, + {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80a539906390591dd39ebb8d773771dc4db82ace6372c4d41e2d293f8e32b8db"}, + {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:445e4cb5048b04e90ce96a79b4b63140e3f4ab5f662321975679b5f6360b90e2"}, + {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd30d9c67d13d891f2360b2a120186729c111238ac63b43dbd37a5a40670b8ca"}, + {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d20fd853fbb5807c8e84c136c278827b6167ded66c72ec6f9a14b863d809211c"}, + {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed1708dbf4d2e3a1c5c69110ba2b4eb6678262028afd6c6fbcc5a8dac9cda68e"}, + {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:be9b5b8659dff1f913039c2feee1aca499cfbc19e98fa12bc85e037c17ec6ca5"}, + {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65308f4b4890aa12d9b6ad9f2844b7ee42c7f7a4fd3390425b242ffc57498f48"}, + {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98da17ce9cbf3bfe4617e836d561e433f871129e3a7ac16d6ef4c680f13a839c"}, + {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8ed7d27cb56b3e058d3cf684d7200703bcae623e1dcc06ed1e18ecda39fee003"}, + {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:b69bb4f51daf461b15e7b3db033160937d3ff88303a7bc808c67bbc1eaf98c78"}, + {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:034b88913ecc1b097f528e42b539453fa82c3557e414b3de9d5632c80439a473"}, + {file = "zstandard-0.23.0-cp311-cp311-win32.whl", hash = "sha256:f2d4380bf5f62daabd7b751ea2339c1a21d1c9463f1feb7fc2bdcea2c29c3160"}, + {file = "zstandard-0.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:62136da96a973bd2557f06ddd4e8e807f9e13cbb0bfb9cc06cfe6d98ea90dfe0"}, + {file = "zstandard-0.23.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b4567955a6bc1b20e9c31612e615af6b53733491aeaa19a6b3b37f3b65477094"}, + {file = "zstandard-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e172f57cd78c20f13a3415cc8dfe24bf388614324d25539146594c16d78fcc8"}, + {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0e166f698c5a3e914947388c162be2583e0c638a4703fc6a543e23a88dea3c1"}, + {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a289832e520c6bd4dcaad68e944b86da3bad0d339ef7989fb7e88f92e96072"}, + {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d50d31bfedd53a928fed6707b15a8dbeef011bb6366297cc435accc888b27c20"}, + {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72c68dda124a1a138340fb62fa21b9bf4848437d9ca60bd35db36f2d3345f373"}, + {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53dd9d5e3d29f95acd5de6802e909ada8d8d8cfa37a3ac64836f3bc4bc5512db"}, + {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6a41c120c3dbc0d81a8e8adc73312d668cd34acd7725f036992b1b72d22c1772"}, + {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:40b33d93c6eddf02d2c19f5773196068d875c41ca25730e8288e9b672897c105"}, + {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9206649ec587e6b02bd124fb7799b86cddec350f6f6c14bc82a2b70183e708ba"}, + {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76e79bc28a65f467e0409098fa2c4376931fd3207fbeb6b956c7c476d53746dd"}, + {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:66b689c107857eceabf2cf3d3fc699c3c0fe8ccd18df2219d978c0283e4c508a"}, + {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9c236e635582742fee16603042553d276cca506e824fa2e6489db04039521e90"}, + {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8fffdbd9d1408006baaf02f1068d7dd1f016c6bcb7538682622c556e7b68e35"}, + {file = "zstandard-0.23.0-cp312-cp312-win32.whl", hash = "sha256:dc1d33abb8a0d754ea4763bad944fd965d3d95b5baef6b121c0c9013eaf1907d"}, + {file = "zstandard-0.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:64585e1dba664dc67c7cdabd56c1e5685233fbb1fc1966cfba2a340ec0dfff7b"}, + {file = "zstandard-0.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:576856e8594e6649aee06ddbfc738fec6a834f7c85bf7cadd1c53d4a58186ef9"}, + {file = "zstandard-0.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38302b78a850ff82656beaddeb0bb989a0322a8bbb1bf1ab10c17506681d772a"}, + {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2240ddc86b74966c34554c49d00eaafa8200a18d3a5b6ffbf7da63b11d74ee2"}, + {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ef230a8fd217a2015bc91b74f6b3b7d6522ba48be29ad4ea0ca3a3775bf7dd5"}, + {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:774d45b1fac1461f48698a9d4b5fa19a69d47ece02fa469825b442263f04021f"}, + {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f77fa49079891a4aab203d0b1744acc85577ed16d767b52fc089d83faf8d8ed"}, + {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac184f87ff521f4840e6ea0b10c0ec90c6b1dcd0bad2f1e4a9a1b4fa177982ea"}, + {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c363b53e257246a954ebc7c488304b5592b9c53fbe74d03bc1c64dda153fb847"}, + {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e7792606d606c8df5277c32ccb58f29b9b8603bf83b48639b7aedf6df4fe8171"}, + {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a0817825b900fcd43ac5d05b8b3079937073d2b1ff9cf89427590718b70dd840"}, + {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9da6bc32faac9a293ddfdcb9108d4b20416219461e4ec64dfea8383cac186690"}, + {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fd7699e8fd9969f455ef2926221e0233f81a2542921471382e77a9e2f2b57f4b"}, + {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d477ed829077cd945b01fc3115edd132c47e6540ddcd96ca169facff28173057"}, + {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ce8b52c5987b3e34d5674b0ab529a4602b632ebab0a93b07bfb4dfc8f8a33"}, + {file = "zstandard-0.23.0-cp313-cp313-win32.whl", hash = "sha256:a9b07268d0c3ca5c170a385a0ab9fb7fdd9f5fd866be004c4ea39e44edce47dd"}, + {file = "zstandard-0.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:f3513916e8c645d0610815c257cbfd3242adfd5c4cfa78be514e5a3ebb42a41b"}, + {file = "zstandard-0.23.0.tar.gz", hash = "sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09"}, +] diff --git a/pyproject.toml b/pyproject.toml index 3dbc5f5..29c680d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,6 +3,7 @@ name = "docker-unpack" description = "Unpack Docker images for use with Apptainer and CVMFS" dependencies = [ "watcloud-utils @ git+https://github.com/WATonomous/watcloud-utils.git@c8ce1006716e65971f750560f90f442721b3777d", + "zstandard>=0.23.0,<1", ] requires-python = ">=3.10" readme = "README.md" diff --git a/src/docker_unpack/cli.py b/src/docker_unpack/cli.py index c42bf6f..7f37bf2 100644 --- a/src/docker_unpack/cli.py +++ b/src/docker_unpack/cli.py @@ -11,6 +11,7 @@ from watcloud_utils.typer import app, typer from ._version import __version__ +from .utils import generate_env, generate_runscript, MyTarFile, StreamProxy @app.command() @@ -20,128 +21,6 @@ def version(): """ print(__version__) - -def escape(value): - """Escapes special characters in a string for use in a shell script.""" - return value.replace('"', r"\"").replace("'", r"\'") - - -def args_quoted(args): - """Quotes each argument and joins them as a single string.""" - return " ".join(f'"{arg}"' for arg in args) - - -def generate_runscript(root_path: Path, img_config: dict): - """ - Generates the runscript (entrypoint) for the Apptainer container. - - References: - - https://apptainer.org/docs/user/main/cli/apptainer_run.html#examples - - https://github.com/cvmfs/cvmfs/blob/531e6f6bd4b2fa8847138d7046d9a09070234464/ducc/singularity/startup_files.go#L65-L148 - """ - runscript_path = root_path / ".singularity.d/runscript" - logger.info(f"Generating Apptainer runscript at {runscript_path}") - - # Create and open the runscript file - runscript_path.parent.mkdir(parents=True, exist_ok=True) - with open(runscript_path, "w") as f: - # Write the shell shebang - f.write("#!/bin/sh\n") - - # Write OCI_ENTRYPOINT - if img_config.get("Entrypoint"): - entrypoint = args_quoted(img_config["Entrypoint"]) - f.write(f"OCI_ENTRYPOINT='{entrypoint}'\n") - else: - f.write("OCI_ENTRYPOINT=''\n") - - # Write OCI_CMD - if img_config.get("Cmd"): - cmd = args_quoted(img_config["Cmd"]) - f.write(f"OCI_CMD='{cmd}'\n") - else: - f.write("OCI_CMD=''\n") - - # Write the rest of the script - f.write( - r"""CMDLINE_ARGS="" -# prepare command line arguments for evaluation -for arg in "$@"; do -CMDLINE_ARGS="${CMDLINE_ARGS} \"$arg\"" -done -# ENTRYPOINT only - run entrypoint plus args -if [ -z "$OCI_CMD" ] && [ -n "$OCI_ENTRYPOINT" ]; then -if [ $# -gt 0 ]; then - SINGULARITY_OCI_RUN="${OCI_ENTRYPOINT} ${CMDLINE_ARGS}" -else - SINGULARITY_OCI_RUN="${OCI_ENTRYPOINT}" -fi -fi -# CMD only - run CMD or override with args -if [ -n "$OCI_CMD" ] && [ -z "$OCI_ENTRYPOINT" ]; then -if [ $# -gt 0 ]; then - SINGULARITY_OCI_RUN="${CMDLINE_ARGS}" -else - SINGULARITY_OCI_RUN="${OCI_CMD}" -fi -fi -# ENTRYPOINT and CMD - run ENTRYPOINT with CMD as default args -# override with user provided args -if [ $# -gt 0 ]; then -SINGULARITY_OCI_RUN="${OCI_ENTRYPOINT} ${CMDLINE_ARGS}" -else -SINGULARITY_OCI_RUN="${OCI_ENTRYPOINT} ${OCI_CMD}" -fi -# Evaluate shell expressions first and set arguments accordingly, -# then execute final command as first container process -eval "set ${SINGULARITY_OCI_RUN}" -exec "$@" -""" - ) - - # Change permissions - os.chmod(runscript_path, 0o755) - - -def generate_env(root_path: Path, img_config: dict): - """ - Generates the environment script for the Apptainer container. - - References: - - https://github.com/cvmfs/cvmfs/blob/531e6f6bd4b2fa8847138d7046d9a09070234464/ducc/singularity/startup_files.go#L150-L190 - """ - env_path = root_path / ".singularity.d/env/10-docker2singularity.sh" - logger.info(f"Generating Apptainer environment script at {env_path}") - - # Ensure the directory exists - env_path.parent.mkdir(parents=True, exist_ok=True) - - # Create and open the environment script file - with open(env_path, "w") as f: - # Write the shell shebang - f.write("#!/bin/sh\n") - - # Write environment variables - for element in img_config.get("Env", []): - env_parts = element.split("=", 1) - if len(env_parts) == 1: - export_line = f'export {env_parts[0]}="${{{env_parts[0]}:-}}"\n' - else: - if env_parts[0] == "PATH": - export_line = f"export {env_parts[0]}={escape(env_parts[1])!r}\n" - else: - export_line = f'export {env_parts[0]}="${{{env_parts[0]}:-{escape(env_parts[1])!r}}}"\n' - - f.write(export_line) - - # Sync file to disk - f.flush() - os.fsync(f.fileno()) - - # Set executable permissions - os.chmod(env_path, 0o755) - - @app.command() def unpack(input_file: typer.FileBinaryRead, output_dir: Path): if output_dir.exists() and any(output_dir.iterdir()): @@ -151,7 +30,8 @@ def unpack(input_file: typer.FileBinaryRead, output_dir: Path): with tempfile.TemporaryDirectory() as temp_dir: logger.info(f"Extracting tar file to {temp_dir=}") - with tarfile.open(fileobj=input_file, mode="r|*") as tar: + input_file_proxy = StreamProxy(input_file) + with MyTarFile.open(fileobj=input_file_proxy, mode=f"r{'|' if input_file_proxy.supports_streaming() else ':'}{input_file_proxy.getcomptype()}") as tar: tar.extractall(temp_dir) manifest_path = Path(temp_dir) / "manifest.json" @@ -176,7 +56,10 @@ def unpack(input_file: typer.FileBinaryRead, output_dir: Path): layer_path = Path(temp_dir) / layer logger.info(f"Extracting {layer_path=}") - with tarfile.open(layer_path) as tar: + with open(layer_path, "rb") as f: + comptype = StreamProxy(f).getcomptype() + + with MyTarFile.open(layer_path, f"r:{comptype}") as tar: for member in tar: basename = os.path.basename(member.name) diff --git a/src/docker_unpack/utils.py b/src/docker_unpack/utils.py new file mode 100644 index 0000000..1406f06 --- /dev/null +++ b/src/docker_unpack/utils.py @@ -0,0 +1,210 @@ +import os +import tarfile +import typing +from pathlib import Path + +from watcloud_utils.logging import logger + + +def escape(value): + """Escapes special characters in a string for use in a shell script.""" + return value.replace('"', r"\"").replace("'", r"\'") + + +def args_quoted(args): + """Quotes each argument and joins them as a single string.""" + return " ".join(f'"{arg}"' for arg in args) + + +def generate_runscript(root_path: Path, img_config: dict): + """ + Generates the runscript (entrypoint) for the Apptainer container. + + References: + - https://apptainer.org/docs/user/main/cli/apptainer_run.html#examples + - https://github.com/cvmfs/cvmfs/blob/531e6f6bd4b2fa8847138d7046d9a09070234464/ducc/singularity/startup_files.go#L65-L148 + """ + runscript_path = root_path / ".singularity.d/runscript" + logger.info(f"Generating Apptainer runscript at {runscript_path}") + + # Create and open the runscript file + runscript_path.parent.mkdir(parents=True, exist_ok=True) + with open(runscript_path, "w") as f: + # Write the shell shebang + f.write("#!/bin/sh\n") + + # Write OCI_ENTRYPOINT + if img_config.get("Entrypoint"): + entrypoint = args_quoted(img_config["Entrypoint"]) + f.write(f"OCI_ENTRYPOINT='{entrypoint}'\n") + else: + f.write("OCI_ENTRYPOINT=''\n") + + # Write OCI_CMD + if img_config.get("Cmd"): + cmd = args_quoted(img_config["Cmd"]) + f.write(f"OCI_CMD='{cmd}'\n") + else: + f.write("OCI_CMD=''\n") + + # Write the rest of the script + f.write( + r"""CMDLINE_ARGS="" +# prepare command line arguments for evaluation +for arg in "$@"; do +CMDLINE_ARGS="${CMDLINE_ARGS} \"$arg\"" +done +# ENTRYPOINT only - run entrypoint plus args +if [ -z "$OCI_CMD" ] && [ -n "$OCI_ENTRYPOINT" ]; then +if [ $# -gt 0 ]; then + SINGULARITY_OCI_RUN="${OCI_ENTRYPOINT} ${CMDLINE_ARGS}" +else + SINGULARITY_OCI_RUN="${OCI_ENTRYPOINT}" +fi +fi +# CMD only - run CMD or override with args +if [ -n "$OCI_CMD" ] && [ -z "$OCI_ENTRYPOINT" ]; then +if [ $# -gt 0 ]; then + SINGULARITY_OCI_RUN="${CMDLINE_ARGS}" +else + SINGULARITY_OCI_RUN="${OCI_CMD}" +fi +fi +# ENTRYPOINT and CMD - run ENTRYPOINT with CMD as default args +# override with user provided args +if [ $# -gt 0 ]; then +SINGULARITY_OCI_RUN="${OCI_ENTRYPOINT} ${CMDLINE_ARGS}" +else +SINGULARITY_OCI_RUN="${OCI_ENTRYPOINT} ${OCI_CMD}" +fi +# Evaluate shell expressions first and set arguments accordingly, +# then execute final command as first container process +eval "set ${SINGULARITY_OCI_RUN}" +exec "$@" +""" + ) + + # Change permissions + os.chmod(runscript_path, 0o755) + + +def generate_env(root_path: Path, img_config: dict): + """ + Generates the environment script for the Apptainer container. + + References: + - https://github.com/cvmfs/cvmfs/blob/531e6f6bd4b2fa8847138d7046d9a09070234464/ducc/singularity/startup_files.go#L150-L190 + """ + env_path = root_path / ".singularity.d/env/10-docker2singularity.sh" + logger.info(f"Generating Apptainer environment script at {env_path}") + + # Ensure the directory exists + env_path.parent.mkdir(parents=True, exist_ok=True) + + # Create and open the environment script file + with open(env_path, "w") as f: + # Write the shell shebang + f.write("#!/bin/sh\n") + + # Write environment variables + for element in img_config.get("Env", []): + env_parts = element.split("=", 1) + if len(env_parts) == 1: + export_line = f'export {env_parts[0]}="${{{env_parts[0]}:-}}"\n' + else: + if env_parts[0] == "PATH": + export_line = f"export {env_parts[0]}={escape(env_parts[1])!r}\n" + else: + export_line = f'export {env_parts[0]}="${{{env_parts[0]}:-{escape(env_parts[1])!r}}}"\n' + + f.write(export_line) + + # Sync file to disk + f.flush() + os.fsync(f.fileno()) + + # Set executable permissions + os.chmod(env_path, 0o755) + + +class StreamProxy: + """ + A stream wrapper to detect compression type. + + Derived from https://github.com/python/cpython/blob/2cf396c368a188e9142843e566ce6d8e6eb08999/Lib/tarfile.py#L574-L598 + """ + + def __init__(self, fileobj): + self.fileobj = fileobj + self.buf = self.fileobj.read(tarfile.BLOCKSIZE) + + def read(self, size): + self.read = self.fileobj.read + return self.buf + + def getcomptype(self): + if self.buf.startswith(b"\x1f\x8b\x08"): + return "gz" + elif self.buf[0:3] == b"BZh" and self.buf[4:10] == b"1AY&SY": + return "bz2" + elif self.buf.startswith((b"\x5d\x00\x00\x80", b"\xfd7zXZ")): + return "xz" + elif self.buf.startswith(b"\x28\xb5\x2f\xfd"): + return "zst" + else: + return "tar" + + def supports_streaming(self): + comptype = self.getcomptype() + return comptype not in ("zst",) + + def close(self): + self.fileobj.close() + + +class MyTarFile(tarfile.TarFile): + """ + A custom TarFile class that supports more compression types. + + Derived from: + - https://github.com/python/cpython/issues/81276#issuecomment-1966037544 + """ + + OPEN_METH = {"zst": "zstopen"} | tarfile.TarFile.OPEN_METH + + @classmethod + def zstopen( + cls, + name: str , + mode: typing.Literal["r", "w", "x"] = "r", + fileobj: typing.Optional[typing.BinaryIO] = None, + ) -> tarfile.TarFile: + if mode not in ("r", "w", "x"): + raise NotImplementedError(f"mode `{mode}' not implemented for zst") + try: + import zstandard + except ImportError: + raise tarfile.CompressionError("zstandard module not available") + if mode == "r": + zfobj = zstandard.open(fileobj or name, "rb") + else: + zfobj = zstandard.open( + fileobj or name, + mode + "b", + cctx=zstandard.ZstdCompressor(write_checksum=True, threads=-1), + ) + try: + tarobj = cls.taropen(name, mode, zfobj) + except (OSError, EOFError, zstandard.ZstdError) as exc: + zfobj.close() + if mode == "r": + raise tarfile.ReadError("not a zst file") from exc + raise + except: + zfobj.close() + raise + # Setting the _extfileobj attribute is important to signal a need to + # close this object and thus flush the compressed stream. + # Unfortunately, tarfile.pyi doesn't know about it. + tarobj._extfileobj = False # type: ignore + return tarobj diff --git a/tests/integration/compression/test.sh b/tests/integration/compression/test.sh index 987d1cb..7dcd3ac 100755 --- a/tests/integration/compression/test.sh +++ b/tests/integration/compression/test.sh @@ -10,7 +10,7 @@ trap 'echo "Error on line $LINENO: $BASH_COMMAND"; exit 1' ERR docker pull alpine # MARK: Compressing the whole package -for compression in gzip bzip2 xz; do +for compression in zstd gzip bzip2 xz; do echo "Testing package compression with $compression" __tmpdir=$(mktemp -d) docker save alpine | $compression > "$__tmpdir/image.tar" @@ -22,7 +22,7 @@ done # MARK: Compressing the layers docker buildx create --name compression-test --driver docker-container --use -for compression in gzip estargz; do +for compression in zstd gzip estargz; do echo "Testing layer compression with $compression" __tmpdir=$(mktemp -d)