diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index e28011d1..ccf71abe 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -66,9 +66,9 @@ jobs: steps: # Access the tag from the first workflow's outputs - name: ⬇️ Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: 🐍 Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.x" - name: 🚧 Set up Python Environment diff --git a/.github/workflows/testing.yaml b/.github/workflows/testing.yaml index 9775e3f0..97b68d50 100644 --- a/.github/workflows/testing.yaml +++ b/.github/workflows/testing.yaml @@ -46,9 +46,9 @@ jobs: steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - name: ⬇️ Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: 🐍 Set up Python v${{ env.PYTHON_VERSION }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ env.PYTHON_VERSION }} - name: 🔽 Install flake8 @@ -65,9 +65,9 @@ jobs: needs: [lint] steps: - name: ⬇️ Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: 🐍 Set up Python v${{ env.PYTHON_VERSION }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ env.PYTHON_VERSION }} - name: 🔄 Upgrade pip diff --git a/poetry.lock b/poetry.lock index fd74bc93..e750af1d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "alabaster" @@ -96,30 +96,30 @@ test = ["astroid (>=2,<5)", "pytest (<9.0)", "pytest-cov", "pytest-xdist"] [[package]] name = "attrs" -version = "25.4.0" +version = "26.1.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.9" groups = ["main", "docs"] files = [ - {file = "attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373"}, - {file = "attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"}, + {file = "attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309"}, + {file = "attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32"}, ] [[package]] name = "autodocsumm" -version = "0.2.14" +version = "0.2.15" description = "Extended sphinx autodoc including automatic autosummaries" optional = false python-versions = ">=3.7" groups = ["docs"] files = [ - {file = "autodocsumm-0.2.14-py3-none-any.whl", hash = "sha256:3bad8717fc5190802c60392a7ab04b9f3c97aa9efa8b3780b3d81d615bfe5dc0"}, - {file = "autodocsumm-0.2.14.tar.gz", hash = "sha256:2839a9d4facc3c4eccd306c08695540911042b46eeafcdc3203e6d0bab40bc77"}, + {file = "autodocsumm-0.2.15-py3-none-any.whl", hash = "sha256:dbe6fabcaeae4540748ea9b3443eb76c2692e063d44f004f67c424610a5aca9a"}, + {file = "autodocsumm-0.2.15.tar.gz", hash = "sha256:eaf431e7a5a39e41a215311173c8b95e83859059df1ccf3b79c64bf3d5582b3c"}, ] [package.dependencies] -Sphinx = ">=4.0,<9.0" +Sphinx = ">=4.0,<10.0" [[package]] name = "babel" @@ -202,14 +202,14 @@ redis = ["redis (>=2.10.5)"] [[package]] name = "cattrs" -version = "25.3.0" +version = "26.1.0" description = "Composable complex class support for attrs and dataclasses." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "cattrs-25.3.0-py3-none-any.whl", hash = "sha256:9896e84e0a5bf723bc7b4b68f4481785367ce07a8a02e7e9ee6eb2819bc306ff"}, - {file = "cattrs-25.3.0.tar.gz", hash = "sha256:1ac88d9e5eda10436c4517e390a4142d88638fe682c436c93db7ce4a277b884a"}, + {file = "cattrs-26.1.0-py3-none-any.whl", hash = "sha256:d1e0804c42639494d469d08d4f26d6b9de9b8ab26b446db7b5f8c2e97f7c3096"}, + {file = "cattrs-26.1.0.tar.gz", hash = "sha256:fa239e0f0ec0715ba34852ce813986dfed1e12117e209b816ab87401271cdd40"}, ] [package.dependencies] @@ -225,18 +225,19 @@ msgspec = ["msgspec (>=0.19.0) ; implementation_name == \"cpython\""] orjson = ["orjson (>=3.11.3) ; implementation_name == \"cpython\""] pyyaml = ["pyyaml (>=6.0)"] tomlkit = ["tomlkit (>=0.11.8)"] +tomllib = ["tomli (>=1.1.0) ; python_version < \"3.11\"", "tomli-w (>=1.1.0)"] ujson = ["ujson (>=5.10.0)"] [[package]] name = "certifi" -version = "2026.1.4" +version = "2026.2.25" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main", "docs"] files = [ - {file = "certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c"}, - {file = "certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120"}, + {file = "certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa"}, + {file = "certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7"}, ] [[package]] @@ -337,139 +338,201 @@ files = [ [package.dependencies] pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} +[[package]] +name = "chardet" +version = "7.4.3" +description = "Universal character encoding detector" +optional = false +python-versions = ">=3.10" +groups = ["docs"] +files = [ + {file = "chardet-7.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0c79b13c9908ac7dfe0a74116ebc9a0f28b2319d23c32f3dfcdfbe1279c7eaf"}, + {file = "chardet-7.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bba8bea1b28d927b3e99e47deafe53658d34497c0a891d95ff1ba8ff6663f01c"}, + {file = "chardet-7.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23163921dccf3103ce59540b0443c106d2c0a0ff2e0503e05196f5e6fdea453f"}, + {file = "chardet-7.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cfb54563fe5f130da17c44c6a4e2e8052ba628e5ab4eab7ef8190f736f0f8f72"}, + {file = "chardet-7.4.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3990fffcc6a6045f2234ab72752ad037e3b2d48c72037f244d42738db397eb75"}, + {file = "chardet-7.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c7116b0452994734ccff35e154b44240090eb0f4f74b9106292668133557c175"}, + {file = "chardet-7.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:25a862cddc6a9ac07023e808aedd297115345fbaabc2690479481ddc0f980e09"}, + {file = "chardet-7.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7005c88da26fd95d8abb8acbe6281d833e9a9181b03cf49b4546c4555389bd97"}, + {file = "chardet-7.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc50f28bad067393cce0af9091052c3b8df7a23115afd8ba7b2e0947f0cef1f8"}, + {file = "chardet-7.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3da294de1a681097848ab58bd3f2771a674f8039d2d87a5538b28856b815e9"}, + {file = "chardet-7.4.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c45e116dd51b66226a53ade3f9f635e870de5399b90e00ce45dcc311093bf4"}, + {file = "chardet-7.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:ccc1f83ab4bcfb901cf39e0c4ba6bc6e726fc6264735f10e24ceb5cb47387578"}, + {file = "chardet-7.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:75d3c65cc16bddf40b8da1fd25ba84fca5f8070f2b14e86083653c1c85aee971"}, + {file = "chardet-7.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:29af5999f654e8729d251f1724a62b538b1262d9292cccaefddf8a02aae1ef6a"}, + {file = "chardet-7.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:626f00299ad62dfe937058a09572beed442ccc7b58f87aa667949b20fd3db235"}, + {file = "chardet-7.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9a4904dd5f071b7a7d7f50b4a67a86db3c902d243bf31708f1d5cde2f68239cb"}, + {file = "chardet-7.4.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5d2879598bc220689e8ce509fe9c3f37ad2fca53a36be9c9bd91abdd91dd364f"}, + {file = "chardet-7.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:4b2799bd58e7245cfa8d4ab2e8ad1d76a5c3a5b1f32318eb6acca4c69a3e7101"}, + {file = "chardet-7.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a9e4486df251b8962e86ea9f139ca235aa6e0542a00f7844c9a04160afb99aa9"}, + {file = "chardet-7.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4fbff1907925b0c5a1064cffb5e040cd5e338585c9c552625f30de6bc2f3107a"}, + {file = "chardet-7.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:365135eaf37ba65a828f8e668eb0a8c38c479dcbec724dc25f4dfd781049c357"}, + {file = "chardet-7.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfc134b70c846c21ead8e43ada3ae1a805fff732f6922f8abcf2ff27b8f6493d"}, + {file = "chardet-7.4.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9acd9988a93e09390f3cd231201ea7166c415eb8da1b735928990ffc05cb9fbb"}, + {file = "chardet-7.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:e1b98790c284ff813f18f7cf7de5f05ea2435a080030c7f1a8318f3a4f80b131"}, + {file = "chardet-7.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d892d3dcd652fdef53e3d6327d39b17c0df40a899dfc919abaeb64c974497531"}, + {file = "chardet-7.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:acc46d1b8b7d5783216afe15db56d1c179b9a40e5a1558bc13164c4fd20674c4"}, + {file = "chardet-7.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ac3bf11c645734a1701a3804e43eabd98851838192267d08c353a834ab79fea"}, + {file = "chardet-7.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e3bd9f936e04bae89c254262af08d9e5b98f805175ba1e29d454e6cba3107b7"}, + {file = "chardet-7.4.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:27cc23da03630cdecc9aa81a895aa86629c211f995cd57651f0fbc280717bf93"}, + {file = "chardet-7.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:b95c934b9ad59e2ba8abb9be49df70d3ad1b0d95d864b9fdb7588d4fa8bd921c"}, + {file = "chardet-7.4.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c77867f0c1cb8bd819502249fcdc500364aedb07881e11b743726fa2148e7b6e"}, + {file = "chardet-7.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cf1efeaf65a6ef2f5b9cc3a1df6f08ba2831b369ccaa4c7018eaf90aa757bb11"}, + {file = "chardet-7.4.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f3504c139a2ad544077dd2d9e412cd08b01786843d76997cd43bb6de311723c"}, + {file = "chardet-7.4.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457f619882ba66327d4d8d14c6c342269bdb1e4e1c38e8117df941d14d351b04"}, + {file = "chardet-7.4.3-py3-none-any.whl", hash = "sha256:1173b74051570cf08099d7429d92e4882d375ad4217f92a6e5240ccfb26f231e"}, + {file = "chardet-7.4.3.tar.gz", hash = "sha256:cc1d4eb92a4ec1c2df3b490836ffa46922e599d34ce0bb75cf41fd2bf6303d56"}, +] + [[package]] name = "charset-normalizer" -version = "3.4.4" +version = "3.4.7" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" groups = ["main", "docs"] files = [ - {file = "charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-win32.whl", hash = "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50"}, - {file = "charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f"}, - {file = "charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e5f4d355f0a2b1a31bc3edec6795b46324349c9cb25eed068049e4f472fb4259"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16d971e29578a5e97d7117866d15889a4a07befe0e87e703ed63cd90cb348c01"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dca4bbc466a95ba9c0234ef56d7dd9509f63da22274589ebd4ed7f1f4d4c54e3"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e80c8378d8f3d83cd3164da1ad2df9e37a666cdde7b1cb2298ed0b558064be30"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36836d6ff945a00b88ba1e4572d721e60b5b8c98c155d465f56ad19d68f23734"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_armv7l.whl", hash = "sha256:bd9b23791fe793e4968dba0c447e12f78e425c59fc0e3b97f6450f4781f3ee60"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aef65cd602a6d0e0ff6f9930fcb1c8fec60dd2cfcb6facaf4bdb0e5873042db0"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:82b271f5137d07749f7bf32f70b17ab6eaabedd297e75dce75081a24f76eb545"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:1efde3cae86c8c273f1eb3b287be7d8499420cf2fe7585c41d370d3e790054a5"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:c593052c465475e64bbfe5dbd81680f64a67fdc752c56d7a0ae205dc8aeefe0f"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:af21eb4409a119e365397b2adbaca4c9ccab56543a65d5dbd9f920d6ac29f686"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:84c018e49c3bf790f9c2771c45e9313a08c2c2a6342b162cd650258b57817706"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dd915403e231e6b1809fe9b6d9fc55cf8fb5e02765ac625d9cd623342a7905d7"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-win32.whl", hash = "sha256:320ade88cfb846b8cd6b4ddf5ee9e80ee0c1f52401f2456b84ae1ae6a1a5f207"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:1dc8b0ea451d6e69735094606991f32867807881400f808a106ee1d963c46a83"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:177a0ba5f0211d488e295aaf82707237e331c24788d8d76c96c5a41594723217"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e0d51f618228538a3e8f46bd246f87a6cd030565e015803691603f55e12afb5"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:14265bfe1f09498b9d8ec91e9ec9fa52775edf90fcbde092b25f4a33d444fea9"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:87fad7d9ba98c86bcb41b2dc8dbb326619be2562af1f8ff50776a39e55721c5a"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f22dec1690b584cea26fade98b2435c132c1b5f68e39f5a0b7627cd7ae31f1dc"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:d61f00a0869d77422d9b2aba989e2d24afa6ffd552af442e0e58de4f35ea6d00"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6370e8686f662e6a3941ee48ed4742317cafbe5707e36406e9df792cdb535776"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a6c5863edfbe888d9eff9c8b8087354e27618d9da76425c119293f11712a6319"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ed065083d0898c9d5b4bbec7b026fd755ff7454e6e8b73a67f8c744b13986e24"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2cd4a60d0e2fb04537162c62bbbb4182f53541fe0ede35cdf270a1c1e723cc42"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:813c0e0132266c08eb87469a642cb30aaff57c5f426255419572aaeceeaa7bf4"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:07d9e39b01743c3717745f4c530a6349eadbfa043c7577eef86c502c15df2c67"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c0f081d69a6e58272819b70288d3221a6ee64b98df852631c80f293514d3b274"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-win32.whl", hash = "sha256:8751d2787c9131302398b11e6c8068053dcb55d5a8964e114b6e196cf16cb366"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:12a6fff75f6bc66711b73a2f0addfc4c8c15a20e805146a02d147a318962c444"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:bb8cc7534f51d9a017b93e3e85b260924f909601c3df002bcdb58ddb4dc41a5c"}, + {file = "charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d"}, + {file = "charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5"}, ] [[package]] name = "click" -version = "8.3.1" +version = "8.3.2" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6"}, - {file = "click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a"}, + {file = "click-8.3.2-py3-none-any.whl", hash = "sha256:1924d2c27c5653561cd2cae4548d1406039cb79b858b747cfea24924bbc1616d"}, + {file = "click-8.3.2.tar.gz", hash = "sha256:14162b8b3b3550a7d479eafa77dfd3c38d9dc8951f6f69c78913a8f9a7540fd5"}, ] [package.dependencies] @@ -523,118 +586,118 @@ test = ["pytest"] [[package]] name = "coverage" -version = "7.13.4" +version = "7.13.5" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.10" groups = ["test"] files = [ - {file = "coverage-7.13.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fc31c787a84f8cd6027eba44010517020e0d18487064cd3d8968941856d1415"}, - {file = "coverage-7.13.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a32ebc02a1805adf637fc8dec324b5cdacd2e493515424f70ee33799573d661b"}, - {file = "coverage-7.13.4-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e24f9156097ff9dc286f2f913df3a7f63c0e333dcafa3c196f2c18b4175ca09a"}, - {file = "coverage-7.13.4-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8041b6c5bfdc03257666e9881d33b1abc88daccaf73f7b6340fb7946655cd10f"}, - {file = "coverage-7.13.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a09cfa6a5862bc2fc6ca7c3def5b2926194a56b8ab78ffcf617d28911123012"}, - {file = "coverage-7.13.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:296f8b0af861d3970c2a4d8c91d48eb4dd4771bcef9baedec6a9b515d7de3def"}, - {file = "coverage-7.13.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e101609bcbbfb04605ea1027b10dc3735c094d12d40826a60f897b98b1c30256"}, - {file = "coverage-7.13.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aa3feb8db2e87ff5e6d00d7e1480ae241876286691265657b500886c98f38bda"}, - {file = "coverage-7.13.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4fc7fa81bbaf5a02801b65346c8b3e657f1d93763e58c0abdf7c992addd81a92"}, - {file = "coverage-7.13.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:33901f604424145c6e9c2398684b92e176c0b12df77d52db81c20abd48c3794c"}, - {file = "coverage-7.13.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:bb28c0f2cf2782508a40cec377935829d5fcc3ad9a3681375af4e84eb34b6b58"}, - {file = "coverage-7.13.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9d107aff57a83222ddbd8d9ee705ede2af2cc926608b57abed8ef96b50b7e8f9"}, - {file = "coverage-7.13.4-cp310-cp310-win32.whl", hash = "sha256:a6f94a7d00eb18f1b6d403c91a88fd58cfc92d4b16080dfdb774afc8294469bf"}, - {file = "coverage-7.13.4-cp310-cp310-win_amd64.whl", hash = "sha256:2cb0f1e000ebc419632bbe04366a8990b6e32c4e0b51543a6484ffe15eaeda95"}, - {file = "coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053"}, - {file = "coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11"}, - {file = "coverage-7.13.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3998e5a32e62fdf410c0dbd3115df86297995d6e3429af80b8798aad894ca7aa"}, - {file = "coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7"}, - {file = "coverage-7.13.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3aa4e7b9e416774b21797365b358a6e827ffadaaca81b69ee02946852449f00"}, - {file = "coverage-7.13.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:71ca20079dd8f27fcf808817e281e90220475cd75115162218d0e27549f95fef"}, - {file = "coverage-7.13.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e2f25215f1a359ab17320b47bcdaca3e6e6356652e8256f2441e4ef972052903"}, - {file = "coverage-7.13.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d65b2d373032411e86960604dc4edac91fdfb5dca539461cf2cbe78327d1e64f"}, - {file = "coverage-7.13.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94eb63f9b363180aff17de3e7c8760c3ba94664ea2695c52f10111244d16a299"}, - {file = "coverage-7.13.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e856bf6616714c3a9fbc270ab54103f4e685ba236fa98c054e8f87f266c93505"}, - {file = "coverage-7.13.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:65dfcbe305c3dfe658492df2d85259e0d79ead4177f9ae724b6fb245198f55d6"}, - {file = "coverage-7.13.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b507778ae8a4c915436ed5c2e05b4a6cecfa70f734e19c22a005152a11c7b6a9"}, - {file = "coverage-7.13.4-cp311-cp311-win32.whl", hash = "sha256:784fc3cf8be001197b652d51d3fd259b1e2262888693a4636e18879f613a62a9"}, - {file = "coverage-7.13.4-cp311-cp311-win_amd64.whl", hash = "sha256:2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f"}, - {file = "coverage-7.13.4-cp311-cp311-win_arm64.whl", hash = "sha256:79e73a76b854d9c6088fe5d8b2ebe745f8681c55f7397c3c0a016192d681045f"}, - {file = "coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459"}, - {file = "coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3"}, - {file = "coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634"}, - {file = "coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3"}, - {file = "coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa"}, - {file = "coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3"}, - {file = "coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a"}, - {file = "coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7"}, - {file = "coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc"}, - {file = "coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47"}, - {file = "coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985"}, - {file = "coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0"}, - {file = "coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246"}, - {file = "coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126"}, - {file = "coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d"}, - {file = "coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9"}, - {file = "coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac"}, - {file = "coverage-7.13.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea"}, - {file = "coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b"}, - {file = "coverage-7.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525"}, - {file = "coverage-7.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242"}, - {file = "coverage-7.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148"}, - {file = "coverage-7.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a"}, - {file = "coverage-7.13.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23"}, - {file = "coverage-7.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80"}, - {file = "coverage-7.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea"}, - {file = "coverage-7.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a"}, - {file = "coverage-7.13.4-cp313-cp313-win32.whl", hash = "sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d"}, - {file = "coverage-7.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd"}, - {file = "coverage-7.13.4-cp313-cp313-win_arm64.whl", hash = "sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af"}, - {file = "coverage-7.13.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d"}, - {file = "coverage-7.13.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12"}, - {file = "coverage-7.13.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b"}, - {file = "coverage-7.13.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9"}, - {file = "coverage-7.13.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092"}, - {file = "coverage-7.13.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9"}, - {file = "coverage-7.13.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26"}, - {file = "coverage-7.13.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2"}, - {file = "coverage-7.13.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940"}, - {file = "coverage-7.13.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c"}, - {file = "coverage-7.13.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0"}, - {file = "coverage-7.13.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b"}, - {file = "coverage-7.13.4-cp313-cp313t-win32.whl", hash = "sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9"}, - {file = "coverage-7.13.4-cp313-cp313t-win_amd64.whl", hash = "sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd"}, - {file = "coverage-7.13.4-cp313-cp313t-win_arm64.whl", hash = "sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997"}, - {file = "coverage-7.13.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601"}, - {file = "coverage-7.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689"}, - {file = "coverage-7.13.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c"}, - {file = "coverage-7.13.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129"}, - {file = "coverage-7.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552"}, - {file = "coverage-7.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a"}, - {file = "coverage-7.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356"}, - {file = "coverage-7.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71"}, - {file = "coverage-7.13.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5"}, - {file = "coverage-7.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98"}, - {file = "coverage-7.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5"}, - {file = "coverage-7.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0"}, - {file = "coverage-7.13.4-cp314-cp314-win32.whl", hash = "sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb"}, - {file = "coverage-7.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505"}, - {file = "coverage-7.13.4-cp314-cp314-win_arm64.whl", hash = "sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2"}, - {file = "coverage-7.13.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056"}, - {file = "coverage-7.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc"}, - {file = "coverage-7.13.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9"}, - {file = "coverage-7.13.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf"}, - {file = "coverage-7.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55"}, - {file = "coverage-7.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72"}, - {file = "coverage-7.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a"}, - {file = "coverage-7.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6"}, - {file = "coverage-7.13.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3"}, - {file = "coverage-7.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750"}, - {file = "coverage-7.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39"}, - {file = "coverage-7.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0"}, - {file = "coverage-7.13.4-cp314-cp314t-win32.whl", hash = "sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea"}, - {file = "coverage-7.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932"}, - {file = "coverage-7.13.4-cp314-cp314t-win_arm64.whl", hash = "sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b"}, - {file = "coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0"}, - {file = "coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91"}, + {file = "coverage-7.13.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0723d2c96324561b9aa76fb982406e11d93cdb388a7a7da2b16e04719cf7ca5"}, + {file = "coverage-7.13.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52f444e86475992506b32d4e5ca55c24fc88d73bcbda0e9745095b28ef4dc0cf"}, + {file = "coverage-7.13.5-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:704de6328e3d612a8f6c07000a878ff38181ec3263d5a11da1db294fa6a9bdf8"}, + {file = "coverage-7.13.5-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a1a6d79a14e1ec1832cabc833898636ad5f3754a678ef8bb4908515208bf84f4"}, + {file = "coverage-7.13.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79060214983769c7ba3f0cee10b54c97609dca4d478fa1aa32b914480fd5738d"}, + {file = "coverage-7.13.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:356e76b46783a98c2a2fe81ec79df4883a1e62895ea952968fb253c114e7f930"}, + {file = "coverage-7.13.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0cef0cdec915d11254a7f549c1170afecce708d30610c6abdded1f74e581666d"}, + {file = "coverage-7.13.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dc022073d063b25a402454e5712ef9e007113e3a676b96c5f29b2bda29352f40"}, + {file = "coverage-7.13.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9b74db26dfea4f4e50d48a4602207cd1e78be33182bc9cbf22da94f332f99878"}, + {file = "coverage-7.13.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ad146744ca4fd09b50c482650e3c1b1f4dfa1d4792e0a04a369c7f23336f0400"}, + {file = "coverage-7.13.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:c555b48be1853fe3997c11c4bd521cdd9a9612352de01fa4508f16ec341e6fe0"}, + {file = "coverage-7.13.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7034b5c56a58ae5e85f23949d52c14aca2cfc6848a31764995b7de88f13a1ea0"}, + {file = "coverage-7.13.5-cp310-cp310-win32.whl", hash = "sha256:eb7fdf1ef130660e7415e0253a01a7d5a88c9c4d158bcf75cbbd922fd65a5b58"}, + {file = "coverage-7.13.5-cp310-cp310-win_amd64.whl", hash = "sha256:3e1bb5f6c78feeb1be3475789b14a0f0a5b47d505bfc7267126ccbd50289999e"}, + {file = "coverage-7.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66a80c616f80181f4d643b0f9e709d97bcea413ecd9631e1dedc7401c8e6695d"}, + {file = "coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587"}, + {file = "coverage-7.13.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0672854dc733c342fa3e957e0605256d2bf5934feeac328da9e0b5449634a642"}, + {file = "coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b"}, + {file = "coverage-7.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be3d4bbad9d4b037791794ddeedd7d64a56f5933a2c1373e18e9e568b9141686"}, + {file = "coverage-7.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d2afbc5cc54d286bfb54541aa50b64cdb07a718227168c87b9e2fb8f25e1743"}, + {file = "coverage-7.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3ad050321264c49c2fa67bb599100456fc51d004b82534f379d16445da40fb75"}, + {file = "coverage-7.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7300c8a6d13335b29bb76d7651c66af6bd8658517c43499f110ddc6717bfc209"}, + {file = "coverage-7.13.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eb07647a5738b89baab047f14edd18ded523de60f3b30e75c2acc826f79c839a"}, + {file = "coverage-7.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9adb6688e3b53adffefd4a52d72cbd8b02602bfb8f74dcd862337182fd4d1a4e"}, + {file = "coverage-7.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7c8d4bc913dd70b93488d6c496c77f3aff5ea99a07e36a18f865bca55adef8bd"}, + {file = "coverage-7.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e3c426ffc4cd952f54ee9ffbdd10345709ecc78a3ecfd796a57236bfad0b9b8"}, + {file = "coverage-7.13.5-cp311-cp311-win32.whl", hash = "sha256:259b69bb83ad9894c4b25be2528139eecba9a82646ebdda2d9db1ba28424a6bf"}, + {file = "coverage-7.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:258354455f4e86e3e9d0d17571d522e13b4e1e19bf0f8596bcf9476d61e7d8a9"}, + {file = "coverage-7.13.5-cp311-cp311-win_arm64.whl", hash = "sha256:bff95879c33ec8da99fc9b6fe345ddb5be6414b41d6d1ad1c8f188d26f36e028"}, + {file = "coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01"}, + {file = "coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422"}, + {file = "coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f"}, + {file = "coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5"}, + {file = "coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376"}, + {file = "coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256"}, + {file = "coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c"}, + {file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5"}, + {file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09"}, + {file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9"}, + {file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf"}, + {file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c"}, + {file = "coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf"}, + {file = "coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810"}, + {file = "coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de"}, + {file = "coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1"}, + {file = "coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3"}, + {file = "coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26"}, + {file = "coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3"}, + {file = "coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b"}, + {file = "coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a"}, + {file = "coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969"}, + {file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161"}, + {file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15"}, + {file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1"}, + {file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6"}, + {file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17"}, + {file = "coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85"}, + {file = "coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b"}, + {file = "coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664"}, + {file = "coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d"}, + {file = "coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0"}, + {file = "coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806"}, + {file = "coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3"}, + {file = "coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9"}, + {file = "coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd"}, + {file = "coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606"}, + {file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e"}, + {file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0"}, + {file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87"}, + {file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479"}, + {file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2"}, + {file = "coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a"}, + {file = "coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819"}, + {file = "coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911"}, + {file = "coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f"}, + {file = "coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e"}, + {file = "coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a"}, + {file = "coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510"}, + {file = "coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247"}, + {file = "coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6"}, + {file = "coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0"}, + {file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882"}, + {file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740"}, + {file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16"}, + {file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0"}, + {file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0"}, + {file = "coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc"}, + {file = "coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633"}, + {file = "coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8"}, + {file = "coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b"}, + {file = "coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c"}, + {file = "coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9"}, + {file = "coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29"}, + {file = "coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607"}, + {file = "coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90"}, + {file = "coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3"}, + {file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab"}, + {file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562"}, + {file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2"}, + {file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea"}, + {file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a"}, + {file = "coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215"}, + {file = "coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43"}, + {file = "coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45"}, + {file = "coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61"}, + {file = "coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179"}, ] [package.dependencies] @@ -645,22 +708,27 @@ toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "cssutils" -version = "2.11.1" +version = "2.14.0" description = "A CSS Cascading Style Sheets library for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" groups = ["docs"] files = [ - {file = "cssutils-2.11.1-py3-none-any.whl", hash = "sha256:a67bfdfdff4f3867fab43698ec4897c1a828eca5973f4073321b3bccaf1199b1"}, - {file = "cssutils-2.11.1.tar.gz", hash = "sha256:0563a76513b6af6eebbe788c3bf3d01c920e46b3f90c8416738c5cfc773ff8e2"}, + {file = "cssutils-2.14.0-py3-none-any.whl", hash = "sha256:79ad979e4a383f39f0b3f0ca82ee3f1b01065da9fa02701b63bfed38ac76eb91"}, + {file = "cssutils-2.14.0.tar.gz", hash = "sha256:c33256f0cbc215ad405b647117ace63c9e22af96fe42dcb7861742a591e6464c"}, ] [package.dependencies] -more-itertools = "*" +encutils = "*" +more_itertools = "*" [package.extras] +check = ["pytest-checkdocs (>=2.14)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -test = ["cssselect", "importlib-resources ; python_version < \"3.9\"", "jaraco.test (>=5.1)", "lxml ; python_version < \"3.11\"", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] +enabler = ["pytest-enabler (>=3.4)"] +test = ["cssselect", "importlib_resources ; python_version < \"3.9\"", "jaraco.test (>=5.1)", "lxml ; python_version < \"3.11\"", "pytest (>=6,!=8.1.*)"] +type = ["pytest-mypy (>=1.0.1) ; platform_python_implementation != \"PyPy\""] [[package]] name = "debugpy" @@ -790,6 +858,21 @@ typing-extensions = ">=3.7.4.1" all = ["pytz (>=2019.1)"] dates = ["pytz (>=2019.1)"] +[[package]] +name = "encutils" +version = "1.0.0" +description = "" +optional = false +python-versions = ">=3.10" +groups = ["docs"] +files = [ + {file = "encutils-1.0.0-py3-none-any.whl", hash = "sha256:605297da19a23d1b2da7d3b9bd75513acc979e9facf03aa7ec7ba04b5f567a79"}, + {file = "encutils-1.0.0.tar.gz", hash = "sha256:38eca5af18cebabd8be43c17f14c9d3fbba83cc5f7ac8e3ab1c86e24c4b2b91a"}, +] + +[package.dependencies] +chardet = "*" + [[package]] name = "enum-tools" version = "0.12.0" @@ -876,14 +959,14 @@ devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benc [[package]] name = "filelock" -version = "3.24.2" +version = "3.29.0" description = "A platform independent file lock." optional = false python-versions = ">=3.10" groups = ["docs"] files = [ - {file = "filelock-3.24.2-py3-none-any.whl", hash = "sha256:667d7dc0b7d1e1064dd5f8f8e80bdac157a6482e8d2e02cd16fd3b6b33bd6556"}, - {file = "filelock-3.24.2.tar.gz", hash = "sha256:c22803117490f156e59fafce621f0550a7a853e2bbf4f87f112b11d469b6c81b"}, + {file = "filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258"}, + {file = "filelock-3.29.0.tar.gz", hash = "sha256:69974355e960702e789734cb4871f884ea6fe50bd8404051a3530bc07809cf90"}, ] [[package]] @@ -954,40 +1037,54 @@ all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2 [[package]] name = "imagesize" -version = "1.4.1" +version = "1.5.0" description = "Getting image size from png/jpeg/jpeg2000/gif file" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" groups = ["docs"] +markers = "python_version >= \"3.12\"" files = [ - {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, - {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, + {file = "imagesize-1.5.0-py2.py3-none-any.whl", hash = "sha256:32677681b3f434c2cb496f00e89c5a291247b35b1f527589909e008057da5899"}, + {file = "imagesize-1.5.0.tar.gz", hash = "sha256:8bfc5363a7f2133a89f0098451e0bcb1cd71aba4dc02bbcecb39d99d40e1b94f"}, +] + +[[package]] +name = "imagesize" +version = "2.0.0" +description = "Get image size from headers (BMP/PNG/JPEG/JPEG2000/GIF/TIFF/SVG/Netpbm/WebP/AVIF/HEIC/HEIF)" +optional = false +python-versions = "<3.15,>=3.10" +groups = ["docs"] +markers = "python_version < \"3.12\"" +files = [ + {file = "imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96"}, + {file = "imagesize-2.0.0.tar.gz", hash = "sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3"}, ] [[package]] name = "importlib-metadata" -version = "8.7.1" +version = "9.0.0" description = "Read metadata from Python packages" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "python_version < \"3.12\"" files = [ - {file = "importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151"}, - {file = "importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb"}, + {file = "importlib_metadata-9.0.0-py3-none-any.whl", hash = "sha256:2d21d1cc5a017bd0559e36150c21c830ab1dc304dedd1b7ea85d20f45ef3edd7"}, + {file = "importlib_metadata-9.0.0.tar.gz", hash = "sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc"}, ] [package.dependencies] zipp = ">=3.20" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +check = ["pytest-checkdocs (>=2.14)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=3.4)"] perf = ["ipython"] -test = ["flufl.flake8", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] -type = ["mypy (<1.19) ; platform_python_implementation == \"PyPy\"", "pytest-mypy (>=1.0.1)"] +test = ["packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["pytest-mypy (>=1.0.1) ; platform_python_implementation != \"PyPy\""] [[package]] name = "iniconfig" @@ -1018,7 +1115,7 @@ pfzy = ">=0.3.1,<0.4.0" prompt-toolkit = ">=3.0.1,<4.0.0" [package.extras] -docs = ["Sphinx (>=4.1.2,<5.0.0)", "furo (>=2021.8.17b43,<2022.0.0)", "myst-parser (>=0.15.1,<0.16.0)", "sphinx-autobuild (>=2021.3.14,<2022.0.0)", "sphinx-copybutton (>=0.4.0,<0.5.0)"] +docs = ["Sphinx (>=4.1.2,<5.0.0)", "furo (>=2021.8.17-beta.43,<2022.0.0)", "myst-parser (>=0.15.1,<0.16.0)", "sphinx-autobuild (>=2021.3.14,<2022.0.0)", "sphinx-copybutton (>=0.4.0,<0.5.0)"] [[package]] name = "ipykernel" @@ -1056,15 +1153,15 @@ test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0,<9)", "pytest-async [[package]] name = "ipython" -version = "8.38.0" +version = "8.39.0" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.10" groups = ["dev"] markers = "python_version == \"3.10\"" files = [ - {file = "ipython-8.38.0-py3-none-any.whl", hash = "sha256:750162629d800ac65bb3b543a14e7a74b0e88063eac9b92124d4b2aa3f6d8e86"}, - {file = "ipython-8.38.0.tar.gz", hash = "sha256:9cfea8c903ce0867cc2f23199ed8545eb741f3a69420bfcf3743ad1cec856d39"}, + {file = "ipython-8.39.0-py3-none-any.whl", hash = "sha256:bb3c51c4fa8148ab1dea07a79584d1c854e234ea44aa1283bcb37bc75054651f"}, + {file = "ipython-8.39.0.tar.gz", hash = "sha256:4110ae96012c379b8b6db898a07e186c40a2a1ef5d57a7fa83166047d9da7624"}, ] [package.dependencies] @@ -1096,15 +1193,15 @@ test-extra = ["curio", "ipython[test]", "jupyter_ai", "matplotlib (!=3.2.0)", "n [[package]] name = "ipython" -version = "9.10.0" +version = "9.10.1" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.11" groups = ["dev"] -markers = "python_version >= \"3.11\"" +markers = "python_version == \"3.11\"" files = [ - {file = "ipython-9.10.0-py3-none-any.whl", hash = "sha256:c6ab68cc23bba8c7e18e9b932797014cc61ea7fd6f19de180ab9ba73e65ee58d"}, - {file = "ipython-9.10.0.tar.gz", hash = "sha256:cd9e656be97618a0676d058134cd44e6dc7012c0e5cb36a9ce96a8c904adaf77"}, + {file = "ipython-9.10.1-py3-none-any.whl", hash = "sha256:82d18ae9fb9164ded080c71ef92a182ee35ee7db2395f67616034bebb020a232"}, + {file = "ipython-9.10.1.tar.gz", hash = "sha256:e170e9b2a44312484415bdb750492699bf329233b03f2557a9692cce6466ada4"}, ] [package.dependencies] @@ -1128,6 +1225,39 @@ matplotlib = ["matplotlib (>3.9)"] test = ["packaging (>=20.1.0)", "pytest (>=7.0.0)", "pytest-asyncio (>=1.0.0)", "setuptools (>=61.2)", "testpath (>=0.2)"] test-extra = ["curio", "ipykernel (>6.30)", "ipython[matplotlib]", "ipython[test]", "jupyter_ai", "nbclient", "nbformat", "numpy (>=1.27)", "pandas (>2.1)", "trio (>=0.1.0)"] +[[package]] +name = "ipython" +version = "9.12.0" +description = "IPython: Productive Interactive Computing" +optional = false +python-versions = ">=3.12" +groups = ["dev"] +markers = "python_version >= \"3.12\"" +files = [ + {file = "ipython-9.12.0-py3-none-any.whl", hash = "sha256:0f2701e8ee86e117e37f50563205d36feaa259d2e08d4a6bc6b6d74b18ce128d"}, + {file = "ipython-9.12.0.tar.gz", hash = "sha256:01daa83f504b693ba523b5a407246cabde4eb4513285a3c6acaff11a66735ee4"}, +] + +[package.dependencies] +colorama = {version = ">=0.4.4", markers = "sys_platform == \"win32\""} +decorator = ">=5.1.0" +ipython-pygments-lexers = ">=1.0.0" +jedi = ">=0.18.2" +matplotlib-inline = ">=0.1.6" +pexpect = {version = ">4.6", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""} +prompt_toolkit = ">=3.0.41,<3.1.0" +pygments = ">=2.14.0" +stack_data = ">=0.6.0" +traitlets = ">=5.13.0" + +[package.extras] +all = ["argcomplete (>=3.0)", "ipython[doc,matplotlib,terminal,test,test-extra]", "types-decorator"] +black = ["black"] +doc = ["docrepr", "exceptiongroup", "intersphinx_registry", "ipykernel", "ipython[matplotlib,test]", "setuptools (>=80.0)", "sphinx (>=8.0)", "sphinx-rtd-theme (>=0.1.8)", "sphinx_toml (==0.0.4)", "typing_extensions"] +matplotlib = ["matplotlib (>3.9)"] +test = ["packaging (>=23.0.0)", "pytest (>=7.0.0)", "pytest-asyncio (>=1.0.0)", "setuptools (>=80.0)", "testpath (>=0.2)"] +test-extra = ["curio", "ipykernel (>6.30)", "ipython[matplotlib]", "ipython[test]", "jupyter_ai", "nbclient", "nbformat", "numpy (>=2.0)", "pandas (>2.1)", "trio (>=0.22.0)"] + [[package]] name = "ipython-pygments-lexers" version = "1.1.1" @@ -1225,7 +1355,7 @@ files = [ [package.dependencies] attrs = ">=22.2.0" -jsonschema-specifications = ">=2023.3.6" +jsonschema-specifications = ">=2023.03.6" referencing = ">=0.28.4" rpds-py = ">=0.25.0" @@ -1507,14 +1637,14 @@ typing-extensions = {version = "*", markers = "python_version < \"3.11\""} [[package]] name = "more-itertools" -version = "10.8.0" +version = "11.0.2" description = "More routines for operating on iterables, beyond itertools" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["docs"] files = [ - {file = "more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b"}, - {file = "more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd"}, + {file = "more_itertools-11.0.2-py3-none-any.whl", hash = "sha256:6e35b35f818b01f691643c6c611bc0902f2e92b46c18fffa77ae1e7c46e912e4"}, + {file = "more_itertools-11.0.2.tar.gz", hash = "sha256:392a9e1e362cbc106a2457d37cabf9b36e5e12efd4ebff1654630e76597df804"}, ] [[package]] @@ -1657,14 +1787,14 @@ test = ["flaky", "ipykernel (>=6.19.3)", "ipython", "ipywidgets", "nbconvert (>= [[package]] name = "nbconvert" -version = "7.17.0" +version = "7.17.1" description = "Convert Jupyter Notebooks (.ipynb files) to other formats." optional = false python-versions = ">=3.9" groups = ["docs"] files = [ - {file = "nbconvert-7.17.0-py3-none-any.whl", hash = "sha256:4f99a63b337b9a23504347afdab24a11faa7d86b405e5c8f9881cd313336d518"}, - {file = "nbconvert-7.17.0.tar.gz", hash = "sha256:1b2696f1b5be12309f6c7d707c24af604b87dfaf6d950794c7b07acab96dda78"}, + {file = "nbconvert-7.17.1-py3-none-any.whl", hash = "sha256:aa85c087b435e7bf1ffd03319f658e285f2b89eccab33bc1ba7025495ab3e7c8"}, + {file = "nbconvert-7.17.1.tar.gz", hash = "sha256:34d0d0a7e73ce3cbab6c5aae8f4f468797280b01fd8bd2ca746da8569eddd7d2"}, ] [package.dependencies] @@ -1763,14 +1893,14 @@ rdflib = ">=7.1.4" [[package]] name = "packaging" -version = "26.0" +version = "26.1" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" groups = ["main", "dev", "docs", "test"] files = [ - {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, - {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, + {file = "packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f"}, + {file = "packaging-26.1.tar.gz", hash = "sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de"}, ] [[package]] @@ -1830,18 +1960,18 @@ files = [ ] [package.extras] -docs = ["Sphinx (>=4.1.2,<5.0.0)", "furo (>=2021.8.17b43,<2022.0.0)", "myst-parser (>=0.15.1,<0.16.0)", "sphinx-autobuild (>=2021.3.14,<2022.0.0)", "sphinx-copybutton (>=0.4.0,<0.5.0)"] +docs = ["Sphinx (>=4.1.2,<5.0.0)", "furo (>=2021.8.17-beta.43,<2022.0.0)", "myst-parser (>=0.15.1,<0.16.0)", "sphinx-autobuild (>=2021.3.14,<2022.0.0)", "sphinx-copybutton (>=0.4.0,<0.5.0)"] [[package]] name = "platformdirs" -version = "4.9.2" +version = "4.9.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.10" groups = ["main", "dev", "docs"] files = [ - {file = "platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd"}, - {file = "platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291"}, + {file = "platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917"}, + {file = "platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a"}, ] [[package]] @@ -1995,14 +2125,14 @@ files = [ [[package]] name = "pygments" -version = "2.19.2" +version = "2.20.0" description = "Pygments is a syntax highlighting package written in Python." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main", "dev", "docs", "test"] files = [ - {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, - {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, + {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, + {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, ] [package.extras] @@ -2100,14 +2230,14 @@ js = ["pyduktape2 (>=0.4.6,<1)"] [[package]] name = "pytest" -version = "9.0.2" +version = "9.0.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.10" groups = ["test"] files = [ - {file = "pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b"}, - {file = "pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11"}, + {file = "pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9"}, + {file = "pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c"}, ] [package.dependencies] @@ -2410,36 +2540,36 @@ typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""} [[package]] name = "requests" -version = "2.32.5" +version = "2.33.1" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main", "docs"] files = [ - {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, - {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, + {file = "requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a"}, + {file = "requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517"}, ] [package.dependencies] -certifi = ">=2017.4.17" +certifi = ">=2023.5.7" charset_normalizer = ">=2,<4" idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" +urllib3 = ">=1.26,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<8)"] [[package]] name = "requests-cache" -version = "1.3.0" +version = "1.3.1" description = "A persistent cache for python requests" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "requests_cache-1.3.0-py3-none-any.whl", hash = "sha256:f09f27bbf100c250886acf13a9db35b53cf2852fddd71977b47c71ea7d90dbba"}, - {file = "requests_cache-1.3.0.tar.gz", hash = "sha256:070e357ccef11a300ccef4294a85de1ab265833c5d9c9538b26cd7ba4085d54a"}, + {file = "requests_cache-1.3.1-py3-none-any.whl", hash = "sha256:43a67448c3b2964c631ac7027b84607f2f63438e28104b68ad2211f32d9f606c"}, + {file = "requests_cache-1.3.1.tar.gz", hash = "sha256:784e9d07f72db4fe234830a065230c59eb446489528f271ba288c640897e47c4"}, ] [package.dependencies] @@ -3128,14 +3258,14 @@ files = [ [[package]] name = "tabulate" -version = "0.9.0" +version = "0.10.0" description = "Pretty-print tabular data" optional = false -python-versions = ">=3.7" +python-versions = ">=3.10" groups = ["docs"] files = [ - {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, - {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, + {file = "tabulate-0.10.0-py3-none-any.whl", hash = "sha256:f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3"}, + {file = "tabulate-0.10.0.tar.gz", hash = "sha256:e2cfde8f79420f6deeffdeda9aaec3b6bc5abce947655d17ac662b126e48a60d"}, ] [package.extras] @@ -3174,60 +3304,60 @@ files = [ [[package]] name = "tomli" -version = "2.4.0" +version = "2.4.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" groups = ["dev", "docs", "test"] markers = "python_version == \"3.10\"" files = [ - {file = "tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867"}, - {file = "tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9"}, - {file = "tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95"}, - {file = "tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76"}, - {file = "tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d"}, - {file = "tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576"}, - {file = "tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a"}, - {file = "tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa"}, - {file = "tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614"}, - {file = "tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1"}, - {file = "tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8"}, - {file = "tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a"}, - {file = "tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1"}, - {file = "tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b"}, - {file = "tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51"}, - {file = "tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729"}, - {file = "tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da"}, - {file = "tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3"}, - {file = "tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0"}, - {file = "tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e"}, - {file = "tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4"}, - {file = "tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e"}, - {file = "tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c"}, - {file = "tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f"}, - {file = "tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86"}, - {file = "tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87"}, - {file = "tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132"}, - {file = "tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6"}, - {file = "tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc"}, - {file = "tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66"}, - {file = "tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d"}, - {file = "tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702"}, - {file = "tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8"}, - {file = "tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776"}, - {file = "tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475"}, - {file = "tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2"}, - {file = "tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9"}, - {file = "tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0"}, - {file = "tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df"}, - {file = "tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d"}, - {file = "tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f"}, - {file = "tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b"}, - {file = "tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087"}, - {file = "tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd"}, - {file = "tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4"}, - {file = "tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a"}, - {file = "tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c"}, + {file = "tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30"}, + {file = "tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a"}, + {file = "tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076"}, + {file = "tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9"}, + {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c"}, + {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc"}, + {file = "tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049"}, + {file = "tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e"}, + {file = "tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece"}, + {file = "tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a"}, + {file = "tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085"}, + {file = "tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9"}, + {file = "tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5"}, + {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585"}, + {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1"}, + {file = "tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917"}, + {file = "tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9"}, + {file = "tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257"}, + {file = "tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54"}, + {file = "tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a"}, + {file = "tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897"}, + {file = "tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f"}, + {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d"}, + {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5"}, + {file = "tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd"}, + {file = "tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36"}, + {file = "tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd"}, + {file = "tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf"}, + {file = "tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac"}, + {file = "tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662"}, + {file = "tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853"}, + {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15"}, + {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba"}, + {file = "tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6"}, + {file = "tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7"}, + {file = "tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232"}, + {file = "tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4"}, + {file = "tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c"}, + {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d"}, + {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41"}, + {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c"}, + {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f"}, + {file = "tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8"}, + {file = "tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26"}, + {file = "tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396"}, + {file = "tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe"}, + {file = "tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f"}, ] [[package]] @@ -3244,24 +3374,22 @@ files = [ [[package]] name = "tornado" -version = "6.5.4" +version = "6.5.5" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." optional = false python-versions = ">=3.9" groups = ["dev", "docs"] files = [ - {file = "tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d6241c1a16b1c9e4cc28148b1cda97dd1c6cb4fb7068ac1bedc610768dff0ba9"}, - {file = "tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2d50f63dda1d2cac3ae1fa23d254e16b5e38153758470e9956cbc3d813d40843"}, - {file = "tornado-6.5.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1cf66105dc6acb5af613c054955b8137e34a03698aa53272dbda4afe252be17"}, - {file = "tornado-6.5.4-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50ff0a58b0dc97939d29da29cd624da010e7f804746621c78d14b80238669335"}, - {file = "tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fb5e04efa54cf0baabdd10061eb4148e0be137166146fff835745f59ab9f7f"}, - {file = "tornado-6.5.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9c86b1643b33a4cd415f8d0fe53045f913bf07b4a3ef646b735a6a86047dda84"}, - {file = "tornado-6.5.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:6eb82872335a53dd063a4f10917b3efd28270b56a33db69009606a0312660a6f"}, - {file = "tornado-6.5.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6076d5dda368c9328ff41ab5d9dd3608e695e8225d1cd0fd1e006f05da3635a8"}, - {file = "tornado-6.5.4-cp39-abi3-win32.whl", hash = "sha256:1768110f2411d5cd281bac0a090f707223ce77fd110424361092859e089b38d1"}, - {file = "tornado-6.5.4-cp39-abi3-win_amd64.whl", hash = "sha256:fa07d31e0cd85c60713f2b995da613588aa03e1303d75705dca6af8babc18ddc"}, - {file = "tornado-6.5.4-cp39-abi3-win_arm64.whl", hash = "sha256:053e6e16701eb6cbe641f308f4c1a9541f91b6261991160391bfc342e8a551a1"}, - {file = "tornado-6.5.4.tar.gz", hash = "sha256:a22fa9047405d03260b483980635f0b041989d8bcc9a313f8fe18b411d84b1d7"}, + {file = "tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:487dc9cc380e29f58c7ab88f9e27cdeef04b2140862e5076a66fb6bb68bb1bfa"}, + {file = "tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:65a7f1d46d4bb41df1ac99f5fcb685fb25c7e61613742d5108b010975a9a6521"}, + {file = "tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e74c92e8e65086b338fd56333fb9a68b9f6f2fe7ad532645a290a464bcf46be5"}, + {file = "tornado-6.5.5-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:435319e9e340276428bbdb4e7fa732c2d399386d1de5686cb331ec8eee754f07"}, + {file = "tornado-6.5.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3f54aa540bdbfee7b9eb268ead60e7d199de5021facd276819c193c0fb28ea4e"}, + {file = "tornado-6.5.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36abed1754faeb80fbd6e64db2758091e1320f6bba74a4cf8c09cd18ccce8aca"}, + {file = "tornado-6.5.5-cp39-abi3-win32.whl", hash = "sha256:dd3eafaaeec1c7f2f8fdcd5f964e8907ad788fe8a5a32c4426fbbdda621223b7"}, + {file = "tornado-6.5.5-cp39-abi3-win_amd64.whl", hash = "sha256:6443a794ba961a9f619b1ae926a2e900ac20c34483eea67be4ed8f1e58d3ef7b"}, + {file = "tornado-6.5.5-cp39-abi3-win_arm64.whl", hash = "sha256:2c9a876e094109333f888539ddb2de4361743e5d21eece20688e3e351e4990a6"}, + {file = "tornado-6.5.5.tar.gz", hash = "sha256:192b8f3ea91bd7f1f50c06955416ed76c6b72f96779b962f07f911b91e8d30e9"}, ] [[package]] @@ -3295,22 +3423,22 @@ markers = {dev = "python_version < \"3.12\"", test = "python_version == \"3.10\" [[package]] name = "typos" -version = "1.43.5" +version = "1.45.1" description = "Source Code Spelling Correction" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "typos-1.43.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:733221c09d6914c001d2ac16f630773dc36ff384df9ab128c9e93b019eeedf2f"}, - {file = "typos-1.43.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b1d2f86b458917fdc91b89f47101498adc7338361e1ed0bed6e4325d0e674aca"}, - {file = "typos-1.43.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d75091c9519224f2f74964b6ca5133abfba9be44ce49f12a981e27c29bcef97"}, - {file = "typos-1.43.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:839dfa2bc802b02460097b21f7d93d0fa87d5f76adb83049072211a7279f5ebe"}, - {file = "typos-1.43.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0825a4d77d66726e86fac03c6cf2efc11b0a5c112b2156dcaeb61a24a807166"}, - {file = "typos-1.43.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7ee0a71ac21820868686ad52cb26a3e08197b09ceedf04d1736cb8472061665b"}, - {file = "typos-1.43.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:09615c8f64e656940533aa025a9e8dc5c93e5cf6bfbedb059cdcafde75cb6d72"}, - {file = "typos-1.43.5-py3-none-win32.whl", hash = "sha256:4c865b1b6149acbdaed9d548282ccfad526e1380de19d189e7ac675cd4869156"}, - {file = "typos-1.43.5-py3-none-win_amd64.whl", hash = "sha256:10009e9a3702da037803b075efc94fa5328bd93af357fb6a2f284c0dbcc77f30"}, - {file = "typos-1.43.5.tar.gz", hash = "sha256:aa445c5eaf0e32095c3183dda96e0fa8ffcabbfd796721c75d7a8a68e9cd0d75"}, + {file = "typos-1.45.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f3cd3d7e7e35f971e04974c7b34563dc1efb101841be3a39fec36c51f3d6ca2d"}, + {file = "typos-1.45.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:be6f26c580915e63df107f88bc766f131efe5f7d01d41c7bad83e6f9e5fe42be"}, + {file = "typos-1.45.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd6a6ccbb1fc4fb8f0d9fee0201642d7a7560bd1661ebbefb9eac2da1ae4a5c"}, + {file = "typos-1.45.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d33c7750a29524dff020a17f356ed079227f36f43ec57f193e9681606a35749b"}, + {file = "typos-1.45.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:745b0584eeead4593858671113fceed3c28b8ca67bdc7a517120127aa509c6a6"}, + {file = "typos-1.45.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e962d414fb92ad31dc4c930fc5d07ac9e4b55fdd4f42688468040fc5649d92da"}, + {file = "typos-1.45.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f39afdfcc2d159705f3ffb11162e13e8affd994d07836738c8d2a592194604ab"}, + {file = "typos-1.45.1-py3-none-win32.whl", hash = "sha256:212fdbb7b90d40522fe77efb69c15f7063c146812df01d5605e5d7816a3f37d3"}, + {file = "typos-1.45.1-py3-none-win_amd64.whl", hash = "sha256:67a56bd1f06184f3761883f4f75dd3cc196f939180de595d0980164d4a19d363"}, + {file = "typos-1.45.1.tar.gz", hash = "sha256:a1ac7ab02e74d4c4a2f8525b1529e1ce6261051df3229701836175fb91bb0583"}, ] [[package]] @@ -3375,15 +3503,15 @@ files = [ [[package]] name = "zipp" -version = "3.23.0" +version = "3.23.1" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" groups = ["main"] markers = "python_version < \"3.12\"" files = [ - {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, - {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, + {file = "zipp-3.23.1-py3-none-any.whl", hash = "sha256:0b3596c50a5c700c9cb40ba8d86d9f2cc4807e9bedb06bcdf7fac85633e444dc"}, + {file = "zipp-3.23.1.tar.gz", hash = "sha256:32120e378d32cd9714ad503c1d024619063ec28aad2248dc6672ad13edfa5110"}, ] [package.extras] diff --git a/pyproject.toml b/pyproject.toml index 100a9810..794420b0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "roc-validator" -version = "0.8.1" +version = "0.9.0" description = "A Python package to validate RO-Crates" authors = [ "Marco Enrico Piras ", diff --git a/rocrate_validator/cli/commands/validate.py b/rocrate_validator/cli/commands/validate.py index fac40d47..e574e667 100644 --- a/rocrate_validator/cli/commands/validate.py +++ b/rocrate_validator/cli/commands/validate.py @@ -25,7 +25,7 @@ from rich.rule import Rule from rocrate_validator.utils import log as logging -from rocrate_validator import services +from rocrate_validator import constants, services from rocrate_validator.cli.commands.errors import handle_error from rocrate_validator.cli.main import cli from rocrate_validator.cli.ui.text.validate import ValidationCommandView @@ -205,6 +205,29 @@ def validate_uri(ctx, param, value): show_default=True, help="Width of the output line", ) +@click.option( + '--cache-max-age', + type=click.INT, + default=constants.DEFAULT_HTTP_CACHE_MAX_AGE, + show_default=True, + help="Maximum age of the HTTP cache in seconds ([bold green]-1[/bold green] for no expiration)", +) +@click.option( + '--cache-path', + type=click.Path(), + default=None, + show_default=True, + help="Path to the HTTP cache directory", +) +@click.option( + '-nc', + '--no-cache', + is_flag=True, + help="Disable the HTTP cache", + default=False, + show_default=True, + hidden=True +) @click.pass_context def validate(ctx, profiles_path: Path = DEFAULT_PROFILES_PATH, @@ -223,7 +246,10 @@ def validate(ctx, verbose: bool = False, output_format: str = "text", output_file: Optional[Path] = None, - output_line_width: Optional[int] = None): + output_line_width: Optional[int] = None, + cache_max_age: int = constants.DEFAULT_HTTP_CACHE_MAX_AGE, + cache_path: Optional[Path] = None, + no_cache: bool = False): """ [magenta]rocrate-validator:[/magenta] Validate a RO-Crate against a profile """ @@ -247,6 +273,11 @@ def validate(ctx, logger.debug("fail_fast: %s", fail_fast) logger.debug("no fail fast: %s", not fail_fast) + # Cache settings + logger.debug("cache_max_age: %s", cache_max_age) + logger.debug("cache_path: %s", os.path.abspath(cache_path) if cache_path else None) + logger.debug("no_cache: %s", no_cache) + if rocrate_uri: logger.debug("rocrate_path: %s", os.path.abspath(rocrate_uri)) @@ -282,7 +313,9 @@ def validate(ctx, "rocrate_relative_root_path": relative_root_path, "abort_on_first": fail_fast, "skip_checks": skip_checks_list, - "metadata_only": metadata_only + "metadata_only": metadata_only, + "cache_max_age": cache_max_age if not no_cache else -1, + "cache_path": cache_path } # Print the application header diff --git a/rocrate_validator/constants.py b/rocrate_validator/constants.py index 0ed94f57..93aad322 100644 --- a/rocrate_validator/constants.py +++ b/rocrate_validator/constants.py @@ -87,5 +87,5 @@ JSON_OUTPUT_FORMAT_VERSION = "0.2" # Http Cache Settings -DEFAULT_HTTP_CACHE_TIMEOUT = 60 +DEFAULT_HTTP_CACHE_MAX_AGE = 300 # in seconds DEFAULT_HTTP_CACHE_PATH_PREFIX = '/tmp/rocrate_validator_cache' diff --git a/rocrate_validator/models.py b/rocrate_validator/models.py index 6fbd446c..ffaed96a 100644 --- a/rocrate_validator/models.py +++ b/rocrate_validator/models.py @@ -31,9 +31,9 @@ import enum_tools from rdflib import RDF, RDFS, Graph, Namespace, URIRef -from rocrate_validator.utils import log as logging from rocrate_validator import __version__ -from rocrate_validator.constants import (DEFAULT_ONTOLOGY_FILE, +from rocrate_validator.constants import (DEFAULT_HTTP_CACHE_MAX_AGE, + DEFAULT_ONTOLOGY_FILE, DEFAULT_PROFILE_IDENTIFIER, DEFAULT_PROFILE_README_FILE, IGNORED_PROFILE_DIRECTORIES, @@ -48,11 +48,13 @@ ROCrateMetadataNotFoundError) from rocrate_validator.events import Event, EventType, Publisher, Subscriber from rocrate_validator.rocrate import ROCrate -from rocrate_validator.utils.collections import (MapIndex) +from rocrate_validator.utils import log as logging +from rocrate_validator.utils.collections import MapIndex, MultiIndexMap +from rocrate_validator.utils.http import HttpRequester from rocrate_validator.utils.paths import get_profiles_path -from rocrate_validator.utils.python_helpers import get_requirement_name_from_file +from rocrate_validator.utils.python_helpers import \ + get_requirement_name_from_file from rocrate_validator.utils.uri import URI -from rocrate_validator.utils.collections import MultiIndexMap # set the default profiles path DEFAULT_PROFILES_PATH = get_profiles_path() @@ -1774,7 +1776,7 @@ def update(self, event: Event, ctx: Optional[ValidationContext] = None) -> None: logger.debug("Validation ended with result: %s", event.validation_result) def to_dict(self) -> dict: - """" + """ Get the computed validation statistics as a dictionary """ return { @@ -2388,11 +2390,19 @@ class ValidationSettings: metadata_dict: dict = None #: Verbose output verbose: bool = False + #: Cache max age in seconds + cache_max_age: Optional[int] = DEFAULT_HTTP_CACHE_MAX_AGE + #: Cache path + cache_path: Optional[Path] = None def __post_init__(self): # if requirement_severity is a str, convert to Severity if isinstance(self.requirement_severity, str): self.requirement_severity = Severity[self.requirement_severity] + # initialize the HTTP cache + HttpRequester.initialize_cache(cache_path=self.cache_path, cache_max_age=self.cache_max_age) + logger.debug("HTTP cache initialized at %s with max age %s seconds", + self.cache_path, self.cache_max_age) def to_dict(self): """ @@ -2637,7 +2647,7 @@ def detect_rocrate_profiles(self) -> list[Profile]: if len(unmatched_profiles) > 0: logger.warning( "The conformance to the following profiles could not be verified: %s", - unmatched_profiles, + ", ".join(unmatched_profiles), ) return candidate_profiles diff --git a/rocrate_validator/profiles/ro-crate/must/0_file_descriptor_format.py b/rocrate_validator/profiles/ro-crate/must/0_file_descriptor_format.py index fa02f648..316eec44 100644 --- a/rocrate_validator/profiles/ro-crate/must/0_file_descriptor_format.py +++ b/rocrate_validator/profiles/ro-crate/must/0_file_descriptor_format.py @@ -12,12 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +import re from typing import Any +from urllib.parse import urljoin -from rocrate_validator.utils import log as logging from rocrate_validator.models import ValidationContext from rocrate_validator.requirements.python import (PyFunctionCheck, check, requirement) +from rocrate_validator.utils import log as logging from rocrate_validator.utils.http import HttpRequester # set up logging @@ -86,17 +88,80 @@ class FileDescriptorJsonLdFormat(PyFunctionCheck): The file descriptor MUST be a valid JSON-LD file """ + def __get_remote_context__(self, context_uri: str) -> object: + raw_data = HttpRequester().get(context_uri, headers={"Accept": "application/ld+json, application/json"}) + if raw_data.status_code != 200: + raise RuntimeError(f"Unable to retrieve the JSON-LD context '{context_uri}'", self) + logger.debug(f"Retrieved context from {context_uri}") + + # Check if the response header contains the correct content type + content_type = raw_data.headers.get("Content-Type", "") + is_valid_content_type = "application/ld+json" in content_type or "application/json" in content_type + # If the content type is not application/ld+json or application/json, + # try to find an alternate link for the JSON-LD context in the response header + if not is_valid_content_type: + logger.debug( + f"The retrieved context from {context_uri} " + f"does not have a Content-Type of application/ld+json or application/json: " + f"the actual Content-Type is {content_type}. " + ) + # check if the response header contains an alternate link location for the JSON-LD context + # (https headers are case-insensitive, according to RFC 7230, + # so we can use .get() without worrying about the case) + link_header = raw_data.headers.get("Link", "") + logger.debug(f"Checking Link header for alternate JSON-LD context: {link_header}") + has_alternate_link = ('rel="alternate"' in link_header and + ('type="application/ld+json"' in link_header or + 'type="application/json"' in link_header)) + + if has_alternate_link: + logger.debug(f"Found alternate link for JSON-LD context in Link header: {link_header}") + # extract the URL of the alternate link + match = re.search(r'<([^>]+)>;\s*rel="alternate";\s*type="application/(ld\+json|json)"', link_header) + if match: + alternate_url = match.group(1) + # If the alternate URL is relative, resolve it against the original context URI + if not alternate_url.startswith("http"): + alternate_url = urljoin(context_uri, alternate_url) + logger.debug(f"Trying to retrieve JSON-LD context from alternate URL: {alternate_url}") + raw_data = HttpRequester().get(alternate_url, headers={ + "Accept": "application/ld+json, application/json"}) + if raw_data.status_code != 200: + raise RuntimeError( + f"Unable to retrieve the JSON-LD context from alternate URL '{alternate_url}'", self) + logger.debug(f"Retrieved context from alternate URL {alternate_url}") + content_type = raw_data.headers.get("Content-Type", "") + if "application/ld+json" not in content_type and "application/json" not in content_type: + raise RuntimeError( + f"The retrieved context from alternate URL {alternate_url} " + "does not have a Content-Type of application/ld+json or application/json: " + f"the actual Content-Type is {content_type}. ", self) + else: + logger.debug(f"No valid alternate link found in Link header: {link_header}") + raise RuntimeError( + f"Unable to retrieve the JSON-LD context from {context_uri} and no valid " + f"alternate link found in Link header: {link_header}", self) + else: + logger.debug(f"No alternate link for JSON-LD context found in Link header: {link_header}") + raise RuntimeError( + f"Unable to retrieve the JSON-LD context from {context_uri} " + f"and no alternate link found in Link header: {link_header}", self) + + # Try to parse the JSON-LD and access the context + jsonLD = raw_data.json()["@context"] + # logger.warning(f"Retrieved JSON-LD context: {jsonLD}") + assert isinstance(jsonLD, dict) + # return the JSON-LD context + return jsonLD + def __check_remote_context__(self, context_uri: str) -> bool: # Try to retrieve the context try: - raw_data = HttpRequester().get(context_uri, headers={"Accept": "application/ld+json"}) - if raw_data.status_code != 200: - raise RuntimeError(f"Unable to retrieve the JSON-LD context '{context_uri}'", self) - logger.debug(f"Retrieved context from {context_uri}") - # Try to parse the JSON-LD and access the context - jsonLD = raw_data.json()["@context"] - assert isinstance(jsonLD, dict) + jsonLD = self.__get_remote_context__(context_uri) + assert isinstance( + jsonLD, dict), f"The retrieved context from {context_uri} is not \ + a valid JSON-LD context: it is not a dictionary" return True except Exception as e: if logger.isEnabledFor(logging.DEBUG): @@ -306,16 +371,8 @@ def __get_remote_context_keys__(self, context_uri: str) -> set: """ Get the keys of the context URI """ logger.debug(f"Retrieving context from {context_uri}...") - # Try to retrieve the context - raw_data = HttpRequester().get(context_uri, headers={"Accept": "application/ld+json"}) - if raw_data.status_code != 200: - raise RuntimeError(f"Unable to retrieve the JSON-LD context '{context_uri}'") - - logger.debug(f"Retrieved context from {context_uri}") - # Get the keys of the context - jsonLD = raw_data.json() - jsonLD_ctx = jsonLD["@context"] + jsonLD_ctx = self.__get_remote_context__(context_uri) if not isinstance(jsonLD_ctx, dict): raise RuntimeError("The context is not a dictionary", self) return set(jsonLD_ctx.keys()) @@ -339,9 +396,27 @@ def add_unexpected_key(k: str, u_keys: dict) -> None: # If the entity is a dictionary, check each key if isinstance(entity, dict): for k, v in entity.items(): - if k not in context_keys and k not in SKIP_KEYS: + # If the key is in the skip keys, skip it + if k in SKIP_KEYS: + logger.debug(f"Key {k} is a reserved JSON-LD keyword, skipping") + + # If the key is not in the context keys, + # it can be used in compacted format only if it is a valid compact IRI + # with a prefix that is in the context + elif k not in context_keys: logger.debug(f"Key {k} not in context keys") - add_unexpected_key(k, unexpected_keys) + + # Try to get the prefix of the compact IRI, if it has one + prefix = k.split(":", 1)[0] if ":" in k else None + logger.debug(f"Checking prefix {prefix} of key {k}") + # If the key does not have a prefix (no colon) or the prefix is not in the context keys, + # it cannot be used as a key in compacted format + if prefix is None or prefix not in context_keys: + logger.debug( + f"Key {k} does not have a valid prefix in context keys, adding to unexpected keys") + add_unexpected_key(k, unexpected_keys) + + # If the value is a dictionary or a list, check its keys recursively if isinstance(v, (dict, list)): self.__check_entity_keys__(v, context_keys, unexpected_keys) @@ -382,7 +457,7 @@ def check_compaction(self, context: ValidationContext) -> bool: # Check if k is a term or a URI if k.startswith("http"): context.result.add_issue( - f'The The {v} occurrence{suffix} of the "{k}" URI cannot be used as a key{suffix} "' + f'The {v} occurrence{suffix} of the "{k}" URI cannot be used as a key{suffix} "' 'because the compacted format requires simple terms as keys ' '(see https://www.w3.org/TR/json-ld-api/#compaction for more details).', self) else: diff --git a/rocrate_validator/profiles/ro-crate/must/1_file-descriptor_metadata.ttl b/rocrate_validator/profiles/ro-crate/must/1_file-descriptor_metadata.ttl index 962e274e..d9d184c9 100644 --- a/rocrate_validator/profiles/ro-crate/must/1_file-descriptor_metadata.ttl +++ b/rocrate_validator/profiles/ro-crate/must/1_file-descriptor_metadata.ttl @@ -11,7 +11,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - @prefix ro: <./> . @prefix ro-crate: . @prefix dct: . @@ -20,8 +19,31 @@ @prefix sh: . @prefix validator: . +ro-crate:ROCrateMetadataFileDescriptorExistence a sh:NodeShape ; + sh:targetNode sh:Graph ; + sh:name "RO-Crate Metadata File Descriptor entity existence" ; + sh:description """The RO-Crate JSON-LD MUST contain a Metadata File Descriptor entity named `ro-crate-metadata.json` and typed as `schema:CreativeWork`""" ; + sh:sparql [ + a sh:SPARQLConstraint ; + sh:name "RO-Crate Metadata File Descriptor entity existence" ; + sh:description """Check if the RO-Crate Metadata File Descriptor entity exists, + i.e., if there exists an entity with @id `ro-crate-metadata.json` and type `schema:CreativeWork`""" ; + sh:message """The root of the document MUST have an entity with @id `ro-crate-metadata.json`, typed as `schema:CreativeWork` and with an `about` property referencing the Root Data Entity""" ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT ?this + WHERE { + FILTER NOT EXISTS { + ?crate a schema:Dataset . + ?md schema:about ?crate . + ?md a schema:CreativeWork . + FILTER( STRENDS(STR(?md), "ro-crate-metadata.json") ) + } + } + """ ; + ] . -ro-crate:FindROCrateMetadataFileDescriptorEntity a sh:NodeShape, validator:HiddenShape; +ro-crate:FindROCrateMetadataFileDescriptorEntity a sh:NodeShape, validator:HiddenShape ; sh:name "Identify the RO-Crate Metadata File Descriptor" ; sh:description """The RO-Crate Metadata File Descriptor entity describes the RO-Crate itself, and it is named as `ro-crate-metadata.json`. It can be identified by name according to the RO-Crate specification @@ -32,7 +54,8 @@ ro-crate:FindROCrateMetadataFileDescriptorEntity a sh:NodeShape, validator:Hidde sh:select """ SELECT ?this WHERE { - ?this a schema:CreativeWork ; + ?crate a schema:Dataset . + ?this schema:about ?crate . FILTER(contains(str(?this), "ro-crate-metadata.json")) } """ @@ -46,29 +69,12 @@ ro-crate:FindROCrateMetadataFileDescriptorEntity a sh:NodeShape, validator:Hidde sh:object ro-crate:ROCrateMetadataFileDescriptor ; ] . -ro-crate:ROCrateMetadataFileDescriptorExistence - a sh:NodeShape ; - sh:name "RO-Crate Metadata File Descriptor entity existence" ; - sh:description "The RO-Crate JSON-LD MUST contain a Metadata File Descriptor entity named `ro-crate-metadata.json` and typed as `schema:CreativeWork`" ; - sh:targetNode ro:ro-crate-metadata.json ; - sh:property [ - a sh:PropertyShape ; - sh:name "RO-Crate Metadata File Descriptor entity existence" ; - sh:description """Check if the RO-Crate Metadata File Descriptor entity exists, - i.e., if there exists an entity with @id `ro-crate-metadata.json` and type `schema:CreativeWork`""" ; - sh:path rdf:type ; - sh:hasValue ro-crate:ROCrateMetadataFileDescriptor ; - sh:minCount 1 ; - sh:message "The root of the document MUST have an entity with @id `ro-crate-metadata.json`" ; - ] . - ro-crate:ROCrateMetadataFileDescriptorRecommendedProperties a sh:NodeShape ; sh:name "RO-Crate Metadata File Descriptor REQUIRED properties" ; sh:description """RO-Crate Metadata Descriptor MUST be defined - according with the requirements details defined in - [RO-Crate Metadata File Descriptor](https://www.researchobject.org/ro-crate/1.1/root-data-entity.html#ro-crate-metadata-file-descriptor)"""; - sh:targetNode ro:ro-crate-metadata.json ; - sh:property [ + according with the requirements details defined in [RO-Crate Metadata File Descriptor](https://www.researchobject.org/ro-crate/1.1/root-data-entity.html#ro-crate-metadata-file-descriptor)""" ; + sh:targetClass ro-crate:ROCrateMetadataFileDescriptor ; + sh:property [ a sh:PropertyShape ; sh:name "Metadata File Descriptor entity type" ; sh:description "Check if the RO-Crate Metadata File Descriptor has `@type` CreativeWork, as per schema.org" ; @@ -78,11 +84,11 @@ ro-crate:ROCrateMetadataFileDescriptorRecommendedProperties a sh:NodeShape ; sh:hasValue schema_org:CreativeWork ; sh:message "The RO-Crate metadata file MUST be a CreativeWork, as per schema.org" ; ] ; - sh:property [ + sh:property [ a sh:PropertyShape ; sh:name "Metadata File Descriptor entity: `about` property" ; sh:description """Check if the RO-Crate Metadata File Descriptor has an `about` property referencing the Root Data Entity""" ; - sh:maxCount 1; + sh:maxCount 1 ; sh:minCount 1 ; sh:nodeKind sh:IRI ; sh:path schema_org:about ; diff --git a/rocrate_validator/profiles/ro-crate/must/4_data_entity_metadata.ttl b/rocrate_validator/profiles/ro-crate/must/4_data_entity_metadata.ttl index a172e1f3..46356e33 100644 --- a/rocrate_validator/profiles/ro-crate/must/4_data_entity_metadata.ttl +++ b/rocrate_validator/profiles/ro-crate/must/4_data_entity_metadata.ttl @@ -137,9 +137,12 @@ ro-crate:DataEntityRequiredPropertiesShape a sh:NodeShape ; sh:property [ a sh:PropertyShape ; - sh:path [ sh:inversePath schema_org:hasPart ] ; - sh:node schema_org:Dataset ; - sh:minCount 1 ; + # sh:path [ sh:inversePath schema_org:hasPart ] ; + # sh:node schema_org:Dataset ; + # sh:minCount 1 ; + sh:path [ sh:oneOrMorePath [ sh:inversePath schema_org:hasPart ] ] ; + sh:qualifiedValueShape [ sh:class ro-crate:RootDataEntity ] ; + sh:qualifiedMinCount 1 ; sh:name "Data Entity MUST be directly referenced" ; sh:description """Check if the Data Entity is linked, either directly or indirectly, to the `Root Data Entity` using the `hasPart` (as defined in `schema.org`) property" """ ; # sh:message "A Data Entity MUST be directly or indirectly linked to the `Root Data Entity` through the `hasPart` property" ; diff --git a/rocrate_validator/profiles/ro-crate/ontology.ttl b/rocrate_validator/profiles/ro-crate/ontology.ttl index 3ccb4003..5f19a200 100644 --- a/rocrate_validator/profiles/ro-crate/ontology.ttl +++ b/rocrate_validator/profiles/ro-crate/ontology.ttl @@ -31,6 +31,10 @@ # # # Classes # # ################################################################# +# Declare the ROCrateMetadataFileDescriptor class +ro-crate:ROCrateMetadataFileDescriptor rdf:type owl:Class ; + rdfs:label "ROCrateMetadataFileDescriptor"@en . + # Declare the RootDataEntity class ro-crate:RootDataEntity rdf:type owl:Class ; rdfs:subClassOf schema:Dataset ; @@ -65,3 +69,12 @@ ro-crate:DataEntity rdf:type owl:Class ; ro-crate:Directory rdf:type owl:Class ; rdfs:subClassOf schema:Dataset ; rdfs:label "Directory"@en . + + +# # ################################################################# +# # # Individuals +# # ############################################################## + +# Declare the individual representing the RO-Crate Metadata File Descriptor entity +ro:ro-crate-metadata.json a owl:NamedIndividual, ro-crate:ROCrateMetadataFileDescriptor . + diff --git a/rocrate_validator/requirements/shacl/checks.py b/rocrate_validator/requirements/shacl/checks.py index 62a625e0..012c8781 100644 --- a/rocrate_validator/requirements/shacl/checks.py +++ b/rocrate_validator/requirements/shacl/checks.py @@ -16,19 +16,27 @@ from timeit import default_timer as timer from typing import Optional -from rocrate_validator.utils import log as logging from rocrate_validator.errors import ROCrateMetadataNotFoundError from rocrate_validator.events import EventType -from rocrate_validator.models import (LevelCollection, Requirement, - RequirementCheck, - RequirementCheckValidationEvent, - SkipRequirementCheck, ValidationContext) +from rocrate_validator.models import ( + LevelCollection, + Requirement, + RequirementCheck, + RequirementCheckValidationEvent, + SkipRequirementCheck, + ValidationContext, +) from rocrate_validator.requirements.shacl.models import Shape -from rocrate_validator.requirements.shacl.utils import make_uris_relative +from rocrate_validator.requirements.shacl.utils import make_uris_relative, resolve_parent_shape from rocrate_validator.requirements.shacl.validator import ( - SHACLValidationAlreadyProcessed, SHACLValidationContext, - SHACLValidationContextManager, SHACLValidationSkip, SHACLValidator, - SHACLViolation) + SHACLValidationAlreadyProcessed, + SHACLValidationContext, + SHACLValidationContextManager, + SHACLValidationSkip, + SHACLValidator, + SHACLViolation, +) +from rocrate_validator.utils import log as logging logger = logging.getLogger(__name__) @@ -41,25 +49,27 @@ class SHACLCheck(RequirementCheck): # Map shape to requirement check instances __instances__ = {} - def __init__(self, - requirement: Requirement, - shape: Shape, - name: Optional[str] = None, - root: bool = False, - hidden: Optional[bool] = None, - level: Optional[bool] = None) -> None: + def __init__( + self, + requirement: Requirement, + shape: Shape, + name: Optional[str] = None, + root: bool = False, + hidden: Optional[bool] = None, + level: Optional[bool] = None, + ) -> None: self._shape = shape self._root = root # init the check - super().__init__(requirement, - name or shape.name if shape and shape.name - else shape.parent.name if shape.parent - else None, - description=shape.description if shape and shape.description - else shape.parent.description if shape.parent - else None, - level=level, - hidden=hidden) + super().__init__( + requirement, + (name or shape.name if shape and shape.name else shape.parent.name if shape.parent else None), + description=( + shape.description if shape and shape.description else shape.parent.description if shape.parent else None + ), + level=level, + hidden=hidden, + ) # store the instance SHACLCheck.__add_instance__(shape, self) @@ -69,10 +79,14 @@ def __init__(self, declared_level = shape.get_declared_level() if declared_level: if shape.level.severity != requirement_level_from_path.severity: - logger.warning("Mismatch in requirement level for check \"%s\": " - "shape level %s does not match the level from the containing folder %s. " - "Consider moving the shape property or removing the severity property.", - self.name, shape.level, requirement_level_from_path) + logger.warning( + 'Mismatch in requirement level for check "%s": ' + "shape level %s does not match the level from the containing folder %s. " + "Consider moving the shape property or removing the severity property.", + self.name, + shape.level, + requirement_level_from_path, + ) self._level = level @property @@ -85,7 +99,11 @@ def root(self) -> bool: @property def description(self) -> str: - return self._shape.description + if self._shape.description: + return self._shape.description + if self._shape.parent and self._shape.parent.description: + return self._shape.parent.description + return f"Check for {self._shape.name}" if self._shape.name else "SHACL validation check" def __compute_requirement_level__(self) -> LevelCollection: if self._shape and self._shape.get_declared_level(): @@ -107,31 +125,50 @@ def severity(self) -> str: def execute_check(self, context: ValidationContext): logger.debug("Starting check %s", self) try: - logger.debug("SHACL Validation of profile %s requirement %s started", - self.requirement.profile.identifier, self.identifier) + logger.debug( + "SHACL Validation of profile %s requirement %s started", + self.requirement.profile.identifier, + self.identifier, + ) with SHACLValidationContextManager(self, context) as ctx: # The check is executed only if the profile is the most specific one - logger.debug("SHACL Validation of requirement check %s (profile: %s) started", - self.requirement.profile.identifier, self.identifier) + logger.debug( + "SHACL Validation of requirement check %s (profile: %s) started", + self.requirement.profile.identifier, + self.identifier, + ) result = self.__do_execute_check__(ctx) ctx.current_validation_result = self.identifier not in result - logger.debug("SHACL Validation of requirement check %s (profile: %s) finished with result %s", - self.requirement.profile.identifier, self.identifier, ctx.current_validation_result) + logger.debug( + "SHACL Validation of requirement check %s (profile: %s) finished with result %s", + self.requirement.profile.identifier, + self.identifier, + ctx.current_validation_result, + ) return ctx.current_validation_result except SHACLValidationAlreadyProcessed: - logger.debug("SHACL Validation of requirement check %s (profile: %s) already processed", - self.requirement.identifier, self.requirement.profile.identifier) + logger.debug( + "SHACL Validation of requirement check %s (profile: %s) already processed", + self.requirement.identifier, + self.requirement.profile.identifier, + ) # The check belongs to a profile which has already been processed # so we can skip the validation and return the specific result for the check return self.identifier not in [i.check.identifier for i in context.result.get_issues()] except SHACLValidationSkip as e: - logger.debug("SHACL Validation of profile %s requirement %s skipped", - self.requirement.profile.identifier, self.identifier) + logger.debug( + "SHACL Validation of profile %s requirement %s skipped", + self.requirement.profile.identifier, + self.identifier, + ) # The validation is postponed to the more specific profiles # so the check is not considered as failed. raise SkipRequirementCheck(self, str(e)) except ROCrateMetadataNotFoundError as e: - logger.debug("Unable to perform metadata validation due to missing metadata file: %s", e) + logger.debug( + "Unable to perform metadata validation due to missing metadata file: %s", + e, + ) return False def __do_execute_check__(self, shacl_context: SHACLValidationContext): @@ -151,12 +188,17 @@ def __do_execute_check__(self, shacl_context: SHACLValidationContext): end_time = timer() logger.debug(f"Execution time for getting data graph: {end_time - start_time} seconds") except json.decoder.JSONDecodeError as e: - logger.debug("Unable to perform metadata validation " - "due to one or more errors in the JSON-LD data file: %s", e) + logger.debug( + "Unable to perform metadata validation " "due to one or more errors in the JSON-LD data file: %s", + e, + ) shacl_context.result.add_issue( - "Unable to perform metadata validation due to one or more errors in the JSON-LD data file", self) + "Unable to perform metadata validation due to one or more errors in the JSON-LD data file", + self, + ) raise ROCrateMetadataNotFoundError( - "Unable to perform metadata validation due to one or more errors in the JSON-LD data file") + "Unable to perform metadata validation due to one or more errors in the JSON-LD data file" + ) # Begin the timer start_time = timer() @@ -177,7 +219,10 @@ def __do_execute_check__(self, shacl_context: SHACLValidationContext): start_time = timer() shacl_validator = SHACLValidator(shapes_graph=shapes_graph, ont_graph=ontology_graph) shacl_result = shacl_validator.validate( - data_graph=data_graph, ontology_graph=ontology_graph, **shacl_context.settings.to_dict()) + data_graph=data_graph, + ontology_graph=ontology_graph, + **shacl_context.settings.to_dict(), + ) # shacl_result.results_graph.serialize("logs/validation_results.ttl", format="turtle") # parse the validation result end_time = timer() @@ -190,18 +235,41 @@ def __do_execute_check__(self, shacl_context: SHACLValidationContext): # if the validation fails, process the failed checks failed_requirements_checks = set() failed_requirements_checks_violations: dict[str, SHACLViolation] = {} - failed_requirement_checks_notified = [_.check.identifier for _ in shacl_context.result.get_issues( - min_severity=shacl_context.settings.requirement_severity)] + failed_requirement_checks_notified = [ + _.check.identifier + for _ in shacl_context.result.get_issues(min_severity=shacl_context.settings.requirement_severity) + ] logger.debug("Parsing Validation with result: %s", shacl_result) # process the failed checks to extract the requirement checks involved for violation in shacl_result.violations: - shape = shapes_registry.get_shape(Shape.compute_key(shapes_graph, violation.sourceShape)) - assert shape is not None, "Unable to map the violation to a shape" + shape = None + try: + shape = shapes_registry.get_shape(Shape.compute_key(shapes_graph, violation.sourceShape)) + except (ValueError, KeyError): + # sourceShape may be a BNode (e.g. an inline sh:sparql constraint). + # Attempt to resolve the owning NodeShape/PropertyShape via the graph. + shape = resolve_parent_shape(shapes_graph, violation.sourceShape, shapes_registry) + if shape is None: + logger.warning( + "Unable to map violation to a shape (sourceShape: %s); skipping", + violation.sourceShape, + ) + continue + if shape is None: + logger.warning( + "Shape not found for violation (sourceShape: %s)", + violation.sourceShape, + ) + continue requirementCheck = SHACLCheck.get_instance(shape) - assert requirementCheck is not None, "The requirement check cannot be None" - if (not shacl_context.settings.skip_checks or - requirementCheck.identifier not in shacl_context.settings.skip_checks): + if requirementCheck is None: + logger.warning("No check instance found for shape: %s", shape.key) + continue + if ( + not shacl_context.settings.skip_checks + or requirementCheck.identifier not in shacl_context.settings.skip_checks + ): failed_requirements_checks.add(requirementCheck) violations = failed_requirements_checks_violations.get(requirementCheck.identifier, None) if violations is None: @@ -213,8 +281,10 @@ def __do_execute_check__(self, shacl_context: SHACLValidationContext): for requirementCheck in sorted(failed_requirements_checks, key=lambda x: (x.identifier, x.severity)): # if the check is not in the current profile # and the disable_inherited_profiles_reporting is enabled, skip it - if requirementCheck.requirement.profile != shacl_context.current_validation_profile and \ - shacl_context.settings.disable_inherited_profiles_issue_reporting: + if ( + requirementCheck.requirement.profile != shacl_context.current_validation_profile + and shacl_context.settings.disable_inherited_profiles_issue_reporting + ): continue for violation in failed_requirements_checks_violations[requirementCheck.identifier]: violating_entity = make_uris_relative(violation.focusNode.toPython(), shacl_context.publicID) @@ -225,13 +295,18 @@ def __do_execute_check__(self, shacl_context: SHACLValidationContext): # check if the violation is already registered # and skip the requirement check if it is for check_issue in registered_check_issues: - if check_issue.message == violation_message and \ - check_issue.violatingProperty == violating_property and \ - check_issue.violatingEntity == violating_entity and \ - check_issue.violatingPropertyValue == violation.value: + if ( + check_issue.message == violation_message + and check_issue.violatingProperty == violating_property + and check_issue.violatingEntity == violating_entity + and check_issue.violatingPropertyValue == violation.value + ): skip_requirement_check = True - logger.debug("Skipping requirement check %s: %s", requirementCheck.identifier, - violation_message) + logger.debug( + "Skipping requirement check %s: %s", + requirementCheck.identifier, + violation_message, + ) break # if the check is not to be skipped, add the issue to the context if not skip_requirement_check: @@ -240,7 +315,9 @@ def __do_execute_check__(self, shacl_context: SHACLValidationContext): check=requirementCheck, violatingProperty=violating_property, violatingEntity=violating_entity, - violatingPropertyValue=violation.value) + violatingPropertyValue=violation.value, + ) + logger.debug("Added validation issue to the context: %s", c) # if the fail fast mode is enabled, stop the validation after the first issue if shacl_context.fail_fast: break @@ -258,7 +335,10 @@ def __do_execute_check__(self, shacl_context: SHACLValidationContext): shacl_context.validator.notify(RequirementCheckValidationEvent( EventType.REQUIREMENT_CHECK_VALIDATION_END, requirementCheck, validation_result=False)) - logger.debug("Added validation issue to the context: %s", c) + logger.debug( + "Added failed check to the context: %s", + requirementCheck.identifier, + ) # if the fail fast mode is enabled, stop the validation after the first failed check if shacl_context.fail_fast: @@ -275,16 +355,30 @@ def __do_execute_check__(self, shacl_context: SHACLValidationContext): if requirementCheck.identifier not in failed_requirement_checks_notified: failed_requirement_checks_notified.append(requirementCheck.identifier) shacl_context.result._add_executed_check(requirementCheck, True) - if requirementCheck.requirement.profile != shacl_context.target_profile and \ - shacl_context.settings.disable_inherited_profiles_issue_reporting: + if ( + requirementCheck.requirement.profile != shacl_context.target_profile + and shacl_context.settings.disable_inherited_profiles_issue_reporting + ): continue - shacl_context.validator.notify(RequirementCheckValidationEvent( - EventType.REQUIREMENT_CHECK_VALIDATION_END, requirementCheck, validation_result=True)) - logger.debug("Added skipped check to the context: %s", requirementCheck.identifier) + shacl_context.validator.notify( + RequirementCheckValidationEvent( + EventType.REQUIREMENT_CHECK_VALIDATION_END, + requirementCheck, + validation_result=True, + ) + ) + logger.debug( + "Added skipped check to the context: %s", + requirementCheck.identifier, + ) logger.debug("Remaining skipped checks: %r", len(shacl_context.result.skipped_checks)) for requirementCheck in shacl_context.result.skipped_checks: - logger.debug("Remaining skipped check: %r - %s", requirementCheck.identifier, requirementCheck.name) + logger.debug( + "Remaining skipped check: %r - %s", + requirementCheck.identifier, + requirementCheck.name, + ) end_time = timer() logger.debug(f"Execution time for parsing the validation result: {end_time - start_time} seconds") diff --git a/rocrate_validator/requirements/shacl/models.py b/rocrate_validator/requirements/shacl/models.py index 92fc28a1..7b61bf62 100644 --- a/rocrate_validator/requirements/shacl/models.py +++ b/rocrate_validator/requirements/shacl/models.py @@ -431,7 +431,6 @@ def __process_property_group__(groups: dict[str, PropertyGroup], property_shape: if group_name: if group_name not in groups: groups[group_name] = PropertyGroup(URIRef(property_shape.group), property_shape.graph) - property_shape.graph.serialize("logs/property_shape.ttl", format="turtle") groups[group_name].add_property(property_shape) property_shape._property_group = groups[group_name] return groups[group_name] diff --git a/rocrate_validator/requirements/shacl/utils.py b/rocrate_validator/requirements/shacl/utils.py index da90d91a..4200bd08 100644 --- a/rocrate_validator/requirements/shacl/utils.py +++ b/rocrate_validator/requirements/shacl/utils.py @@ -16,7 +16,7 @@ import hashlib from pathlib import Path -from typing import Optional, Union +from typing import TYPE_CHECKING, Optional, Union from rdflib import RDF, BNode, Graph, Namespace from rdflib.term import Node @@ -26,6 +26,9 @@ from rocrate_validator.errors import BadSyntaxError from rocrate_validator.models import Severity +if TYPE_CHECKING: + from rocrate_validator.requirements.shacl.models import Shape + # set up logging logger = logging.getLogger(__name__) @@ -296,3 +299,39 @@ def load_shapes_from_graph(g: Graph) -> ShapesList: subgraphs[shape] = subgraph return ShapesList(node_shapes, property_shapes, subgraphs, g) + + +def resolve_parent_shape( + shapes_graph: Graph, source_shape_node: Node, shapes_registry +) -> Optional[Shape]: + """ + Try to resolve the parent NodeShape/PropertyShape for a BNode constraint node. + + When a SPARQL constraint (sh:sparql) or inline constraint BNode fires a violation, + pyshacl reports the BNode as `sh:sourceShape`. That BNode is not registered + in the ShapesRegistry directly; instead the containing NodeShape is. + This helper walks up via sh:sparql and sh:property predicates to find it. + """ + from rocrate_validator.requirements.shacl.models import Shape + + if not isinstance(source_shape_node, BNode): + return None + SHACL = Namespace(SHACL_NS) + # Predicates via which a NodeShape/PropertyShape can own a constraint BNode + parent_predicates = [SHACL.sparql, SHACL.property] + for predicate in parent_predicates: + for parent_node in shapes_graph.subjects(predicate, source_shape_node): + try: + parent_shape = shapes_registry.get_shape( + Shape.compute_key(shapes_graph, parent_node) + ) + if parent_shape is not None: + logger.debug( + "Resolved parent shape %s for SPARQL/inline constraint BNode %s", + parent_shape.key, + source_shape_node, + ) + return parent_shape + except (ValueError, KeyError): + continue + return None diff --git a/rocrate_validator/requirements/shacl/validator.py b/rocrate_validator/requirements/shacl/validator.py index 0359eb25..1ee3163f 100644 --- a/rocrate_validator/requirements/shacl/validator.py +++ b/rocrate_validator/requirements/shacl/validator.py @@ -23,7 +23,6 @@ from rdflib import BNode, Graph from rdflib.term import Node, URIRef -from rocrate_validator.utils import log as logging from rocrate_validator.constants import (DEFAULT_ONTOLOGY_FILE, RDF_SERIALIZATION_FORMATS, RDF_SERIALIZATION_FORMATS_TYPES, @@ -34,6 +33,8 @@ from rocrate_validator.requirements.shacl.models import ShapesRegistry from rocrate_validator.requirements.shacl.utils import (make_uris_relative, map_severity) +from rocrate_validator.utils import log as logging +from rocrate_validator.utils.rdf import extract_base_from_jsonld # set up logging logger = logging.getLogger(__name__) @@ -189,6 +190,22 @@ def __get_ontology_path__(self, profile_path: Path, ontology_filename: str = DEF self._ontology_path = Path(f"{profile_path}/{ontology_filename}") return self._ontology_path + def __get_data_graph_base__(self) -> Optional[str]: + """ + Get the @base from the RO-Crate metadata JSON-LD. + + This extracts the @base from the @context of the data graph metadata, + which can be used to align the ontology graph's base URI with the data graph. + + :return: The @base value if found, None otherwise + """ + try: + metadata_dict = self.ro_crate.metadata.as_dict() + return extract_base_from_jsonld(metadata_dict) + except Exception as e: + logger.debug("Unable to extract @base from data graph metadata: %s", e) + return None + def __load_ontology_graph__(self, profile_path: Path, ontology_filename: Optional[str] = DEFAULT_ONTOLOGY_FILE) -> Graph: # load the graph of ontologies @@ -197,8 +214,20 @@ def __load_ontology_graph__(self, profile_path: Path, if os.path.exists(ontology_path): logger.debug("Loading ontologies: %s", ontology_path) ontology_graph = Graph() + + # Determine the publicID to use: + # 1. First, try to get @base from the data graph metadata + # 2. Fall back to the default publicID (RO-Crate URI) + data_graph_base = self.__get_data_graph_base__() + public_id = data_graph_base if data_graph_base else self.publicID + + if data_graph_base: + logger.debug("Using @base from data graph metadata: %s", data_graph_base) + else: + logger.debug("Using default publicID: %s", self.publicID) + ontology_graph.parse(ontology_path, format="ttl", - publicID=self.publicID) + publicID=public_id) logger.debug("Ontologies loaded: %s", ontology_graph) return ontology_graph diff --git a/rocrate_validator/utils/http.py b/rocrate_validator/utils/http.py index b7fadff4..197588dc 100644 --- a/rocrate_validator/utils/http.py +++ b/rocrate_validator/utils/http.py @@ -12,11 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import atexit import os import random import string import threading +from typing import Optional import requests @@ -34,8 +37,9 @@ class HttpRequester: _instance = None _lock = threading.Lock() - def __new__(cls): + def __new__(cls, *args, **kwargs) -> HttpRequester: if cls._instance is None: + logger.debug(f"Creating instance of {cls.__name__} with args: {args}, kwargs: {kwargs}") with cls._lock: if cls._instance is None: logger.debug(f"Creating instance of {cls.__name__}") @@ -44,7 +48,10 @@ def __new__(cls): logger.debug(f"Instance created: {cls._instance.__class__.__name__}") return cls._instance - def __init__(self): + def __init__(self, + cache_max_age: int = constants.DEFAULT_HTTP_CACHE_MAX_AGE, + cache_path: Optional[str] = None): + logger.debug(f"Initializing instance of {self.__class__.__name__} {self}") # check if the instance is already initialized if not hasattr(self, "_initialized"): # check if the instance is already initialized @@ -52,14 +59,23 @@ def __init__(self): if not getattr(self, "_initialized", False): # set the initialized flag self._initialized = False + # store the parameters + try: + logger.debug(f"Setting cache_max_age to {cache_max_age}") + self.cache_max_age = int(cache_max_age) + except ValueError: + raise TypeError("cache_max_age must be an integer") + self.cache_path_prefix = cache_path + # flag to indicate if the cache is permanent or temporary + self.permanent_cache = cache_path is not None # initialize the session - self.__initialize_session__() + self.__initialize_session__(cache_max_age, cache_path) # set the initialized flag self._initialized = True else: logger.debug(f"Instance of {self} already initialized") - def __initialize_session__(self): + def __initialize_session__(self, cache_max_age: int, cache_path: Optional[str] = None): # initialize the session self.session = None logger.debug(f"Initializing instance of {self.__class__.__name__}") @@ -67,17 +83,24 @@ def __initialize_session__(self): # check if requests_cache is installed # and set up the cached session try: - if constants.DEFAULT_HTTP_CACHE_TIMEOUT > 0: + if cache_max_age >= 0: from requests_cache import CachedSession - # Generate a random path for the cache - # to avoid conflicts with other instances - random_suffix = ''.join(random.choices(string.ascii_letters + string.digits, k=8)) + # If cache_path is not provided, use the default path prefix + if not cache_path: + # Generate a random path for the cache + # to avoid conflicts with other instances + random_suffix = ''.join(random.choices(string.ascii_letters + string.digits, k=8)) + cache_path = constants.DEFAULT_HTTP_CACHE_PATH_PREFIX + f"_{random_suffix}" + logger.debug(f"Using default cache path: {cache_path}") + else: + logger.debug(f"Using provided cache path: {cache_path}") + self.permanent_cache = True # Initialize the session with a cache self.session = CachedSession( # Cache name with random suffix - cache_name=f"{constants.DEFAULT_HTTP_CACHE_PATH_PREFIX}_{random_suffix}", - expire_after=constants.DEFAULT_HTTP_CACHE_TIMEOUT, # Cache expiration time in seconds + cache_name=cache_path, + expire_after=cache_max_age, # Cache expiration time in seconds backend='sqlite', # Use SQLite backend allowable_methods=('GET',), # Cache GET allowable_codes=(200, 302, 404) # Cache responses with these status codes @@ -86,15 +109,23 @@ def __initialize_session__(self): logger.warning("requests_cache is not installed. Using requests instead.") except Exception as e: logger.error("Error initializing requests_cache: %s", e) - logger.warning("Using requests instead of requests_cache") - # if requests_cache is not installed or an error occurred, use requests - # instead of requests_cache + + # if requests_cache is not installed or an error occurred, + # use requests instead of requests_cache # and create a new session if not self.session: - logger.debug("Using requests instead of requests_cache") + logger.debug("Cache disabled: using requests instead of requests_cache") self.session = requests.Session() def __del__(self): + """ + Destructor to clean up the cache file used by CachedSession. + """ + logger.debug(f"Deleting instance of {self.__class__.__name__}") + if hasattr(self, "permanent_cache") and not self.permanent_cache: + self.cleanup() + + def cleanup(self): """ Destructor to clean up the cache file used by CachedSession. """ @@ -119,3 +150,15 @@ def __getattr__(self, name): if name.upper() in {"GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS", "PATCH"}: return getattr(self.session, name.lower()) raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") + + @classmethod + def initialize_cache(cls, + cache_max_age: int = constants.DEFAULT_HTTP_CACHE_MAX_AGE, + cache_path: Optional[str] = None) -> HttpRequester: + """ + Initialize the HttpRequester singleton with cache settings. + + :param max_age: The maximum age of the cache in seconds. + :param cache_path: The path to the cache directory. + """ + return cls(cache_max_age=cache_max_age, cache_path=cache_path) diff --git a/rocrate_validator/utils/io_helpers/output/text/formatters.py b/rocrate_validator/utils/io_helpers/output/text/formatters.py index 4bd3915f..c0fc7a15 100644 --- a/rocrate_validator/utils/io_helpers/output/text/formatters.py +++ b/rocrate_validator/utils/io_helpers/output/text/formatters.py @@ -58,7 +58,7 @@ def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderR yield Padding( f"[bold][{issue_color}][ {check.identifier.center(16)} ][/{issue_color}] " f"[magenta]{check.name}[/magenta][/bold]:", - (0, 7) + (1, 8, 0, 8) ) yield Padding(Markdown(check.description), (0, 0, 0, len(check.identifier) + 13)) yield Padding("[u] Detected issues [/u]", (0, 8)) @@ -74,7 +74,7 @@ def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderR if issue.violatingEntity: path = f"{path} on [cyan]<{issue.violatingEntity}>[/cyan]" yield Padding(f"- [[red]Violation[/red]{path}]: " - f"{Markdown(issue.message).markup}", (0, 9)) + f"{Markdown(issue.message).markup}", (0, 9, 1, 9)) if console.no_color: yield Padding("\n", (0, 0)) yield Padding("\n", (0, 0)) diff --git a/rocrate_validator/utils/rdf.py b/rocrate_validator/utils/rdf.py index 306eb444..cfe32e90 100644 --- a/rocrate_validator/utils/rdf.py +++ b/rocrate_validator/utils/rdf.py @@ -11,12 +11,16 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +from typing import Optional from rdflib import Graph -from rocrate_validator.utils import logger -from rocrate_validator.utils.paths import list_graph_paths from rocrate_validator import constants +from rocrate_validator.utils import log as logging +from rocrate_validator.utils.paths import list_graph_paths + +# set up logging +logger = logging.getLogger(__name__) def get_full_graph( @@ -37,3 +41,32 @@ def get_full_graph( full_graph.parse(graph_path, format="turtle", publicID=publicID) logger.debug("Loaded triples from %s", graph_path) return full_graph + + +def extract_base_from_jsonld(json_data: dict) -> Optional[str]: + """ + Extract the @base from the @context of a JSON-LD document. + + The @context can be: + - A dictionary (e.g., {"@base": "http://example.org/"}) + - A list of contexts (e.g., [{"@base": "http://example.org/"}, "https://schema.org"]) + + :param json_data: The JSON-LD data as a dictionary + :return: The @base value if found, None otherwise + """ + context = json_data.get('@context') + + if not context: + return None + + # If @context is a dictionary, look for @base directly + if isinstance(context, dict): + return context.get('@base') + + # If @context is a list, look for @base in each context item + if isinstance(context, list): + for ctx in context: + if isinstance(ctx, dict) and '@base' in ctx: + return ctx['@base'] + + return None diff --git a/tests/data/crates/invalid/4_data_entity_metadata/cyclic_datasets/ro-crate-metadata.json b/tests/data/crates/invalid/4_data_entity_metadata/cyclic_datasets/ro-crate-metadata.json new file mode 100644 index 00000000..4a6cc932 --- /dev/null +++ b/tests/data/crates/invalid/4_data_entity_metadata/cyclic_datasets/ro-crate-metadata.json @@ -0,0 +1,69 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "name": "Root", + "description": "Root Dataset", + "datePublished": "2024-05-17T01:04:52+01:00", + "hasPart": [ + ], + "license": "CC-BY-4.0" + }, + { + "@id": "dataset1/file1.txt", + "@type": "File", + "description": "A file", + "encodingFormat": "text/plain", + "name": "file1.txt" + }, + { + "@id": "dataset2/file2.txt", + "@type": "File", + "description": "Another file", + "encodingFormat": "text/plain", + "name": "file2.txt" + }, + { + "@id": "dataset1/", + "@type": "Dataset", + "name": "Dataset 1", + "description": "First dataset", + "hasPart": [ + { + "@id": "dataset1/file1.txt" + }, + { + "@id": "dataset2/" + } + ] + }, + { + "@id": "dataset2/", + "@type": "Dataset", + "name": "Dataset 2", + "description": "Second dataset", + "hasPart": [ + { + "@id": "dataset2/file2.txt" + }, + { + "@id": "dataset1/" + } + ] + } + ] +} diff --git a/tests/data/crates/valid/rocrate-with-at-base-set/example1/.gitkeep b/tests/data/crates/valid/rocrate-with-at-base-set/example1/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tests/data/crates/valid/rocrate-with-at-base-set/index.html b/tests/data/crates/valid/rocrate-with-at-base-set/index.html new file mode 100644 index 00000000..e69de29b diff --git a/tests/data/crates/valid/rocrate-with-at-base-set/ro-crate-metadata.json b/tests/data/crates/valid/rocrate-with-at-base-set/ro-crate-metadata.json new file mode 100644 index 00000000..16cd265a --- /dev/null +++ b/tests/data/crates/valid/rocrate-with-at-base-set/ro-crate-metadata.json @@ -0,0 +1,1299 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.2-DRAFT/context", + { + "@base": "https://www.researchobject.org/workflow-run-crate/profiles/0.5/process_run_crate/" + } + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + } + }, + { + "@id": "ro-crate-preview.html", + "@type": "CreativeWork", + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "isBasedOn": { + "@id": "ro-crate-metadata.json" + }, + "name": "RO-Crate preview of the Process Run Crate profile", + "encodingFormat": "text/html", + "about": "https://w3id.org/ro/wfrun/process/0.5" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": [ + "Dataset", + "Profile" + ], + "datePublished": "2024-06-01", + "isProfileOf": [ + { + "@id": "https://w3id.org/ro/crate/1.2-DRAFT" + } + ], + "identifier": "https://w3id.org/ro/wfrun/process/0.5", + "name": "Process Run Crate profile", + "description": "A RO-Crate profile for packaging metadata about the execution of computational workflows, based on the Workflow Run Crate model described in Leo et al. (2024)", + "version": "0.5", + "license": { + "@id": "https://www.apache.org/licenses/LICENSE-2.0" + }, + "author": [ + { + "@id": "https://www.researchobject.org/workflow-run-crate/" + } + ], + "hasPart": [ + { + "@id": "index.html" + }, + { + "@id": "example1/" + }, + { + "@id": "https://w3id.org/ro/doi/10.5281/zenodo.10368989" + }, + { + "@id": "https://www.researchobject.org/workflow-run-crate-paper/mapping/" + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#" + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run" + }, + { + "@id": "https://gxy.io/GTN:T00343" + }, + { + "@id": "https://pypi.org/project/runcrate/" + }, + { + "@id": "http://schema.org/SoftwareApplication" + }, + { + "@id": "http://schema.org/SoftwareSourceCode" + }, + { + "@id": "https://bioschemas.org/ComputationalWorkflow" + }, + { + "@id": "http://schema.org/CreateAction" + }, + { + "@id": "http://schema.org/name" + }, + { + "@id": "http://schema.org/description" + }, + { + "@id": "http://schema.org/endTime" + }, + { + "@id": "http://schema.org/startTime" + }, + { + "@id": "http://schema.org/instrument" + }, + { + "@id": "http://schema.org/agent" + }, + { + "@id": "http://schema.org/Person" + }, + { + "@id": "http://schema.org/Organization" + }, + { + "@id": "http://schema.org/object" + }, + { + "@id": "http://schema.org/result" + }, + { + "@id": "http://schema.org/actionStatus" + }, + { + "@id": "http://schema.org/error" + }, + { + "@id": "http://schema.org/CompletedActionStatus" + }, + { + "@id": "http://schema.org/FailedActionStatus" + }, + { + "@id": "http://schema.org/MediaObject" + }, + { + "@id": "http://schema.org/Dataset" + }, + { + "@id": "http://schema.org/Collection" + }, + { + "@id": "http://schema.org/CreativeWork" + }, + { + "@id": "http://schema.org/PropertyValue" + }, + { + "@id": "http://schema.org/alternateName" + }, + { + "@id": "http://schema.org/mainEntity" + } + ], + "hasResource": [ + { + "@id": "#hasSpecification" + }, + { + "@id": "#hasConstraints" + }, + { + "@id": "#hasVocabulary" + }, + { + "@id": "#hasGuidance" + }, + { + "@id": "#hasExample" + }, + { + "@id": "#hasMappingToProv" + } + ], + "citation": { + "@id": "https://doi.org/10.48550/arXiv.2312.07852" + } + }, + { + "@id": "https://www.researchobject.org/workflow-run-crate/", + "@type": "Project", + "name": "Workflow Run Crate task force", + "member": [ + { + "@id": "https://orcid.org/0000-0001-8271-5429" + }, + { + "@id": "https://orcid.org/0000-0003-4929-1219" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + { + "@id": "https://orcid.org/0000-0002-5432-2748" + }, + { + "@id": "https://orcid.org/0000-0002-4806-5140" + }, + { + "@id": "https://orcid.org/0000-0003-3156-2105" + }, + { + "@id": "https://orcid.org/0000-0002-6190-122X" + }, + { + "@id": "https://orcid.org/0000-0003-0454-7145" + }, + { + "@id": "https://orcid.org/0000-0002-8940-4946" + }, + { + "@id": "https://orcid.org/0000-0003-0606-2512" + }, + { + "@id": "https://orcid.org/0000-0002-3468-0652" + }, + { + "@id": "https://orcid.org/0000-0002-2961-9670" + }, + { + "@id": "https://orcid.org/0000-0003-3986-0510" + }, + { + "@id": "https://orcid.org/0000-0002-0003-2024" + }, + { + "@id": "https://orcid.org/0000-0002-9464-6640" + }, + { + "@id": "https://orcid.org/0000-0001-5845-8880" + }, + { + "@id": "https://orcid.org/0000-0003-4894-4660" + }, + { + "@id": "https://orcid.org/0000-0002-4405-6802" + }, + { + "@id": "https://orcid.org/0000-0001-9290-2017" + }, + { + "@id": "https://orcid.org/0000-0003-0617-9219" + }, + { + "@id": "https://orcid.org/0000-0001-9228-2882" + }, + { + "@id": "https://orcid.org/0000-0003-3898-9451" + }, + { + "@id": "https://orcid.org/0000-0003-3777-5945" + }, + { + "@id": "https://orcid.org/0000-0003-2765-0049" + }, + { + "@id": "https://orcid.org/0000-0001-9818-9320" + }, + { + "@id": "https://orcid.org/0000-0002-8122-9522" + }, + { + "@id": "https://orcid.org/0000-0002-8330-4071" + }, + { + "@id": "https://orcid.org/0000-0003-4073-7456" + }, + { + "@id": "https://orcid.org/0000-0003-1361-7301" + }, + { + "@id": "https://orcid.org/0000-0002-5358-616X" + }, + { + "@id": "https://orcid.org/0000-0002-5477-287X" + }, + { + "@id": "https://orcid.org/0000-0001-8250-4074" + }, + { + "@id": "https://orcid.org/0000-0003-0902-0086" + }, + { + "@id": "https://orcid.org/0000-0001-8172-8981" + }, + { + "@id": "https://orcid.org/0000-0001-6740-9212" + }, + { + "@id": "https://orcid.org/0000-0001-5411-356X" + }, + { + "@id": "https://orcid.org/0000-0003-0035-0951" + }, + { + "@id": "https://orcid.org/0000-0003-0711-5196" + } + ], + "parentOrganization": { + "@id": "https://www.researchobject.org/ro-crate/community" + } + }, + { + "@id": "https://www.researchobject.org/ro-crate/community", + "@type": "Project", + "name": "RO-Crate Community" + }, + { + "@id": "index.html", + "@type": "File", + "name": "Process Run Crate (HTML)", + "encodingFormat": [ + "text/html", + { + "@id": "https://www.nationalarchives.gov.uk/PRONOM/fmt/471" + } + ] + }, + { + "@id": "index.html#requirements", + "@type": "WebPageElement", + "name": "Process Run Crate requirements" + }, + { + "@id": "example1/", + "@type": "Dataset", + "name": "Example Process Run Crate", + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.4" + } + ], + "subjectOf": [ + { + "@id": "example1/ro-crate-metadata.json" + }, + { + "@id": "example1/ro-crate-preview.html" + } + ] + }, + { + "@id": "example1/ro-crate-metadata.json", + "@type": "CreativeWork", + "encodingFormat": "application/ld+json" + }, + { + "@id": "example1/ro-crate-preview.html", + "@type": "CreativeWork", + "encodingFormat": "text/html" + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run", + "@type": "File", + "name": "JSON-LD context for workflow-run terms", + "contentUrl": "https://www.researchobject.org/ro-terms/workflow-run/context.json", + "conformsTo": { + "@id": "http://www.w3.org/ns/json-ld#Context" + }, + "encodingFormat": "application/ld+json", + "isBasedOn": [ + { + "@id": "https://w3id.org/ro/crate/1.1/context" + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#" + } + ], + "license": { + "@id": "https://creativecommons.org/publicdomain/zero/1.0/" + } + }, + { + "@id": "#hasSpecification", + "@type": "ResourceDescriptor", + "hasRole": { + "@id": "http://www.w3.org/ns/dx/prof/role/specification" + }, + "hasArtifact": { + "@id": "index.html" + } + }, + { + "@id": "#hasConstraints", + "@type": "ResourceDescriptor", + "hasRole": { + "@id": "http://www.w3.org/ns/dx/prof/role/constraints" + }, + "hasArtifact": { + "@id": "index.html#requirements" + } + }, + { + "@id": "#hasVocabulary", + "@type": "ResourceDescriptor", + "hasRole": { + "@id": "http://www.w3.org/ns/dx/prof/role/vocabulary" + }, + "hasArtifact": { + "@id": "https://w3id.org/ro/terms/workflow-run#" + } + }, + { + "@id": "#hasGuidance", + "@type": "ResourceDescriptor", + "hasRole": { + "@id": "http://www.w3.org/ns/dx/prof/role/guidance" + }, + "hasArtifact": { + "@id": "https://gxy.io/GTN:T00343" + } + }, + { + "@id": "#hasExample", + "@type": "ResourceDescriptor", + "hasRole": { + "@id": "http://www.w3.org/ns/dx/prof/role/example" + }, + "hasArtifact": { + "@id": "example1/" + } + }, + { + "@id": "#hasMappingToProv", + "@type": "ResourceDescriptor", + "hasRole": { + "@id": "http://www.w3.org/ns/dx/prof/role/mapping" + }, + "hasArtifact": { + "@id": "https://www.researchobject.org/workflow-run-crate-paper/mapping/" + } + }, + { + "@id": "https://www.researchobject.org/workflow-run-crate-paper/mapping/", + "@type": "Dataset", + "name": "SSSOM mapping from PROV to Workflow Run Crate", + "isPartOf": { + "@id": "https://w3id.org/ro/doi/10.5281/zenodo.10368989" + } + }, + { + "@id": "https://w3id.org/ro/doi/10.5281/zenodo.10368989", + "@type": "Dataset", + "name": "Recording provenance of workflow runs with RO-Crate (RO-Crate and mapping)", + "conformsTo": { + "@id": "https://w3id.org/ro/crate" + }, + "subjectOf": [ + { + "@id": "https://www.researchobject.org/workflow-run-crate-paper/ro-crate-metadata.jsonld" + }, + { + "@id": "https://www.researchobject.org/workflow-run-crate-paper/ro-crate-preview.html" + } + ] + }, + { + "@id": "https://www.researchobject.org/workflow-run-crate-paper/ro-crate-metadata.jsonld", + "@type": "CreativeWork", + "encodingFormat": "application/ld+json" + }, + { + "@id": "https://www.researchobject.org/workflow-run-crate-paper/ro-crate-preview.html", + "@type": "CreativeWork", + "encodingFormat": "text/html" + }, + { + "@id": "https://gxy.io/GTN:T00343", + "@type": "LearningResource", + "name": "Workflow Run RO-Crate Introduction", + "author": { + "@id": "https://orcid.org/0000-0001-8271-5429" + } + }, + { + "@id": "https://doi.org/10.48550/arXiv.2312.07852", + "@type": "ScholarlyArticle", + "name": "Recording provenance of workflow runs with RO-Crate", + "author": [ + { + "@id": "https://orcid.org/0000-0001-8271-5429" + }, + { + "@id": "https://orcid.org/0000-0002-2961-9670" + }, + { + "@id": "https://orcid.org/0000-0003-4929-1219" + }, + { + "@id": "https://orcid.org/0000-0003-0606-2512" + }, + { + "@id": "https://orcid.org/0000-0002-3468-0652" + }, + { + "@id": "https://orcid.org/0000-0002-8940-4946" + }, + { + "@id": "https://orcid.org/0000-0002-0003-2024" + }, + { + "@id": "https://orcid.org/0000-0002-4663-5613" + }, + { + "@id": "https://orcid.org/0000-0003-0454-7145" + }, + { + "@id": "https://orcid.org/0000-0002-4806-5140" + }, + { + "@id": "https://orcid.org/0000-0001-9290-2017" + }, + { + "@id": "https://orcid.org/0000-0002-1119-1792" + }, + { + "@id": "https://orcid.org/0000-0003-3777-5945" + }, + { + "@id": "https://orcid.org/0000-0003-2765-0049" + }, + { + "@id": "https://orcid.org/0000-0002-0309-604X" + }, + { + "@id": "https://orcid.org/0000-0003-0902-0086" + }, + { + "@id": "https://orcid.org/0000-0001-8250-4074" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718" + } + ], + "creditText": "Simone Leo, et al. (2024): Recording provenance of workflow runs with RO-Crate. arXiv:2312.07852" + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#", + "@type": "DefinedTermSet", + "name": "Namespace for Workflow Run RO-Crate model", + "hasDefinedTerm": [ + { + "@id": "https://w3id.org/ro/terms/workflow-run#ParameterConnection" + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#ContainerImage" + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#DockerImage" + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#SIFImage" + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#connection" + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#sourceParameter" + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#targetParameter" + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#environment" + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#registry" + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#tag" + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#containerImage" + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#resourceUsage" + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#md5" + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#sha1" + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#sha256" + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#sha512" + } + ] + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#ParameterConnection", + "@type": [ + "DefinedTerm", + "rdfs:Class" + ], + "termCode": "ParameterConnection", + "name": "Parameter Connection", + "description": "A connection between parameters of different applications" + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#ContainerImage", + "@type": [ + "DefinedTerm", + "rdfs:Class" + ], + "termCode": "ContainerImage", + "name": "Container Image", + "description": "A containerization software container image" + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#DockerImage", + "@type": [ + "DefinedTerm", + "rdfs:Class" + ], + "termCode": "DockerImage", + "name": "Docker Image", + "description": "A docker container image" + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#SIFImage", + "@type": [ + "DefinedTerm", + "rdfs:Class" + ], + "termCode": "SIFImage", + "name": "SIF Image", + "description": "A Singularity Image Format container image" + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#connection", + "@type": [ + "DefinedTerm", + "rdf:Property" + ], + "termCode": "connection", + "name": "connection", + "description": "A parameter connection created by this workflow", + "domainIncludes": [ + { + "@id": "https://bioschemas.org/ComputationalWorkflow" + }, + { + "@id": "http://schema.org/HowToStep" + } + ], + "rangeIncludes": [ + { + "@id": "https://w3id.org/ro/terms/workflow-run#ParameterConnection" + } + ] + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#sourceParameter", + "@type": [ + "DefinedTerm", + "rdf:Property" + ], + "termCode": "sourceParameter", + "name": "source parameter", + "description": "The source (upstream) parameter", + "domainIncludes": [ + { + "@id": "https://w3id.org/ro/terms/workflow-run#ParameterConnection" + } + ], + "rangeIncludes": [ + { + "@id": "https://bioschemas.org/FormalParameter" + } + ] + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#targetParameter", + "@type": [ + "DefinedTerm", + "rdf:Property" + ], + "termCode": "targetParameter", + "name": "target parameter", + "description": "The target (upstream) parameter", + "domainIncludes": [ + { + "@id": "https://w3id.org/ro/terms/workflow-run#ParameterConnection" + } + ], + "rangeIncludes": [ + { + "@id": "https://bioschemas.org/FormalParameter" + } + ] + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#environment", + "@type": [ + "DefinedTerm", + "rdf:Property" + ], + "termCode": "environment", + "name": "environment", + "description": "environment variables used by the application", + "domainIncludes": [ + { + "@id": "http://schema.org/SoftwareApplication" + }, + { + "@id": "http://schema.org/SoftwareSourceCode" + }, + { + "@id": "http://schema.org/CreateAction" + }, + { + "@id": "https://bioschema.org/ComputationalWorkflow" + } + ], + "rangeIncludes": [ + { + "@id": "https://bioschemas.org/FormalParameter" + }, + { + "@id": "http://schema.org/PropertyValue" + } + ] + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#registry", + "@type": [ + "DefinedTerm", + "rdf:Property" + ], + "termCode": "registry", + "name": "registry", + "description": "A service to register software products, such as container images", + "domainIncludes": [ + { + "@id": "https://w3id.org/ro/terms/workflow-run#ContainerImage" + } + ], + "rangeIncludes": [ + { + "@id": "http://schema.org/Text" + } + ] + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#tag", + "@type": [ + "DefinedTerm", + "rdf:Property" + ], + "termCode": "tag", + "name": "tag", + "description": "A tag assigned to a software product, such as a container image", + "domainIncludes": [ + { + "@id": "https://w3id.org/ro/terms/workflow-run#ContainerImage" + } + ], + "rangeIncludes": [ + { + "@id": "http://schema.org/Text" + } + ] + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#containerImage", + "@type": [ + "DefinedTerm", + "rdf:Property" + ], + "termCode": "containerImage", + "name": "container image", + "description": "A container image associated with this entity", + "domainIncludes": [ + { + "@id": "http://schema.org/CreateAction" + } + ], + "rangeIncludes": [ + { + "@id": "https://w3id.org/ro/terms/workflow-run#ContainerImage" + }, + { + "@id": "http://schema.org/URL" + } + ] + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#resourceUsage", + "@type": [ + "DefinedTerm", + "rdf:Property" + ], + "termCode": "resourceUsage", + "name": "resource usage", + "description": "A resource usage item, such as peak memory", + "domainIncludes": [ + { + "@id": "http://schema.org/CreateAction" + } + ], + "rangeIncludes": [ + { + "@id": "http://schema.org/PropertyValue" + } + ] + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#md5", + "@type": [ + "DefinedTerm", + "rdf:Property" + ], + "termCode": "md5", + "name": "md5 checksum", + "description": "md5 checksum as a hexadecimal string", + "domainIncludes": [ + { + "@id": "http://schema.org/MediaObject" + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#ContainerImage" + } + ], + "rangeIncludes": [ + { + "@id": "http://schema.org/Text" + } + ] + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#sha1", + "@type": [ + "DefinedTerm", + "rdf:Property" + ], + "termCode": "sha1", + "name": "sha1 checksum", + "description": "sha1 checksum as a hexadecimal string", + "domainIncludes": [ + { + "@id": "http://schema.org/MediaObject" + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#ContainerImage" + } + ], + "rangeIncludes": [ + { + "@id": "http://schema.org/Text" + } + ] + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#sha256", + "@type": [ + "DefinedTerm", + "rdf:Property" + ], + "termCode": "sha256", + "name": "sha256 checksum", + "description": "sha256 checksum as a hexadecimal string", + "domainIncludes": [ + { + "@id": "http://schema.org/MediaObject" + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#ContainerImage" + } + ], + "rangeIncludes": [ + { + "@id": "http://schema.org/Text" + } + ], + "sameAs": { + "@id": "http://schema.org/sha256" + } + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#sha512", + "@type": [ + "DefinedTerm", + "rdf:Property" + ], + "termCode": "sha512", + "name": "sha512 checksum", + "description": "sha512 checksum as a hexadecimal string", + "domainIncludes": [ + { + "@id": "http://schema.org/MediaObject" + }, + { + "@id": "https://w3id.org/ro/terms/workflow-run#ContainerImage" + } + ], + "rangeIncludes": [ + { + "@id": "http://schema.org/Text" + } + ] + }, + { + "@id": "https://pypi.org/project/runcrate/", + "@type": "SoftwareApplication", + "name": "runcrate", + "version": "0.5.0", + "url": "https://pypi.org/project/runcrate/" + }, + { + "@id": "http://schema.org/MediaObject", + "@type": "DefinedTerm", + "name": "MediaObject", + "termCode": "File" + }, + { + "@id": "http://schema.org/Text", + "@type": "DefinedTerm", + "name": "Text" + }, + { + "@id": "https://bioschemas.org/FormalParameter", + "@type": "DefinedTerm", + "name": "FormalParameter" + }, + { + "@id": "https://bioschemas.org/ComputationalWorkflow", + "@type": "DefinedTerm", + "name": "ComputationalWorkflow" + }, + { + "@id": "http://schema.org/HowToStep", + "@type": "DefinedTerm", + "name": "HowToStep" + }, + { + "@id": "http://schema.org/CreateAction", + "@type": "DefinedTerm", + "name": "CreateAction" + }, + { + "@id": "http://schema.org/name", + "@type": "DefinedTerm", + "name": "name" + }, + { + "@id": "http://schema.org/description", + "@type": "DefinedTerm", + "name": "description" + }, + { + "@id": "http://schema.org/endTime", + "@type": "DefinedTerm", + "name": "endTime" + }, + { + "@id": "http://schema.org/startTime", + "@type": "DefinedTerm", + "name": "startTime" + }, + { + "@id": "http://schema.org/instrument", + "@type": "DefinedTerm", + "name": "instrument" + }, + { + "@id": "http://schema.org/agent", + "@type": "DefinedTerm", + "name": "agent" + }, + { + "@id": "http://schema.org/Person", + "@type": "DefinedTerm", + "name": "Person" + }, + { + "@id": "http://schema.org/Organization", + "@type": "DefinedTerm", + "name": "Organization" + }, + { + "@id": "http://schema.org/object", + "@type": "DefinedTerm", + "name": "object" + }, + { + "@id": "http://schema.org/result", + "@type": "DefinedTerm", + "name": "result" + }, + { + "@id": "http://schema.org/actionStatus", + "@type": "DefinedTerm", + "name": "actionStatus" + }, + { + "@id": "http://schema.org/CompletedActionStatus", + "@type": "DefinedTerm", + "name": "CompletedActionStatus" + }, + { + "@id": "http://schema.org/FailedActionStatus", + "@type": "DefinedTerm", + "name": "FailedActionStatus" + }, + { + "@id": "http://schema.org/error", + "@type": "DefinedTerm", + "name": "error" + }, + { + "@id": "http://schema.org/Dataset", + "@type": "DefinedTerm", + "name": "Dataset" + }, + { + "@id": "http://schema.org/Collection", + "@type": "DefinedTerm", + "name": "Collection" + }, + { + "@id": "http://schema.org/CreativeWork", + "@type": "DefinedTerm", + "name": "CreativeWork" + }, + { + "@id": "http://schema.org/PropertyValue", + "@type": "DefinedTerm", + "name": "PropertyValue" + }, + { + "@id": "http://schema.org/alternateName", + "@type": "DefinedTerm", + "name": "alternateName" + }, + { + "@id": "http://schema.org/mainEntity", + "@type": "DefinedTerm", + "name": "mainEntity" + }, + { + "@id": "http://schema.org/SoftwareApplication", + "@type": "DefinedTerm", + "name": "SoftwareApplication" + }, + { + "@id": "http://schema.org/SoftwareSourceCode", + "@type": "DefinedTerm", + "name": "SoftwareSourceCode" + }, + { + "@id": "https://orcid.org/0000-0001-8271-5429", + "@type": "Person", + "name": "Simone Leo" + }, + { + "@id": "https://orcid.org/0000-0003-4929-1219", + "@type": "Person", + "name": "Laura Rodríguez-Navas" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-5432-2748", + "@type": "Person", + "name": "Paul Brack" + }, + { + "@id": "https://orcid.org/0000-0002-4806-5140", + "@type": "Person", + "name": "José María Fernández" + }, + { + "@id": "https://orcid.org/0000-0003-3156-2105", + "@type": "Person", + "name": "Alan R Williams" + }, + { + "@id": "https://orcid.org/0000-0002-6190-122X", + "@type": "Person", + "name": "Ignacio Eguinoa" + }, + { + "@id": "https://orcid.org/0000-0003-0454-7145", + "@type": "Person", + "name": "Daniel Garijo" + }, + { + "@id": "https://orcid.org/0000-0002-8940-4946", + "@type": "Person", + "name": "Paul De Geest" + }, + { + "@id": "https://orcid.org/0000-0003-0606-2512", + "@type": "Person", + "name": "Raül Sirvent" + }, + { + "@id": "https://orcid.org/0000-0002-3468-0652", + "@type": "Person", + "name": "Alexander Kanitz" + }, + { + "@id": "https://orcid.org/0000-0002-2961-9670", + "@type": "Person", + "name": "Michael R Crusoe" + }, + { + "@id": "https://orcid.org/0000-0003-3986-0510", + "@type": "Person", + "name": "LJ Garcia Castro" + }, + { + "@id": "https://orcid.org/0000-0002-0003-2024", + "@type": "Person", + "name": "Rudolf Wittner" + }, + { + "@id": "https://orcid.org/0000-0002-9464-6640", + "@type": "Person", + "name": "Wolfgang Maier" + }, + { + "@id": "https://orcid.org/0000-0001-5845-8880", + "@type": "Person", + "name": "Sebastiaan Huber" + }, + { + "@id": "https://orcid.org/0000-0003-4894-4660", + "@type": "Person", + "name": "Kevin Jablonka" + }, + { + "@id": "https://orcid.org/0000-0002-4405-6802", + "@type": "Person", + "name": "Haris Zafeiropoulos" + }, + { + "@id": "https://orcid.org/0000-0001-9290-2017", + "@type": "Person", + "name": "Iacopo Colonnelli" + }, + { + "@id": "https://orcid.org/0000-0003-0617-9219", + "@type": "Person", + "name": "Jake Emerson" + }, + { + "@id": "https://orcid.org/0000-0001-9228-2882", + "@type": "Person", + "name": "Abigail Miller" + }, + { + "@id": "https://orcid.org/0000-0003-3898-9451", + "@type": "Person", + "name": "Stelios Ninidakis" + }, + { + "@id": "https://orcid.org/0000-0003-3777-5945", + "@type": "Person", + "name": "Tazro Ohta" + }, + { + "@id": "https://orcid.org/0000-0003-2765-0049", + "@type": "Person", + "name": "Hirotaka Suetake" + }, + { + "@id": "https://orcid.org/0000-0001-9818-9320", + "@type": "Person", + "name": "Johannes Köster" + }, + { + "@id": "https://orcid.org/0000-0002-8122-9522", + "@type": "Person", + "name": "Luiz Gadelha" + }, + { + "@id": "https://orcid.org/0000-0002-8330-4071", + "@type": "Person", + "name": "Mahnoor Zulfiqar" + }, + { + "@id": "https://orcid.org/0000-0003-4073-7456", + "@type": "Person", + "name": "Romain David" + }, + { + "@id": "https://orcid.org/0000-0003-1361-7301", + "@type": "Person", + "name": "Maciek Bąk" + }, + { + "@id": "https://orcid.org/0000-0002-5358-616X", + "@type": "Person", + "name": "Petr Holub" + }, + { + "@id": "https://orcid.org/0000-0002-5477-287X", + "@type": "Person", + "name": "Milan Markovic" + }, + { + "@id": "https://orcid.org/0000-0001-8250-4074", + "@type": "Person", + "name": "Bruno P. Kinoshita" + }, + { + "@id": "https://orcid.org/0000-0003-0902-0086", + "@type": "Person", + "name": "Renske de Wit" + }, + { + "@id": "https://orcid.org/0000-0001-8172-8981", + "@type": "Person", + "name": "Jasper Koehorst" + }, + { + "@id": "https://orcid.org/0000-0001-6740-9212", + "@type": "Person", + "name": "Samuel Lampa" + }, + { + "@id": "https://orcid.org/0000-0001-5411-356X", + "@type": "Person", + "name": "Samuel Grayson" + }, + { + "@id": "https://orcid.org/0000-0003-0035-0951", + "@type": "Person", + "name": "Jörg F. Unger" + }, + { + "@id": "https://orcid.org/0000-0003-0711-5196", + "@type": "Person", + "name": "Felix Bartusch" + }, + { + "@id": "https://orcid.org/0000-0002-4663-5613", + "@type": "Person", + "name": "Luca Pireddu" + }, + { + "@id": "https://orcid.org/0000-0002-1119-1792", + "@type": "Person", + "name": "Matej Gallo" + }, + { + "@id": "https://orcid.org/0000-0002-0309-604X", + "@type": "Person", + "name": "Salvador Capella-Gutierrez" + }, + { + "@id": "https://www.apache.org/licenses/LICENSE-2.0", + "@type": "CreativeWork", + "name": "Apache License 2.0", + "version": "2.0", + "identifier": { + "@id": "http://spdx.org/licenses/Apache-2.0" + } + }, + { + "@id": "https://creativecommons.org/publicdomain/zero/1.0/", + "@type": "CreativeWork", + "identifier": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "name": "Creative Commons Zero v1.0 Universal", + "version": "1.0" + }, + { + "@id": "http://spdx.org/licenses/Apache-2.0", + "@type": "PropertyValue", + "propertyID": "http://spdx.org/rdf/terms#licenseId", + "name": "spdx", + "value": "Apache-2.0" + }, + { + "@id": "http://spdx.org/licenses/CC0-1.0", + "@type": "PropertyValue", + "propertyID": "http://spdx.org/rdf/terms#licenseId", + "name": "spdx", + "value": "CC0-1.0" + } + ] +} diff --git a/tests/data/crates/valid/rocrate-with-custom-terms/ro-crate-metadata.json b/tests/data/crates/valid/rocrate-with-custom-terms/ro-crate-metadata.json index b201b9e8..e4e0c953 100644 --- a/tests/data/crates/valid/rocrate-with-custom-terms/ro-crate-metadata.json +++ b/tests/data/crates/valid/rocrate-with-custom-terms/ro-crate-metadata.json @@ -2,7 +2,10 @@ "@context": [ "https://w3id.org/ro/crate/1.1/context", "https://w3id.org/ro/terms/workflow-run/context", - { "myCustomTerm": "https://example.org#myCustomTerm" } + { + "myCustomTerm": "https://example.org#myCustomTerm", + "rdfs": "http://www.w3.org/2000/01/rdf-schema#" + } ], "@graph": [ { @@ -14,7 +17,9 @@ "about": { "@id": "./" }, - "myCustomTerm": "custom term value" + "myCustomTerm": "custom term value", + "rdfs:label": "RO-Crate with custom terms", + "rdfs:comment": "This is a comment using a term from the rdfs namespace" }, { "@id": "./", diff --git a/tests/data/profiles/sparql_test/must/agent_project_intersection.ttl b/tests/data/profiles/sparql_test/must/agent_project_intersection.ttl new file mode 100644 index 00000000..7237b33e --- /dev/null +++ b/tests/data/profiles/sparql_test/must/agent_project_intersection.ttl @@ -0,0 +1,38 @@ +# Copyright (c) 2024-2026 CRS4 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +@prefix dct: . +@prefix ro-crate: . +@prefix sh: . +@prefix xsd: . + +# Test shape for SPARQL constraint with BNode sourceShape resolution +# This shape ALWAYS produces a violation to test the handling of SPARQL constraints +# with BNode sourceShape (sh:sparql creates a BNode constraint node) + +ro-crate:AlwaysFailShape a sh:NodeShape ; + sh:name "Always Fail Test" ; + sh:description "Test shape that always produces a violation to test SPARQL constraint handling." ; + sh:targetNode ro-crate:ROCrateMetadataFileDescriptor ; + sh:sparql [ + a sh:SPARQLConstraint ; + sh:message "This is a test violation to verify SPARQL constraint handling" ; + sh:select """ + SELECT $this + WHERE { + BIND($this AS ?fail) + } + """ ; + sh:severity sh:Violation ; + ] . diff --git a/tests/data/profiles/sparql_test/profile.ttl b/tests/data/profiles/sparql_test/profile.ttl new file mode 100644 index 00000000..a243e75f --- /dev/null +++ b/tests/data/profiles/sparql_test/profile.ttl @@ -0,0 +1,24 @@ +# Copyright (c) 2024-2026 CRS4 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +@prefix dct: . +@prefix prof: . +@prefix rdfs: . + + a prof:Profile ; + rdfs:label "SPARQL Constraint Test Profile" ; + rdfs:comment """A test profile to verify SPARQL constraint handling with BNode sourceShapes."""@en ; + dct:publisher ; + # prof:isProfileOf ; + prof:hasToken "sparql-test" ; + . diff --git a/tests/integration/profiles/ro-crate/test_data_entity_metadata.py b/tests/integration/profiles/ro-crate/test_data_entity_metadata.py index 7236cfac..01b1a5a0 100644 --- a/tests/integration/profiles/ro-crate/test_data_entity_metadata.py +++ b/tests/integration/profiles/ro-crate/test_data_entity_metadata.py @@ -48,6 +48,16 @@ def test_data_entity_must_be_directly_linked(): ) +def test_data_entity_not_linked(): + """Test a RO-Crate with datasets not linked to the root data entity.""" + do_entity_test( + paths.dataset_not_linked_to_root, + models.Severity.REQUIRED, + False, + skip_checks=[SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER] + ) + + def test_data_entity_must_be_indirectly_linked(): """Test a RO-Crate without a root data entity.""" do_entity_test( diff --git a/tests/integration/profiles/ro-crate/test_valid_ro-crate.py b/tests/integration/profiles/ro-crate/test_valid_ro-crate.py index 1f63cbaa..2f86f389 100644 --- a/tests/integration/profiles/ro-crate/test_valid_ro-crate.py +++ b/tests/integration/profiles/ro-crate/test_valid_ro-crate.py @@ -102,3 +102,14 @@ def test_valid_roc_remote_bagit_required(): Severity.REQUIRED, True ) + + +def test_valid_roc_with_at_base_set(): + """Test a valid RO-Crate.""" + do_entity_test( + ValidROC().rocrate_with_at_base_set, + Severity.REQUIRED, + True, + # Skipping check: Root Data Entity URI ending with / is not required in RO-Crate 1.2 + skip_checks=["ro-crate-1.1_10.1"] + ) diff --git a/tests/integration/profiles/test_metadata_only.py b/tests/integration/profiles/test_metadata_only.py index c4fabca0..9b998a54 100644 --- a/tests/integration/profiles/test_metadata_only.py +++ b/tests/integration/profiles/test_metadata_only.py @@ -34,7 +34,12 @@ def valid_roc_paths(): value for attr in dir(valid_roc) if not attr.startswith('_') - and not any(excluded in attr for excluded in ('bagit', 'multi_profile_crate', 'rocrate_with_relative_root')) + and not any(excluded in attr for excluded in ( + 'bagit', + 'multi_profile_crate', + 'rocrate_with_relative_root', + 'rocrate_with_at_base_set' # Excluded: has dedicated test with skip_checks + )) and not str(value := getattr(valid_roc, attr)).endswith('.zip') ] diff --git a/tests/integration/test_sparql_constraints.py b/tests/integration/test_sparql_constraints.py new file mode 100644 index 00000000..a59bdfa8 --- /dev/null +++ b/tests/integration/test_sparql_constraints.py @@ -0,0 +1,189 @@ +# Copyright (c) 2024-2026 CRS4 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Integration tests for SPARQL constraint handling in SHACL validation. + +These tests verify that: +1. Violations from SPARQL constraints (where sourceShape is a BNode) + are correctly resolved to their parent NodeShape/PropertyShape +2. The import cycle between utils.py and models.py is properly resolved +3. Error messages include appropriate context from the parent shape + +The tests use a custom profile (sparql-test) that extends ro-crate +and includes a NodeShape with a sh:sparql constraint. +""" + +import json +import logging +import os +import tempfile +from pathlib import Path + +import pytest +from rdflib import BNode, Graph, Namespace, URIRef + +from rocrate_validator import models, services +from rocrate_validator.models import Severity, ValidationResult +from rocrate_validator.requirements.shacl.models import Shape, ShapesRegistry +from rocrate_validator.requirements.shacl.utils import resolve_parent_shape +from tests.conftest import TEST_DATA_PATH + + +logger = logging.getLogger(__name__) + +CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) + +SPARQL_TEST_PROFILES_PATH = os.path.join(TEST_DATA_PATH, "profiles", "sparql_test") + + +@pytest.fixture +def sparql_test_profiles_path(): + """Path to the test profile with SPARQL constraints.""" + return SPARQL_TEST_PROFILES_PATH + + +@pytest.fixture +def sparql_test_rocrate(): + """Create a minimal valid RO-Crate for SPARQL constraint testing.""" + with tempfile.TemporaryDirectory() as tmp_dir: + rocrate_dir = Path(tmp_dir) / "sparql_test_crate" + rocrate_dir.mkdir() + + metadata = { + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": {"@id": "https://w3id.org/ro/crate/1.1"}, + "about": {"@id": "./"}, + }, + { + "@id": "./", + "@type": "Dataset", + "name": "Test RO-Crate for SPARQL Constraints", + "description": "A minimal RO-Crate to test SPARQL constraint handling", + "datePublished": "2024-01-01", + "license": {"@id": "http://spdx.org/licenses/CC0-1.0"}, + }, + ], + } + + with open(rocrate_dir / "ro-crate-metadata.json", "w") as f: + json.dump(metadata, f, indent=2) + + yield rocrate_dir + + +def test_sparql_profile_shape_loaded_correctly(sparql_test_profiles_path): + """Test that the sparql-test profile loads the test shape with SPARQL constraint.""" + registry = ShapesRegistry() + shape_file = os.path.join( + sparql_test_profiles_path, "must", "agent_project_intersection.ttl" + ) + + shapes = registry.load_shapes(shape_file) + + assert len(shapes) > 0, "Should load at least one shape" + + # Find the test shape (AlwaysFailShape or similar name) + test_shape = None + for shape in shapes: + if ( + "Always" in shape.name + or "Test" in shape.name + or "test" in shape.name.lower() + ): + test_shape = shape + break + + assert test_shape is not None, "Should find the test SPARQL shape" + assert test_shape.description is not None + assert len(test_shape.description) > 0 + + +def test_sparql_constraint_with_bnode_sourceShape( + sparql_test_profiles_path, sparql_test_rocrate +): + """ + Test that SPARQL constraint violations with BNode sourceShape + are handled gracefully by the validation pipeline. + + Uses the sparql-test profile which includes AgentProjectIntersection + shape with a sh:sparql constraint targeting ROCrateMetadataFileDescriptor. + """ + result = services.validate( + models.ValidationSettings( + rocrate_uri=sparql_test_rocrate, + requirement_severity=Severity.REQUIRED, + profile_identifier="sparql-test", + profiles_path=sparql_test_profiles_path, + ) + ) + + assert result is not None + assert isinstance(result, ValidationResult) + assert result.context is not None + # The SPARQL constraint uses FILTER(true) so it should produce a violation + issues = list(result.get_issues(Severity.REQUIRED)) + assert len(issues) > 0, "Expected issues from SPARQL constraint violation" + assert issues[0].check is not None, "Issue should have an associated check" + assert issues[0].check.description is not None, "Check should have a description" + assert issues[0].message is not None, "Issue should have a message" + assert len(issues[0].message) > 0, "Issue message should not be empty" + assert ( + "SPARQL constraint violation" in issues[0].message + or "SPARQL" in issues[0].check.description + ), "Check description should reference parent shape" + + +def test_resolve_parent_shape_with_sparql_bnode(): + """ + Test resolve_parent_shape with a BNode representing a sh:sparql constraint. + + This simulates the scenario where pyshacl reports a violation with + sourceShape being a BNode (the sh:SPARQLConstraint). + """ + SHACL = Namespace("http://www.w3.org/ns/shacl#") + + registry = ShapesRegistry() + profiles_path = "rocrate_validator/profiles/ro-crate/must" + + # Load shapes from profile + for filename in os.listdir(profiles_path): + if filename.endswith(".ttl"): + registry.load_shapes(os.path.join(profiles_path, filename)) + + g = Graph() + + # Simulate a NodeShape with a SPARQL constraint (BNode) + node_shape_uri = URIRef("http://example.org/TestShape") + sparql_bnode = BNode() + + g.add((node_shape_uri, SHACL.sparql, sparql_bnode)) + g.add((node_shape_uri, SHACL.targetClass, URIRef("http://example.org/TestClass"))) + + # Create and register the shape + shape = Shape(node_shape_uri, g) + shape._name = "TestShape" + shape._description = "Test shape for SPARQL constraint" + registry.add_shape(shape) + + # Resolve the parent shape from the BNode + result = resolve_parent_shape(g, sparql_bnode, registry) + + assert result is not None, "Should resolve parent shape for SPARQL BNode" + assert result.key == shape.key, "Resolved shape key should match the original shape" + assert result.name == "TestShape", "Resolved shape should have correct name" diff --git a/tests/ro_crates.py b/tests/ro_crates.py index df0ff9d1..7d940768 100644 --- a/tests/ro_crates.py +++ b/tests/ro_crates.py @@ -55,6 +55,10 @@ def rocrate_with_value_objects(self) -> Path: def rocrate_with_relative_root(self) -> Path: return VALID_CRATES_DATA_PATH / "rocrate-relative-root" + @property + def rocrate_with_at_base_set(self) -> Path: + return VALID_CRATES_DATA_PATH / "rocrate-with-at-base-set" + @property def bagit(self) -> Path: return VALID_CRATES_DATA_PATH / "bagit" @@ -246,6 +250,10 @@ def missing_hasPart_data_entity_reference(self) -> Path: def direct_hasPart_data_entity_reference(self) -> Path: return self.base_path / "valid_direct_hasPart_reference" + @property + def dataset_not_linked_to_root(self) -> Path: + return self.base_path / "cyclic_datasets" + @property def indirect_hasPart_data_entity_reference(self) -> Path: return self.base_path / "valid_indirect_hasPart_reference" diff --git a/tests/unit/requirements/test_shacl_checks.py b/tests/unit/requirements/test_shacl_checks.py new file mode 100644 index 00000000..808f77bc --- /dev/null +++ b/tests/unit/requirements/test_shacl_checks.py @@ -0,0 +1,222 @@ +# Copyright (c) 2024-2026 CRS4 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging + +from rdflib import BNode, Graph, Namespace, URIRef + +from rocrate_validator.constants import SHACL_NS +from rocrate_validator.requirements.shacl.checks import SHACLCheck +from rocrate_validator.requirements.shacl.models import Shape, ShapesRegistry +from rocrate_validator.requirements.shacl.utils import resolve_parent_shape + +logger = logging.getLogger(__name__) + + +class MockRequirement: + def __init__(self): + self.profile = None + self.requirement_level_from_path = None + + +class MockParentShape: + def __init__(self, name=None, description=None): + self.name = name + self.description = description + + +def test_description_fallback_shape_with_description(): + """Test that description returns shape's description when available.""" + g = Graph() + shape = Shape(URIRef("http://example.org/shape"), g) + shape._name = "TestShape" + shape._description = "Test Description" + + req = MockRequirement() + check = SHACLCheck(req, shape) + + assert check.description == "Test Description" + + +def test_description_fallback_shape_without_description(): + """Test fallback description when shape has no description but has name.""" + g = Graph() + shape = Shape(URIRef("http://example.org/shape"), g) + shape._name = "TestShape" + shape._description = None + + req = MockRequirement() + check = SHACLCheck(req, shape) + + assert check.description == "Check for TestShape" + + +def test_description_fallback_parent_description(): + """Test fallback to parent description when shape has no description.""" + g = Graph() + shape = Shape(URIRef("http://example.org/shape"), g) + shape._name = "ChildShape" + shape._description = None + shape._parent = MockParentShape( + name="ParentShape", description="Parent Description" + ) + + req = MockRequirement() + check = SHACLCheck(req, shape) + + assert check.description == "Parent Description" + + +def test_description_fallback_no_name_no_description(): + """Test fallback when shape has no name and no description.""" + g = Graph() + shape = Shape(BNode(), g) + shape._name = None + shape._description = None + + req = MockRequirement() + check = SHACLCheck(req, shape) + + # BNode generates a name from node_name, so we check it starts with the fallback prefix + assert check.description.startswith("Check for ") + + +def test_description_fallback_no_description_no_parent_description(): + """Test fallback when shape has no description but parent has no description either.""" + g = Graph() + shape = Shape(BNode(), g) + shape._name = "ChildShape" + shape._description = None + shape._parent = MockParentShape(name="ParentShape", description=None) + + req = MockRequirement() + check = SHACLCheck(req, shape) + + assert check.description == "Check for ChildShape" + + +def test_property_shape_description_fallback(): + """Test description fallback for PropertyShape without explicit description.""" + from rocrate_validator.requirements.shacl.models import PropertyShape + + g = Graph() + prop = PropertyShape(URIRef("http://example.org/property"), g) + prop._name = "testProperty" + prop._description = None + prop._parent = MockParentShape(name="ParentShape", description="Parent Description") + + req = MockRequirement() + check = SHACLCheck(req, prop) + + assert "testProperty" in check.description + assert "ParentShape" in check.description + + +def test_resolve_parent_shape_function_callable(): + """Verify that resolve_parent_shape is callable without circular import.""" + result = resolve_parent_shape(Graph(), BNode(), ShapesRegistry()) + assert result is None + + +def test_resolve_parent_shape_via_sparql_predicate(): + """When sourceShape is a BNode SPARQL constraint, resolves the owning NodeShape.""" + SHACL = Namespace(SHACL_NS) + g = Graph() + node_shape_uri = URIRef("http://example.org/NodeShape") + sparql_bnode = BNode() + g.add((node_shape_uri, SHACL.sparql, sparql_bnode)) + g.add((node_shape_uri, SHACL.name, URIRef("http://example.org/name"))) + + shape = Shape(node_shape_uri, g) + registry = ShapesRegistry() + registry.add_shape(shape) + + result = resolve_parent_shape(g, sparql_bnode, registry) + assert result is not None + assert result.key == shape.key + + +def test_resolve_parent_shape_via_property_predicate(): + """When sourceShape is a BNode property constraint, resolves the owning NodeShape.""" + SHACL = Namespace(SHACL_NS) + g = Graph() + node_shape_uri = URIRef("http://example.org/NodeShape2") + prop_bnode = BNode() + g.add((node_shape_uri, SHACL.property, prop_bnode)) + + shape = Shape(node_shape_uri, g) + registry = ShapesRegistry() + registry.add_shape(shape) + + result = resolve_parent_shape(g, prop_bnode, registry) + assert result is not None + assert result.key == shape.key + + +def test_resolve_parent_shape_returns_none_for_uri_ref(): + """Returns None immediately when sourceShape is a URIRef (not a BNode).""" + g = Graph() + node_shape_uri = URIRef("http://example.org/NodeShape3") + + shape = Shape(node_shape_uri, g) + registry = ShapesRegistry() + registry.add_shape(shape) + + # URIRef — should not attempt parent lookup + result = resolve_parent_shape(g, node_shape_uri, registry) + assert result is None + + +def test_resolve_parent_shape_returns_none_when_no_parent_in_graph(): + """Returns None when the BNode has no parent in the shapes graph.""" + g = Graph() + orphan_bnode = BNode() + # nothing connects orphan_bnode to any shape in g + + registry = ShapesRegistry() + + result = resolve_parent_shape(g, orphan_bnode, registry) + assert result is None + + +def test_resolve_parent_shape_with_property_bnode(): + """ + Test resolve_parent_shape with a BNode representing a sh:property constraint. + + This simulates the scenario where pyshacl reports a violation with + sourceShape being a BNode (a sh:PropertyShape defined inline). + """ + SHACL = Namespace("http://www.w3.org/ns/shacl#") + + registry = ShapesRegistry() + + g = Graph() + + # Simulate a NodeShape with a property constraint (BNode) + node_shape_uri = URIRef("http://example.org/ParentShape") + property_bnode = BNode() + + g.add((node_shape_uri, SHACL.property, property_bnode)) + g.add((node_shape_uri, SHACL.targetClass, URIRef("http://example.org/TestClass"))) + + # Create and register the shape + shape = Shape(node_shape_uri, g) + shape._name = "ParentShape" + registry.add_shape(shape) + + # Resolve the parent shape from the BNode + result = resolve_parent_shape(g, property_bnode, registry) + + assert result is not None, "Should resolve parent shape for property BNode" + assert result.key == shape.key diff --git a/tests/unit/test_remote_context_retrieval.py b/tests/unit/test_remote_context_retrieval.py new file mode 100644 index 00000000..01151aaf --- /dev/null +++ b/tests/unit/test_remote_context_retrieval.py @@ -0,0 +1,333 @@ +# Copyright (c) 2024-2026 CRS4 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import importlib.util +import sys +from unittest.mock import MagicMock + +import pytest + + +@pytest.fixture(scope="module") +def fd_format(): + """Load the module with numeric prefix.""" + spec = importlib.util.spec_from_file_location( + "fd_format", + "rocrate_validator/profiles/ro-crate/must/0_file_descriptor_format.py" + ) + module = importlib.util.module_from_spec(spec) + sys.modules["fd_format"] = module + spec.loader.exec_module(module) + return module + + +class TestGetRemoteContextLogic: + """Test the logic of __get_remote_context__ method without needing to instantiate the class.""" + + def test_success_with_correct_content_type(self, fd_format): + """Test successful context retrieval with correct Content-Type.""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.headers = {"Content-Type": "application/ld+json"} + mock_response.json.return_value = {"@context": {"name": "https://schema.org/name"}} + + original_requester = fd_format.HttpRequester + fd_format.HttpRequester = lambda: MagicMock(get=lambda url, headers=None: mock_response) + + try: + checker = object.__new__(fd_format.FileDescriptorJsonLdFormat) + result = checker.__get_remote_context__("https://example.com/context.json") + assert result == {"name": "https://schema.org/name"} + finally: + fd_format.HttpRequester = original_requester + + def test_fallback_to_alternate_link(self, fd_format): + """Test fallback to alternate Link header when Content-Type is not application/ld+json.""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.headers = { + "Content-Type": "text/html", + "Link": '; rel="alternate"; type="application/ld+json"' + } + + mock_alternate_response = MagicMock() + mock_alternate_response.status_code = 200 + mock_alternate_response.headers = {"Content-Type": "application/ld+json"} + mock_alternate_response.json.return_value = {"@context": {"alternate": "value"}} + + original_requester = fd_format.HttpRequester + call_count = [0] + + def mock_requester(): + class MockHttpRequester: + def get(self, url, headers=None): + call_count[0] += 1 + # Return the initial response on the first call, + # and the alternate response on the second call + if call_count[0] == 1: + return mock_response + # Return the alternate response for the second call + return mock_alternate_response + return MockHttpRequester() + + fd_format.HttpRequester = mock_requester + + try: + checker = object.__new__(fd_format.FileDescriptorJsonLdFormat) + result = checker.__get_remote_context__("https://example.com/context.json") + assert result == {"alternate": "value"} + finally: + fd_format.HttpRequester = original_requester + + def test_relative_alternate_url_resolution(self, fd_format): + """Test relative URL resolution for alternate links.""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.headers = { + "Content-Type": "text/html", + "Link": '<./alternate-context.json>; rel="alternate"; type="application/ld+json"' + } + + mock_alternate_response = MagicMock() + mock_alternate_response.status_code = 200 + mock_alternate_response.headers = {"Content-Type": "application/ld+json"} + mock_alternate_response.json.return_value = {"@context": {"relative": "resolved"}} + + original_requester = fd_format.HttpRequester + call_count = [0] + call_args_list = [] + + def mock_requester(): + class MockHttpRequester: + def get(self, url, headers=None): + call_count[0] += 1 + call_args_list.append((url, headers)) + if call_count[0] == 1: + return mock_response + return mock_alternate_response + return MockHttpRequester() + + fd_format.HttpRequester = mock_requester + + try: + checker = object.__new__(fd_format.FileDescriptorJsonLdFormat) + result = checker.__get_remote_context__("https://example.com/base/context.json") + assert result == {"relative": "resolved"} + assert call_count[0] == 2 # Ensure both requests were made + # Check that the first request was made to the original context URI + assert call_args_list[0][0] == "https://example.com/base/context.json", \ + f"The first request should be made to the original context URI " \ + f"{'https://example.com/base/context.json'}, " \ + f"but got {call_args_list[0][0]}" + # Check that the second request was made to the resolved alternate URL + assert call_args_list[1][0] == "https://example.com/base/alternate-context.json", \ + f"The second request should be made to the resolved alternate URL " \ + f"{'https://example.com/base/alternate-context.json'}, " \ + f"but got {call_args_list[1][0]}" + finally: + fd_format.HttpRequester = original_requester + + def test_no_content_type_no_alternate_raises_error(self, fd_format): + """Test error when no Content-Type and no alternate Link header.""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.headers = {"Content-Type": "text/html"} + + original_requester = fd_format.HttpRequester + fd_format.HttpRequester = lambda: MagicMock(get=lambda url, headers=None: mock_response) + + try: + checker = object.__new__(fd_format.FileDescriptorJsonLdFormat) + with pytest.raises(RuntimeError) as exc_info: + checker.__get_remote_context__("https://example.com/context.json") + assert "no alternate link found in Link header" in str(exc_info.value) + finally: + fd_format.HttpRequester = original_requester + + def test_invalid_alternate_link_format_raises_error(self, fd_format): + """Test error when alternate Link header format is invalid.""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.headers = { + "Content-Type": "text/html", + "Link": "invalid-link-format" + } + + original_requester = fd_format.HttpRequester + fd_format.HttpRequester = lambda: MagicMock(get=lambda url, headers=None: mock_response) + + try: + checker = object.__new__(fd_format.FileDescriptorJsonLdFormat) + with pytest.raises(RuntimeError) as exc_info: + checker.__get_remote_context__("https://example.com/context.json") + assert "no alternate link found" in str(exc_info.value) + finally: + fd_format.HttpRequester = original_requester + + def test_failed_request_raises_error(self, fd_format): + """Test error when HTTP request fails.""" + mock_response = MagicMock() + mock_response.status_code = 404 + + original_requester = fd_format.HttpRequester + fd_format.HttpRequester = lambda: MagicMock(get=lambda url, headers=None: mock_response) + + try: + checker = object.__new__(fd_format.FileDescriptorJsonLdFormat) + with pytest.raises(RuntimeError) as exc_info: + checker.__get_remote_context__("https://example.com/context.json") + assert "Unable to retrieve" in str(exc_info.value) + finally: + fd_format.HttpRequester = original_requester + + def test_alternate_request_failed_raises_error(self, fd_format): + """Test error when alternate URL request fails.""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.headers = { + "Content-Type": "text/html", + "Link": '; rel="alternate"; type="application/ld+json"' + } + + mock_alternate_response = MagicMock() + mock_alternate_response.status_code = 500 + + original_requester = fd_format.HttpRequester + call_count = [0] + + def mock_requester(): + class MockHttpRequester: + def get(self, url, headers=None): + call_count[0] += 1 + if call_count[0] == 1: + return mock_response + return mock_alternate_response + return MockHttpRequester() + + fd_format.HttpRequester = mock_requester + + try: + checker = object.__new__(fd_format.FileDescriptorJsonLdFormat) + with pytest.raises(RuntimeError) as exc_info: + checker.__get_remote_context__("https://example.com/context.json") + assert "Unable to retrieve the JSON-LD context from alternate URL" in str(exc_info.value) + finally: + fd_format.HttpRequester = original_requester + + def test_alternate_wrong_content_type_raises_error(self, fd_format): + """Test error when alternate URL returns wrong Content-Type.""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.headers = { + "Content-Type": "text/html", + "Link": '; rel="alternate"; type="application/ld+json"' + } + + mock_alternate_response = MagicMock() + mock_alternate_response.status_code = 200 + mock_alternate_response.headers = {"Content-Type": "text/plain"} + + original_requester = fd_format.HttpRequester + call_count = [0] + + def mock_requester(): + class MockHttpRequester: + def get(self, url, headers=None): + call_count[0] += 1 + if call_count[0] == 1: + return mock_response + return mock_alternate_response + return MockHttpRequester() + + fd_format.HttpRequester = mock_requester + + try: + checker = object.__new__(fd_format.FileDescriptorJsonLdFormat) + with pytest.raises(RuntimeError) as exc_info: + checker.__get_remote_context__("https://example.com/context.json") + assert "does not have a Content-Type of application/ld+json" in str(exc_info.value) + finally: + fd_format.HttpRequester = original_requester + + +class TestCheckRemoteContext: + + def test_check_remote_context_valid(self, fd_format): + """Test successful remote context validation.""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.headers = {"Content-Type": "application/ld+json"} + mock_response.json.return_value = {"@context": {"name": "https://schema.org/name"}} + + original_requester = fd_format.HttpRequester + fd_format.HttpRequester = lambda: MagicMock(get=lambda url, headers=None: mock_response) + + try: + checker = object.__new__(fd_format.FileDescriptorJsonLdFormat) + result = checker.__check_remote_context__("https://example.com/context.json") + assert result is True + finally: + fd_format.HttpRequester = original_requester + + def test_check_remote_context_invalid(self, fd_format): + """Test failed remote context validation.""" + mock_response = MagicMock() + mock_response.status_code = 404 + + original_requester = fd_format.HttpRequester + fd_format.HttpRequester = lambda: MagicMock(get=lambda url, headers=None: mock_response) + + try: + checker = object.__new__(fd_format.FileDescriptorJsonLdFormat) + result = checker.__check_remote_context__("https://example.com/context.json") + assert result is False + finally: + fd_format.HttpRequester = original_requester + + +class TestGetContextKeys: + + def test_get_context_keys_from_string(self, fd_format): + """Test getting context keys from a remote URI string.""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.headers = {"Content-Type": "application/ld+json"} + mock_response.json.return_value = {"@context": {"key1": "value1", "key2": "value2"}} + + original_requester = fd_format.HttpRequester + fd_format.HttpRequester = lambda: MagicMock(get=lambda url, headers=None: mock_response) + + try: + checker = object.__new__(fd_format.FileDescriptorJsonLdFormat) + result = checker.__get_context_keys__("https://example.com/context.json") + assert result == {"key1", "key2"} + finally: + fd_format.HttpRequester = original_requester + + def test_get_context_keys_from_dict(self, fd_format): + """Test getting context keys from a dictionary.""" + checker = object.__new__(fd_format.FileDescriptorJsonLdFormat) + + ctx = {"key1": "value1", "key2": "value2"} + result = checker.__get_context_keys__(ctx) + assert result == {"key1", "key2"} + + def test_get_context_keys_from_list(self, fd_format): + """Test getting context keys from a list of contexts.""" + checker = object.__new__(fd_format.FileDescriptorJsonLdFormat) + + ctx1 = {"key1": "value1"} + ctx2 = {"key2": "value2"} + result = checker.__get_context_keys__([ctx1, ctx2]) + assert result == {"key1", "key2"}