diff --git a/.github/workflows/build-on-change-linux-bare.yaml b/.github/workflows/build-on-change-linux-bare.yaml index 5c6cad9f..996d0394 100644 --- a/.github/workflows/build-on-change-linux-bare.yaml +++ b/.github/workflows/build-on-change-linux-bare.yaml @@ -35,7 +35,7 @@ on: jobs: build-linux: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 env: TEST_ENABLED: ${{ github.event_name == 'pull_request' && 'ON' || 'OFF' }} @@ -120,8 +120,9 @@ jobs: cp -r /home/$(whoami)/qc-build/bin bin cp out_build/examples/checker_bundle_example/DemoCheckerBundle bin/ cd runtime - python3 -m pip install -r requirements.txt - python3 -m pytest -rA > runtime_test.log + curl -sSL https://install.python-poetry.org | python3 - + poetry install --with dev + poetry run pytest -rA > runtime_test.log - name: Archive runtime test results if: github.event_name == 'pull_request' && (success() || failure()) diff --git a/.github/workflows/build-on-change-windows.yaml b/.github/workflows/build-on-change-windows.yaml index 738b0516..46b40681 100644 --- a/.github/workflows/build-on-change-windows.yaml +++ b/.github/workflows/build-on-change-windows.yaml @@ -160,8 +160,8 @@ jobs: Copy-Item -Path "$env:WORKING_PATH\qc-framework\qc-framework\out_build\examples\checker_bundle_example\Release\DemoCheckerBundle.exe" -Destination "$env:WORKING_PATH\qc-framework\qc-framework\bin" cd "$env:WORKING_PATH\qc-framework\qc-framework\runtime" - python3 -m pip install -r requirements.txt - python3 -m pytest - + python3 -m pip install poetry + python3 -m poetry install + python3 -m poetry run pytest Write-Output "All runtime tests done." shell: pwsh diff --git a/demo_pipeline/manifests/framework_manifest.json b/demo_pipeline/manifests/framework_manifest.json new file mode 100644 index 00000000..cd2f1102 --- /dev/null +++ b/demo_pipeline/manifests/framework_manifest.json @@ -0,0 +1,9 @@ +{ + "manifest_file_path": [ + "/app/demo_pipeline/manifests/otx_manifest.json", + "/app/demo_pipeline/manifests/osc_manifest.json", + "/app/demo_pipeline/manifests/odr_manifest.json", + "/app/demo_pipeline/manifests/result_pooling.json", + "/app/demo_pipeline/manifests/text_report.json" + ] +} diff --git a/demo_pipeline/manifests/odr_manifest.json b/demo_pipeline/manifests/odr_manifest.json new file mode 100644 index 00000000..25cc2bb2 --- /dev/null +++ b/demo_pipeline/manifests/odr_manifest.json @@ -0,0 +1,10 @@ +{ + "module": [ + { + "name": "xodrBundle", + "exec_type": "executable", + "module_type": "checker_bundle", + "exec_command": "cd $ASAM_QC_FRAMEWORK_WORKING_DIR && qc_opendrive -c $ASAM_QC_FRAMEWORK_CONFIG_FILE" + } + ] + } diff --git a/demo_pipeline/manifests/osc_manifest.json b/demo_pipeline/manifests/osc_manifest.json new file mode 100644 index 00000000..05dde507 --- /dev/null +++ b/demo_pipeline/manifests/osc_manifest.json @@ -0,0 +1,10 @@ +{ + "module": [ + { + "name": "xoscBundle", + "exec_type": "executable", + "module_type": "checker_bundle", + "exec_command": "cd $ASAM_QC_FRAMEWORK_WORKING_DIR && qc_openscenario -c $ASAM_QC_FRAMEWORK_CONFIG_FILE" + } + ] + } diff --git a/demo_pipeline/manifests/otx_manifest.json b/demo_pipeline/manifests/otx_manifest.json new file mode 100644 index 00000000..a271b8fd --- /dev/null +++ b/demo_pipeline/manifests/otx_manifest.json @@ -0,0 +1,10 @@ +{ + "module": [ + { + "name": "otxBundle", + "exec_type": "executable", + "module_type": "checker_bundle", + "exec_command": "cd $ASAM_QC_FRAMEWORK_WORKING_DIR && qc_otx -c $ASAM_QC_FRAMEWORK_CONFIG_FILE" + } + ] + } diff --git a/demo_pipeline/manifests/result_pooling.json b/demo_pipeline/manifests/result_pooling.json new file mode 100644 index 00000000..10584137 --- /dev/null +++ b/demo_pipeline/manifests/result_pooling.json @@ -0,0 +1,10 @@ +{ + "module": [ + { + "name": "ResultPooling", + "exec_type": "executable", + "module_type": "result_pooling", + "exec_command": "cd $ASAM_QC_FRAMEWORK_WORKING_DIR && /app/framework/bin/ResultPooling $ASAM_QC_FRAMEWORK_WORKING_DIR $ASAM_QC_FRAMEWORK_CONFIG_FILE" + } + ] + } diff --git a/demo_pipeline/manifests/text_report.json b/demo_pipeline/manifests/text_report.json new file mode 100644 index 00000000..e671124b --- /dev/null +++ b/demo_pipeline/manifests/text_report.json @@ -0,0 +1,10 @@ +{ + "module": [ + { + "name": "TextReport", + "exec_type": "executable", + "module_type": "report_module", + "exec_command": "cd $ASAM_QC_FRAMEWORK_WORKING_DIR && /app/framework/bin/TextReport $ASAM_QC_FRAMEWORK_CONFIG_FILE" + } + ] + } diff --git a/demo_pipeline/otxBundle b/demo_pipeline/otxBundle deleted file mode 100644 index c9fee913..00000000 --- a/demo_pipeline/otxBundle +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -# Copyright 2024, ASAM e.V. -# This Source Code Form is subject to the terms of the Mozilla -# Public License, v. 2.0. If a copy of the MPL was not distributed -# with this file, You can obtain one at https://mozilla.org/MPL/2.0/. -readonly CONFIG_FILE=$1 - -qc_otx -c $CONFIG_FILE -cp *.xqar /app/framework/bin/ diff --git a/demo_pipeline/run_pipeline.sh b/demo_pipeline/run_pipeline.sh index 6449e31a..bac35fc1 100755 --- a/demo_pipeline/run_pipeline.sh +++ b/demo_pipeline/run_pipeline.sh @@ -5,9 +5,9 @@ python3 /app/demo_pipeline/configuration_generator.py -python3 /app/framework/runtime/runtime/runtime.py \ +qc_runtime \ --config "/tmp/generated_config/config.xml" \ - --install_dir "/app/framework/bin" + --manifest "/app/demo_pipeline/manifests/framework_manifest.json" mkdir -p /out/qc-result-$INPUT_FILENAME cp /app/framework/bin/*.xqar /out/qc-result-$INPUT_FILENAME diff --git a/demo_pipeline/templates/otx_template.xml b/demo_pipeline/templates/otx_template.xml index a3100f27..a5b9ff9a 100644 --- a/demo_pipeline/templates/otx_template.xml +++ b/demo_pipeline/templates/otx_template.xml @@ -4,7 +4,7 @@ - + @@ -13,7 +13,7 @@ - + diff --git a/demo_pipeline/templates/xodr_template.xml b/demo_pipeline/templates/xodr_template.xml index 230926f2..54fede79 100644 --- a/demo_pipeline/templates/xodr_template.xml +++ b/demo_pipeline/templates/xodr_template.xml @@ -4,7 +4,7 @@ - + @@ -12,7 +12,7 @@ - + diff --git a/demo_pipeline/templates/xosc_template.xml b/demo_pipeline/templates/xosc_template.xml index e14b76d0..6a12143d 100644 --- a/demo_pipeline/templates/xosc_template.xml +++ b/demo_pipeline/templates/xosc_template.xml @@ -4,7 +4,7 @@ - + @@ -14,7 +14,7 @@ - + diff --git a/demo_pipeline/xodrBundle b/demo_pipeline/xodrBundle deleted file mode 100644 index 953f6a43..00000000 --- a/demo_pipeline/xodrBundle +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -# Copyright 2024, ASAM e.V. -# This Source Code Form is subject to the terms of the Mozilla -# Public License, v. 2.0. If a copy of the MPL was not distributed -# with this file, You can obtain one at https://mozilla.org/MPL/2.0/. -readonly CONFIG_FILE=$1 - -qc_opendrive -c $CONFIG_FILE -cp *.xqar /app/framework/bin/ diff --git a/demo_pipeline/xoscBundle b/demo_pipeline/xoscBundle deleted file mode 100644 index 602477b4..00000000 --- a/demo_pipeline/xoscBundle +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -# Copyright 2024, ASAM e.V. -# This Source Code Form is subject to the terms of the Mozilla -# Public License, v. 2.0. If a copy of the MPL was not distributed -# with this file, You can obtain one at https://mozilla.org/MPL/2.0/. -readonly CONFIG_FILE=$1 - -qc_openscenario -c $CONFIG_FILE -cp *.xqar /app/framework/bin/ diff --git a/docker/Dockerfile.linux b/docker/Dockerfile.linux index 0579e9e1..d432e95b 100644 --- a/docker/Dockerfile.linux +++ b/docker/Dockerfile.linux @@ -50,22 +50,13 @@ RUN echo "Building framework..." && \ cmake --install ./build && \ echo "Done." - -# Copy bash script to install dir - OpenSCENARIO + OpenDRIVE + OTX -RUN chmod +x /app/demo_pipeline/xoscBundle && \ - cp /app/demo_pipeline/xoscBundle /home/root/qc-build/bin/xoscBundle && \ - chmod +x /app/demo_pipeline/xodrBundle && \ - cp /app/demo_pipeline/xodrBundle /home/root/qc-build/bin/xodrBundle && \ - chmod +x /app/demo_pipeline/otxBundle && \ - cp /app/demo_pipeline/otxBundle /home/root/qc-build/bin/otxBundle - RUN python3 -m pip install --no-cache-dir -r /app/demo_pipeline/requirements.txt && \ - python3 -m pip install --no-cache-dir -r /app/framework/runtime/requirements.txt && \ + python3 -m pip install --no-cache-dir /app/framework/runtime && \ python3 -m pip install asam-qc-opendrive@git+https://github.com/asam-ev/qc-opendrive@develop && \ python3 -m pip install asam-qc-openscenarioxml@git+https://github.com/asam-ev/qc-openscenarioxml@develop && \ python3 -m pip install asam-qc-otx@git+https://github.com/asam-ev/qc-otx@develop - - + + # Runtime test stage FROM ubuntu:22.04 as runtime_test @@ -75,6 +66,7 @@ RUN echo "Installing Qt..." && \ qtbase5-dev \ libqt5xmlpatterns5-dev \ libxerces-c-dev \ + python3-pip \ pkg-config && \ echo "Dependencies installed." @@ -95,7 +87,9 @@ SHELL ["/bin/bash", "-c"] COPY --from=framework_builder /usr/local/lib/python3.10/dist-packages /usr/local/lib/python3.10/dist-packages COPY --from=framework_builder /usr/local/bin /usr/local/bin -CMD python3 -m pytest -rA > runtime_test.log && cp /app/framework/runtime/runtime_test.log /out/runtime_test.log +RUN python3 -m pip install poetry==1.8.3 +RUN poetry install --with dev +CMD poetry run pytest -rA > runtime_test.log && cp /app/framework/runtime/runtime_test.log /out/runtime_test.log # Runtime stage FROM framework_builder as unit_test diff --git a/runtime/poetry.lock b/runtime/poetry.lock new file mode 100644 index 00000000..772b7f53 --- /dev/null +++ b/runtime/poetry.lock @@ -0,0 +1,537 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "asam-qc-baselib" +version = "0.1.0" +description = "Python base library for ASAM Quality Checker Framework" +optional = false +python-versions = "^3.10" +files = [] +develop = false + +[package.dependencies] +lxml = "^5.2.2" +pydantic = "^2.7.2" +pydantic-xml = "^2.11.0" + +[package.source] +type = "git" +url = "https://github.com/asam-ev/qc-baselib-py.git" +reference = "develop" +resolved_reference = "74f4d88c6ef07233dec5a381cbc5451aa841bbd4" + +[[package]] +name = "black" +version = "24.8.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"}, + {file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"}, + {file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"}, + {file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"}, + {file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"}, + {file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"}, + {file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"}, + {file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"}, + {file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"}, + {file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"}, + {file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"}, + {file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"}, + {file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"}, + {file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"}, + {file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"}, + {file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"}, + {file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"}, + {file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"}, + {file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"}, + {file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"}, + {file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"}, + {file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "lxml" +version = "5.3.0" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +optional = false +python-versions = ">=3.6" +files = [ + {file = "lxml-5.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd36439be765e2dde7660212b5275641edbc813e7b24668831a5c8ac91180656"}, + {file = "lxml-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ae5fe5c4b525aa82b8076c1a59d642c17b6e8739ecf852522c6321852178119d"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:501d0d7e26b4d261fca8132854d845e4988097611ba2531408ec91cf3fd9d20a"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb66442c2546446944437df74379e9cf9e9db353e61301d1a0e26482f43f0dd8"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e41506fec7a7f9405b14aa2d5c8abbb4dbbd09d88f9496958b6d00cb4d45330"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f7d4a670107d75dfe5ad080bed6c341d18c4442f9378c9f58e5851e86eb79965"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41ce1f1e2c7755abfc7e759dc34d7d05fd221723ff822947132dc934d122fe22"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:44264ecae91b30e5633013fb66f6ddd05c006d3e0e884f75ce0b4755b3e3847b"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:3c174dc350d3ec52deb77f2faf05c439331d6ed5e702fc247ccb4e6b62d884b7"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:2dfab5fa6a28a0b60a20638dc48e6343c02ea9933e3279ccb132f555a62323d8"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b1c8c20847b9f34e98080da785bb2336ea982e7f913eed5809e5a3c872900f32"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2c86bf781b12ba417f64f3422cfc302523ac9cd1d8ae8c0f92a1c66e56ef2e86"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c162b216070f280fa7da844531169be0baf9ccb17263cf5a8bf876fcd3117fa5"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:36aef61a1678cb778097b4a6eeae96a69875d51d1e8f4d4b491ab3cfb54b5a03"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f65e5120863c2b266dbcc927b306c5b78e502c71edf3295dfcb9501ec96e5fc7"}, + {file = "lxml-5.3.0-cp310-cp310-win32.whl", hash = "sha256:ef0c1fe22171dd7c7c27147f2e9c3e86f8bdf473fed75f16b0c2e84a5030ce80"}, + {file = "lxml-5.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:052d99051e77a4f3e8482c65014cf6372e61b0a6f4fe9edb98503bb5364cfee3"}, + {file = "lxml-5.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74bcb423462233bc5d6066e4e98b0264e7c1bed7541fff2f4e34fe6b21563c8b"}, + {file = "lxml-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a3d819eb6f9b8677f57f9664265d0a10dd6551d227afb4af2b9cd7bdc2ccbf18"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b8f5db71b28b8c404956ddf79575ea77aa8b1538e8b2ef9ec877945b3f46442"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3406b63232fc7e9b8783ab0b765d7c59e7c59ff96759d8ef9632fca27c7ee4"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ecdd78ab768f844c7a1d4a03595038c166b609f6395e25af9b0f3f26ae1230f"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168f2dfcfdedf611eb285efac1516c8454c8c99caf271dccda8943576b67552e"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa617107a410245b8660028a7483b68e7914304a6d4882b5ff3d2d3eb5948d8c"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:69959bd3167b993e6e710b99051265654133a98f20cec1d9b493b931942e9c16"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:bd96517ef76c8654446fc3db9242d019a1bb5fe8b751ba414765d59f99210b79"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ab6dd83b970dc97c2d10bc71aa925b84788c7c05de30241b9e96f9b6d9ea3080"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eec1bb8cdbba2925bedc887bc0609a80e599c75b12d87ae42ac23fd199445654"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a7095eeec6f89111d03dabfe5883a1fd54da319c94e0fb104ee8f23616b572d"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f651ebd0b21ec65dfca93aa629610a0dbc13dbc13554f19b0113da2e61a4763"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f422a209d2455c56849442ae42f25dbaaba1c6c3f501d58761c619c7836642ec"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:62f7fdb0d1ed2065451f086519865b4c90aa19aed51081979ecd05a21eb4d1be"}, + {file = "lxml-5.3.0-cp311-cp311-win32.whl", hash = "sha256:c6379f35350b655fd817cd0d6cbeef7f265f3ae5fedb1caae2eb442bbeae9ab9"}, + {file = "lxml-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c52100e2c2dbb0649b90467935c4b0de5528833c76a35ea1a2691ec9f1ee7a1"}, + {file = "lxml-5.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e99f5507401436fdcc85036a2e7dc2e28d962550afe1cbfc07c40e454256a859"}, + {file = "lxml-5.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:384aacddf2e5813a36495233b64cb96b1949da72bef933918ba5c84e06af8f0e"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:874a216bf6afaf97c263b56371434e47e2c652d215788396f60477540298218f"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65ab5685d56914b9a2a34d67dd5488b83213d680b0c5d10b47f81da5a16b0b0e"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aac0bbd3e8dd2d9c45ceb82249e8bdd3ac99131a32b4d35c8af3cc9db1657179"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b369d3db3c22ed14c75ccd5af429086f166a19627e84a8fdade3f8f31426e52a"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24037349665434f375645fa9d1f5304800cec574d0310f618490c871fd902b3"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:62d172f358f33a26d6b41b28c170c63886742f5b6772a42b59b4f0fa10526cb1"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:c1f794c02903c2824fccce5b20c339a1a14b114e83b306ff11b597c5f71a1c8d"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:5d6a6972b93c426ace71e0be9a6f4b2cfae9b1baed2eed2006076a746692288c"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:3879cc6ce938ff4eb4900d901ed63555c778731a96365e53fadb36437a131a99"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:74068c601baff6ff021c70f0935b0c7bc528baa8ea210c202e03757c68c5a4ff"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ecd4ad8453ac17bc7ba3868371bffb46f628161ad0eefbd0a855d2c8c32dd81a"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7e2f58095acc211eb9d8b5771bf04df9ff37d6b87618d1cbf85f92399c98dae8"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e63601ad5cd8f860aa99d109889b5ac34de571c7ee902d6812d5d9ddcc77fa7d"}, + {file = "lxml-5.3.0-cp312-cp312-win32.whl", hash = "sha256:17e8d968d04a37c50ad9c456a286b525d78c4a1c15dd53aa46c1d8e06bf6fa30"}, + {file = "lxml-5.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:c1a69e58a6bb2de65902051d57fde951febad631a20a64572677a1052690482f"}, + {file = "lxml-5.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c72e9563347c7395910de6a3100a4840a75a6f60e05af5e58566868d5eb2d6a"}, + {file = "lxml-5.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e92ce66cd919d18d14b3856906a61d3f6b6a8500e0794142338da644260595cd"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d04f064bebdfef9240478f7a779e8c5dc32b8b7b0b2fc6a62e39b928d428e51"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c2fb570d7823c2bbaf8b419ba6e5662137f8166e364a8b2b91051a1fb40ab8b"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c120f43553ec759f8de1fee2f4794452b0946773299d44c36bfe18e83caf002"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:562e7494778a69086f0312ec9689f6b6ac1c6b65670ed7d0267e49f57ffa08c4"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:423b121f7e6fa514ba0c7918e56955a1d4470ed35faa03e3d9f0e3baa4c7e492"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c00f323cc00576df6165cc9d21a4c21285fa6b9989c5c39830c3903dc4303ef3"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:1fdc9fae8dd4c763e8a31e7630afef517eab9f5d5d31a278df087f307bf601f4"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:658f2aa69d31e09699705949b5fc4719cbecbd4a97f9656a232e7d6c7be1a367"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1473427aff3d66a3fa2199004c3e601e6c4500ab86696edffdbc84954c72d832"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a87de7dd873bf9a792bf1e58b1c3887b9264036629a5bf2d2e6579fe8e73edff"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0d7b36afa46c97875303a94e8f3ad932bf78bace9e18e603f2085b652422edcd"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cf120cce539453ae086eacc0130a324e7026113510efa83ab42ef3fcfccac7fb"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:df5c7333167b9674aa8ae1d4008fa4bc17a313cc490b2cca27838bbdcc6bb15b"}, + {file = "lxml-5.3.0-cp313-cp313-win32.whl", hash = "sha256:c802e1c2ed9f0c06a65bc4ed0189d000ada8049312cfeab6ca635e39c9608957"}, + {file = "lxml-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d"}, + {file = "lxml-5.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8f0de2d390af441fe8b2c12626d103540b5d850d585b18fcada58d972b74a74e"}, + {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1afe0a8c353746e610bd9031a630a95bcfb1a720684c3f2b36c4710a0a96528f"}, + {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56b9861a71575f5795bde89256e7467ece3d339c9b43141dbdd54544566b3b94"}, + {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:9fb81d2824dff4f2e297a276297e9031f46d2682cafc484f49de182aa5e5df99"}, + {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2c226a06ecb8cdef28845ae976da407917542c5e6e75dcac7cc33eb04aaeb237"}, + {file = "lxml-5.3.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:7d3d1ca42870cdb6d0d29939630dbe48fa511c203724820fc0fd507b2fb46577"}, + {file = "lxml-5.3.0-cp36-cp36m-win32.whl", hash = "sha256:094cb601ba9f55296774c2d57ad68730daa0b13dc260e1f941b4d13678239e70"}, + {file = "lxml-5.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:eafa2c8658f4e560b098fe9fc54539f86528651f61849b22111a9b107d18910c"}, + {file = "lxml-5.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cb83f8a875b3d9b458cada4f880fa498646874ba4011dc974e071a0a84a1b033"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25f1b69d41656b05885aa185f5fdf822cb01a586d1b32739633679699f220391"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23e0553b8055600b3bf4a00b255ec5c92e1e4aebf8c2c09334f8368e8bd174d6"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ada35dd21dc6c039259596b358caab6b13f4db4d4a7f8665764d616daf9cc1d"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:81b4e48da4c69313192d8c8d4311e5d818b8be1afe68ee20f6385d0e96fc9512"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:2bc9fd5ca4729af796f9f59cd8ff160fe06a474da40aca03fcc79655ddee1a8b"}, + {file = "lxml-5.3.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:07da23d7ee08577760f0a71d67a861019103e4812c87e2fab26b039054594cc5"}, + {file = "lxml-5.3.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:ea2e2f6f801696ad7de8aec061044d6c8c0dd4037608c7cab38a9a4d316bfb11"}, + {file = "lxml-5.3.0-cp37-cp37m-win32.whl", hash = "sha256:5c54afdcbb0182d06836cc3d1be921e540be3ebdf8b8a51ee3ef987537455f84"}, + {file = "lxml-5.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:f2901429da1e645ce548bf9171784c0f74f0718c3f6150ce166be39e4dd66c3e"}, + {file = "lxml-5.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c56a1d43b2f9ee4786e4658c7903f05da35b923fb53c11025712562d5cc02753"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ee8c39582d2652dcd516d1b879451500f8db3fe3607ce45d7c5957ab2596040"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdf3a3059611f7585a78ee10399a15566356116a4288380921a4b598d807a22"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:146173654d79eb1fc97498b4280c1d3e1e5d58c398fa530905c9ea50ea849b22"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:0a7056921edbdd7560746f4221dca89bb7a3fe457d3d74267995253f46343f15"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:9e4b47ac0f5e749cfc618efdf4726269441014ae1d5583e047b452a32e221920"}, + {file = "lxml-5.3.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f914c03e6a31deb632e2daa881fe198461f4d06e57ac3d0e05bbcab8eae01945"}, + {file = "lxml-5.3.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:213261f168c5e1d9b7535a67e68b1f59f92398dd17a56d934550837143f79c42"}, + {file = "lxml-5.3.0-cp38-cp38-win32.whl", hash = "sha256:218c1b2e17a710e363855594230f44060e2025b05c80d1f0661258142b2add2e"}, + {file = "lxml-5.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:315f9542011b2c4e1d280e4a20ddcca1761993dda3afc7a73b01235f8641e903"}, + {file = "lxml-5.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1ffc23010330c2ab67fac02781df60998ca8fe759e8efde6f8b756a20599c5de"}, + {file = "lxml-5.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2b3778cb38212f52fac9fe913017deea2fdf4eb1a4f8e4cfc6b009a13a6d3fcc"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b0c7a688944891086ba192e21c5229dea54382f4836a209ff8d0a660fac06be"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:747a3d3e98e24597981ca0be0fd922aebd471fa99d0043a3842d00cdcad7ad6a"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86a6b24b19eaebc448dc56b87c4865527855145d851f9fc3891673ff97950540"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b11a5d918a6216e521c715b02749240fb07ae5a1fefd4b7bf12f833bc8b4fe70"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68b87753c784d6acb8a25b05cb526c3406913c9d988d51f80adecc2b0775d6aa"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:109fa6fede314cc50eed29e6e56c540075e63d922455346f11e4d7a036d2b8cf"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:02ced472497b8362c8e902ade23e3300479f4f43e45f4105c85ef43b8db85229"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_s390x.whl", hash = "sha256:6b038cc86b285e4f9fea2ba5ee76e89f21ed1ea898e287dc277a25884f3a7dfe"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:7437237c6a66b7ca341e868cda48be24b8701862757426852c9b3186de1da8a2"}, + {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7f41026c1d64043a36fda21d64c5026762d53a77043e73e94b71f0521939cc71"}, + {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:482c2f67761868f0108b1743098640fbb2a28a8e15bf3f47ada9fa59d9fe08c3"}, + {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:1483fd3358963cc5c1c9b122c80606a3a79ee0875bcac0204149fa09d6ff2727"}, + {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2dec2d1130a9cda5b904696cec33b2cfb451304ba9081eeda7f90f724097300a"}, + {file = "lxml-5.3.0-cp39-cp39-win32.whl", hash = "sha256:a0eabd0a81625049c5df745209dc7fcef6e2aea7793e5f003ba363610aa0a3ff"}, + {file = "lxml-5.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:89e043f1d9d341c52bf2af6d02e6adde62e0a46e6755d5eb60dc6e4f0b8aeca2"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7b1cd427cb0d5f7393c31b7496419da594fe600e6fdc4b105a54f82405e6626c"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51806cfe0279e06ed8500ce19479d757db42a30fd509940b1701be9c86a5ff9a"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee70d08fd60c9565ba8190f41a46a54096afa0eeb8f76bd66f2c25d3b1b83005"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8dc2c0395bea8254d8daebc76dcf8eb3a95ec2a46fa6fae5eaccee366bfe02ce"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6ba0d3dcac281aad8a0e5b14c7ed6f9fa89c8612b47939fc94f80b16e2e9bc83"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:6e91cf736959057f7aac7adfc83481e03615a8e8dd5758aa1d95ea69e8931dba"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:94d6c3782907b5e40e21cadf94b13b0842ac421192f26b84c45f13f3c9d5dc27"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c300306673aa0f3ed5ed9372b21867690a17dba38c68c44b287437c362ce486b"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d9b952e07aed35fe2e1a7ad26e929595412db48535921c5013edc8aa4a35ce"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:01220dca0d066d1349bd6a1726856a78f7929f3878f7e2ee83c296c69495309e"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2d9b8d9177afaef80c53c0a9e30fa252ff3036fb1c6494d427c066a4ce6a282f"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:20094fc3f21ea0a8669dc4c61ed7fa8263bd37d97d93b90f28fc613371e7a875"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ace2c2326a319a0bb8a8b0e5b570c764962e95818de9f259ce814ee666603f19"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92e67a0be1639c251d21e35fe74df6bcc40cba445c2cda7c4a967656733249e2"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd5350b55f9fecddc51385463a4f67a5da829bc741e38cf689f38ec9023f54ab"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c1fefd7e3d00921c44dc9ca80a775af49698bbfd92ea84498e56acffd4c5469"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:71a8dd38fbd2f2319136d4ae855a7078c69c9a38ae06e0c17c73fd70fc6caad8"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:97acf1e1fd66ab53dacd2c35b319d7e548380c2e9e8c54525c6e76d21b1ae3b1"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:68934b242c51eb02907c5b81d138cb977b2129a0a75a8f8b60b01cb8586c7b21"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b710bc2b8292966b23a6a0121f7a6c51d45d2347edcc75f016ac123b8054d3f2"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18feb4b93302091b1541221196a2155aa296c363fd233814fa11e181adebc52f"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3eb44520c4724c2e1a57c0af33a379eee41792595023f367ba3952a2d96c2aab"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:609251a0ca4770e5a8768ff902aa02bf636339c5a93f9349b48eb1f606f7f3e9"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:516f491c834eb320d6c843156440fe7fc0d50b33e44387fcec5b02f0bc118a4c"}, + {file = "lxml-5.3.0.tar.gz", hash = "sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f"}, +] + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html-clean = ["lxml-html-clean"] +html5 = ["html5lib"] +htmlsoup = ["BeautifulSoup4"] +source = ["Cython (>=3.0.11)"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "platformdirs" +version = "4.2.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pydantic" +version = "2.8.2" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, + {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.20.1" +typing-extensions = [ + {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, + {version = ">=4.6.1", markers = "python_version < \"3.13\""}, +] + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.20.1" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"}, + {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"}, + {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"}, + {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"}, + {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"}, + {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"}, + {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"}, + {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"}, + {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"}, + {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"}, + {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"}, + {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"}, + {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"}, + {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pydantic-xml" +version = "2.11.0" +description = "pydantic xml extension" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_xml-2.11.0-py3-none-any.whl", hash = "sha256:033b2b997a99bdb18ab93d7ed66a5bc9ec490753afc011f084bd625ad2d0bdfd"}, + {file = "pydantic_xml-2.11.0.tar.gz", hash = "sha256:7fbe816b8251b45aabe0c11992fc52e2921afd8f46f256b3f60ed82f869361da"}, +] + +[package.dependencies] +pydantic = ">=2.6.0" +pydantic-core = ">=2.15.0" + +[package.extras] +docs = ["Sphinx (>=5.3.0,<6.0.0)", "furo (>=2022.12.7,<2023.0.0)", "sphinx-copybutton (>=0.5.1,<0.6.0)", "sphinx_design (>=0.3.0,<0.4.0)", "toml (>=0.10.2,<0.11.0)"] +lxml = ["lxml (>=4.9.0)"] + +[[package]] +name = "pytest" +version = "8.3.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, + {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.10" +content-hash = "f730c25ae48b4dd8955ac45518f376ea5dff5386c35e32964c1c55acf55d84fa" diff --git a/runtime/pyproject.toml b/runtime/pyproject.toml new file mode 100644 index 00000000..73f2d9d5 --- /dev/null +++ b/runtime/pyproject.toml @@ -0,0 +1,26 @@ +[tool.poetry] +name = "asam-qc-runtime" +version = "0.1.0" +description = "Python runtime module responsible of executing the qc-framework modules specified in an input configuration file." +authors = ["Danilo Romano ","Patrick Abrahão ","Tung Dinh "] +license = "MPL-2.0" +readme = "../doc/manual/runtime_module.md" +packages = [ + { include = "runtime" }, +] + +[tool.poetry.dependencies] +python = "^3.10" +asam-qc-baselib = {git = "https://github.com/asam-ev/qc-baselib-py.git", rev = "develop"} +pydantic = "^2.7.2" + +[tool.poetry.group.dev.dependencies] +pytest = "^8.2.1" +black = "^24.4.2" + +[tool.poetry.scripts] +qc_runtime = 'runtime.runtime:main' + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/runtime/requirements.txt b/runtime/requirements.txt deleted file mode 100644 index 19f48381..00000000 --- a/runtime/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -lxml>=5.0.0 -pytest>=8.2.1 diff --git a/runtime/runtime/__init__.py b/runtime/runtime/__init__.py index e69de29b..253f5724 100644 --- a/runtime/runtime/__init__.py +++ b/runtime/runtime/__init__.py @@ -0,0 +1 @@ +from . import models as models diff --git a/runtime/runtime/models.py b/runtime/runtime/models.py new file mode 100644 index 00000000..2d0243f8 --- /dev/null +++ b/runtime/runtime/models.py @@ -0,0 +1,38 @@ +import os +import enum + +from typing import List +from pydantic import BaseModel, field_validator + + +class ExecType(str, enum.Enum): + EXECUTABLE = "executable" + + +class ModuleType(str, enum.Enum): + CHECKER_BUNDLE = "checker_bundle" + REPORT_MODULE = "report_module" + RESULT_POOLING = "result_pooling" + + +class FrameworkManifest(BaseModel): + manifest_file_path: List[str] + + @field_validator("manifest_file_path") + @classmethod + def file_path_must_exist(cls, v: List[str]) -> str: + for file_path in v: + if not os.path.isfile(file_path): + raise ValueError(f"File path '{file_path}' must exist.") + return v + + +class Module(BaseModel): + name: str + exec_type: ExecType + module_type: ModuleType + exec_command: str + + +class ModuleManifest(BaseModel): + module: List[Module] diff --git a/runtime/runtime/runtime.py b/runtime/runtime/runtime.py index 96f04100..3457ff41 100644 --- a/runtime/runtime/runtime.py +++ b/runtime/runtime/runtime.py @@ -3,188 +3,171 @@ # Public License, v. 2.0. If a copy of the MPL was not distributed # with this file, You can obtain one at https://mozilla.org/MPL/2.0/. -import subprocess -from lxml import etree -import sys, os import argparse -from typing import List - - -def on_windows() -> bool: - """Check if script is executed in Windows OS - - Returns: - bool: True if executing the script on Windows - """ - return os.name == "nt" - - -def check_executable_exists(executable: str) -> bool: - """Check if specified executable exists in current working directory - - Args: - executable (str): executable name to find +import datetime +import os +import subprocess - Returns: - bool: True if executable exists - """ - return executable in list_files_in_cwd() +from typing import Dict +from qc_baselib import Configuration -def is_valid_xml(xml_file: str, schema_file: str) -> bool: - """Check if input xml file (.xml) is valid against the input schema file (.xsd) - Args: - xml_file (str): XML file path to test - schema_file (str): XSD file path containing the schema for the validation - Returns: - bool: True if file pointed by xml_file is valid w.r.t. input schema file. False otherwise - """ - with open(schema_file, "rb") as schema_f: - schema_doc = etree.parse(schema_f) - schema = etree.XMLSchema(schema_doc) +from runtime import models - with open(xml_file, "rb") as xml_f: - xml_doc = etree.parse(xml_f) - if schema.validate(xml_doc): - print("XML is valid.") - return True - else: - print("XML is invalid!") - for error in schema.error_log: - print(error.message) - return False +FRAMEWORK_WORKING_DIR_VAR_NAME = "ASAM_QC_FRAMEWORK_WORKING_DIR" +FRAMEWORK_CONFIG_PATH_VAR_NAME = "ASAM_QC_FRAMEWORK_CONFIG_FILE" -def run_command(cmd_list: List[str]) -> None: - """Execute command specified in cmd_list list parameter +def run_module_command( + module: models.Module, config_file_path: str, output_path: str +) -> None: + """Execute command specified in module configured with information in the + provided configuration. Args: - cmd_list (List): A list containing all the strings that constitutes the command to execute - E.g. cmd_list = ["python3", "script.py", "--help"] - + module (models.Module): module to be executed + config (Configuration): xml configuration containing information for execution. """ try: - print(f"Executing command: {' '.join(cmd_list)}") - process = subprocess.Popen( - cmd_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=os.getcwd() + print(f"Executing command: {module.exec_command}") + + cmd_env = os.environ.copy() + cmd_env[FRAMEWORK_WORKING_DIR_VAR_NAME] = output_path + cmd_env[FRAMEWORK_CONFIG_PATH_VAR_NAME] = config_file_path + + process = subprocess.run( + module.exec_command, + env=cmd_env, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, ) - stdout, stderr = process.communicate() + exit_code = process.returncode if exit_code == 0: print("Command executed successfully.") print("Output:") - print(stdout.decode()) else: print("Error occurred while executing the command.") print("Error message:") - print(stderr.decode()) + + print(process.stdout.decode()) + except Exception as e: print(f"Error: {str(e)}") -def list_files_in_cwd() -> List[str]: - """List all files in current working directory +def execute_modules( + config_file_path: str, + available_checker_bundles: Dict[str, models.Module], + available_result_pooling: models.Module, + available_report_modules: Dict[str, models.Module], + output_path: str, +) -> None: + config = Configuration() + config.load_from_file(config_file_path) - Returns: - List: List with all the executables found in working directory - """ - cwd = os.getcwd() - files = os.listdir(cwd) - files = [f for f in files if os.path.isfile(os.path.join(cwd, f))] - return files + checker_bundles = [] + report_modules = [] + for bundle in config.get_all_checker_bundles(): + name = bundle.application + if name not in available_checker_bundles: + raise RuntimeError( + f"Checker bundle {name} is not available in the framework manifest." + ) -def get_os_command_from_app_name(app_name: str) -> str: - """Get command to execute the specified app_name as input + checker_bundles.append(available_checker_bundles[name]) - The function checks the operating system and build the returning command needed - e.g. app_name: HelloWorld - if the script is executed on Linux the result is "./HelloWorld" - if on Windows the result is HelloWorld.exe + for report in config.get_all_report_modules(): + name = report.application + if name not in available_report_modules: + raise RuntimeError( + f"Report module {name} is not available in the framework manifest." + ) - Args: - app_name (str): The executable name that needs to be decorated with required chars + report_modules.append(available_report_modules[name]) - Returns: - str: A ready to execute string representation of app_name considering the current OS requirements. - """ - os_command = None + # Checker bundles + print(f"Executing checker bundles") + for checker_bundle in checker_bundles: + print(f"Executing checker module: {checker_bundle.name}") + run_module_command(checker_bundle, config_file_path, output_path) - if on_windows(): - app_name = app_name + ".exe" - os_command = app_name - else: - os_command = "./" + app_name + # Result pooling + print(f"Executing result pooling: {available_result_pooling.name}") + run_module_command(available_result_pooling, config_file_path, output_path) - if not check_executable_exists(app_name): - print(f"Executable {app_name} does not exist!\nAbort...") - sys.exit() + # Report modules + print(f"Executing report modules") + for report_module in report_modules: + print(f"Executing report module: {report_module.name}") + run_module_command(report_module, config_file_path, output_path) - return os_command +def execute_runtime(config_file_path: str, manifest_file_path: str) -> None: + """Execute all runtime operations defined in the input manifest over the + defined configuration. -def run_commands_from_xml(xml_file: str, install_folder: str) -> None: - """Execute the qc-framework runtime steps specified in the input xml file - - The function: - - For each checker bundle specified in configuration, execute its process - - Execute result pooling for collect results from all checker bundle executed in step 2 - - For each report module specified in configuration, execute its process Args: - xml_file (str): input configuration xml file - install_folder (str): folder where executables specified in the input xml files are installed + config_file_path (str): input configuration xml file path + manifest_file_path (str): input manifest json file path """ - root = etree.parse(xml_file) - - print("Executable found in install directory: ", list_files_in_cwd()) - - # 1. Execute all CheckerBundle - print("#" * 50) - print("1. Execute all CheckerBundle") - print("#" * 50) - for element in root.findall(".//CheckerBundle"): - app_name = element.get("application") - os_command = get_os_command_from_app_name(app_name) - cmd_list = [os_command, xml_file] - run_command(cmd_list) - - # 2. Execute ResultPooling - print("#" * 50) - print("2. Execute ResultPooling") - print("#" * 50) - app_name = "ResultPooling" - os_command = get_os_command_from_app_name(app_name) - cmd_list = [os_command, xml_file] - run_command(cmd_list) - - # 3. Execute Report Modules - print("#" * 50) - print("3. Execute Report Modules") - print("#" * 50) - for element in root.findall(".//ReportModule"): - app_name = element.get("application") - os_command = get_os_command_from_app_name(app_name) - cmd_list = [os_command, xml_file] - run_command(cmd_list) - - -def execute_runtime(xml_file: str, install_folder: str): - """Execute all runtime operations on input xml file - - Args: - xml_file (str): input configuration xml file - install_folder (str): folder where executables specified in the input xml files are installed - """ - os.chdir(install_folder) + checker_bundles = {} + report_modules = {} + result_pooling = None + + with open(manifest_file_path, "rb") as framework_manifest_file: + json_data = framework_manifest_file.read().decode() + framework_manifest = models.FrameworkManifest.model_validate_json(json_data) + + for module_manifest_path in framework_manifest.manifest_file_path: + with open(module_manifest_path, "rb") as module_manifest_file: + json_data = module_manifest_file.read().decode() + module_manifest = models.ModuleManifest.model_validate_json(json_data) + + for module in module_manifest.module: + if module.module_type == models.ModuleType.CHECKER_BUNDLE: + if module.name in checker_bundles: + raise RuntimeError( + f"{module.name} is used by multiple checker bundles on framework manifest." + ) + checker_bundles[module.name] = module + elif module.module_type == models.ModuleType.REPORT_MODULE: + if module.name in report_modules: + raise RuntimeError( + f"{module.name} is used by multiple report modules on framework manifest." + ) + report_modules[module.name] = module + elif module.module_type == models.ModuleType.RESULT_POOLING: + if result_pooling is not None: + raise RuntimeError( + "Multiple result pooling modules defined on framework manifest. There must be exactly one result pooling module defined." + ) + result_pooling = module + else: + raise RuntimeError( + f"Module of type {module.module_type.name} not supported." + ) + + if result_pooling is None: + raise RuntimeError( + f"No result pooling module found in the provided framework manifest. There must be exactly one result pooling module defined." + ) - schema_file = os.path.join("..", "doc", "schema", "config_format.xsd") + formatted_now = datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_%S_%f") + execution_output_path = os.path.join(os.getcwd(), formatted_now) + os.makedirs(execution_output_path, exist_ok=True) - if not is_valid_xml(xml_file, schema_file): - print("Aborting due to invalid XML.") - sys.exit() - run_commands_from_xml(xml_file, install_folder) + execute_modules( + config_file_path, + checker_bundles, + result_pooling, + report_modules, + execution_output_path, + ) def main(): @@ -196,15 +179,15 @@ def main(): required=True, ) parser.add_argument( - "--install_dir", + "--manifest", type=str, - help="Path where compiled binaries are installed", + help="Path to the JSON manifest file", required=True, ) args = parser.parse_args() - execute_runtime(args.config, args.install_dir) + execute_runtime(args.config, args.manifest) if __name__ == "__main__": diff --git a/runtime/tests/test_data/3steps_config.xml b/runtime/tests/test_data/3step_config.xml similarity index 100% rename from runtime/tests/test_data/3steps_config.xml rename to runtime/tests/test_data/3step_config.xml diff --git a/runtime/tests/test_data/DemoCheckerBundle_config.xml b/runtime/tests/test_data/DemoCheckerBundle_config.xml deleted file mode 100644 index fc533a59..00000000 --- a/runtime/tests/test_data/DemoCheckerBundle_config.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - diff --git a/runtime/tests/test_data/linux/demo_checker_bundle_manifest.json b/runtime/tests/test_data/linux/demo_checker_bundle_manifest.json new file mode 100644 index 00000000..657a5a36 --- /dev/null +++ b/runtime/tests/test_data/linux/demo_checker_bundle_manifest.json @@ -0,0 +1,10 @@ +{ + "module": [ + { + "name": "DemoCheckerBundle", + "exec_type": "executable", + "module_type": "checker_bundle", + "exec_command": "cd $ASAM_QC_FRAMEWORK_WORKING_DIR && ../../bin/DemoCheckerBundle $ASAM_QC_FRAMEWORK_CONFIG_FILE" + } + ] +} diff --git a/runtime/tests/test_data/linux/framework_manifest.json b/runtime/tests/test_data/linux/framework_manifest.json new file mode 100644 index 00000000..93c26ab5 --- /dev/null +++ b/runtime/tests/test_data/linux/framework_manifest.json @@ -0,0 +1,7 @@ +{ + "manifest_file_path": [ + "tests/test_data/linux/demo_checker_bundle_manifest.json", + "tests/test_data/linux/result_pooling_manifest.json", + "tests/test_data/linux/text_report_manifest.json" + ] +} diff --git a/runtime/tests/test_data/linux/result_pooling_manifest.json b/runtime/tests/test_data/linux/result_pooling_manifest.json new file mode 100644 index 00000000..21ca66d6 --- /dev/null +++ b/runtime/tests/test_data/linux/result_pooling_manifest.json @@ -0,0 +1,10 @@ +{ + "module": [ + { + "name": "ResultPooling", + "exec_type": "executable", + "module_type": "result_pooling", + "exec_command": "cd $ASAM_QC_FRAMEWORK_WORKING_DIR && ../../bin/ResultPooling $ASAM_QC_FRAMEWORK_WORKING_DIR $ASAM_QC_FRAMEWORK_CONFIG_FILE" + } + ] + } diff --git a/runtime/tests/test_data/linux/text_report_manifest.json b/runtime/tests/test_data/linux/text_report_manifest.json new file mode 100644 index 00000000..f74f1177 --- /dev/null +++ b/runtime/tests/test_data/linux/text_report_manifest.json @@ -0,0 +1,10 @@ +{ + "module": [ + { + "name": "TextReport", + "exec_type": "executable", + "module_type": "report_module", + "exec_command": "cd $ASAM_QC_FRAMEWORK_WORKING_DIR && ../../bin/TextReport $ASAM_QC_FRAMEWORK_CONFIG_FILE" + } + ] + } diff --git a/runtime/tests/test_data/windows/demo_checker_bundle_manifest.json b/runtime/tests/test_data/windows/demo_checker_bundle_manifest.json new file mode 100644 index 00000000..2f476462 --- /dev/null +++ b/runtime/tests/test_data/windows/demo_checker_bundle_manifest.json @@ -0,0 +1,10 @@ +{ + "module": [ + { + "name": "DemoCheckerBundle", + "exec_type": "executable", + "module_type": "checker_bundle", + "exec_command": "cd %ASAM_QC_FRAMEWORK_WORKING_DIR% && ..\\..\\bin\\DemoCheckerBundle.exe %ASAM_QC_FRAMEWORK_CONFIG_FILE%" + } + ] +} diff --git a/runtime/tests/test_data/windows/framework_manifest.json b/runtime/tests/test_data/windows/framework_manifest.json new file mode 100644 index 00000000..e5253474 --- /dev/null +++ b/runtime/tests/test_data/windows/framework_manifest.json @@ -0,0 +1,7 @@ +{ + "manifest_file_path": [ + "tests\\test_data\\windows\\demo_checker_bundle_manifest.json", + "tests\\test_data\\windows\\result_pooling_manifest.json", + "tests\\test_data\\windows\\text_report_manifest.json" + ] +} diff --git a/runtime/tests/test_data/windows/result_pooling_manifest.json b/runtime/tests/test_data/windows/result_pooling_manifest.json new file mode 100644 index 00000000..6ef2fc7e --- /dev/null +++ b/runtime/tests/test_data/windows/result_pooling_manifest.json @@ -0,0 +1,10 @@ +{ + "module": [ + { + "name": "ResultPooling", + "exec_type": "executable", + "module_type": "result_pooling", + "exec_command": "cd %ASAM_QC_FRAMEWORK_WORKING_DIR% && ..\\..\\bin\\ResultPooling.exe %ASAM_QC_FRAMEWORK_WORKING_DIR% %ASAM_QC_FRAMEWORK_CONFIG_FILE%" + } + ] + } diff --git a/runtime/tests/test_data/windows/text_report_manifest.json b/runtime/tests/test_data/windows/text_report_manifest.json new file mode 100644 index 00000000..ca2be181 --- /dev/null +++ b/runtime/tests/test_data/windows/text_report_manifest.json @@ -0,0 +1,10 @@ +{ + "module": [ + { + "name": "TextReport", + "exec_type": "executable", + "module_type": "report_module", + "exec_command": "cd %ASAM_QC_FRAMEWORK_WORKING_DIR% && ..\\..\\bin\\TextReport.exe %ASAM_QC_FRAMEWORK_CONFIG_FILE%" + } + ] + } diff --git a/runtime/tests/test_runtime.py b/runtime/tests/test_runtime.py index 7d2a4f48..5b867a65 100644 --- a/runtime/tests/test_runtime.py +++ b/runtime/tests/test_runtime.py @@ -4,128 +4,64 @@ # with this file, You can obtain one at https://mozilla.org/MPL/2.0/. import pytest -import runtime.runtime as runtime import os -import subprocess -from lxml import etree -from typing import List - - -def is_valid_xml(xml_file: str, schema_file: str) -> bool: - """Check if input xml file (.xml) is valid against the input schema file (.xsd) +import sys - Args: - xml_file (str): XML file path to test - schema_file (str): XSD file path containing the schema for the validation - - Returns: - bool: True if file pointed by xml_file is valid w.r.t. input schema file. False otherwise - """ - with open(schema_file, "rb") as schema_f: - schema_doc = etree.parse(schema_f) - schema = etree.XMLSchema(schema_doc) - - with open(xml_file, "rb") as xml_f: - xml_doc = etree.parse(xml_f) - - if schema.validate(xml_doc): - print("XML is valid.") - return True - else: - print("XML is invalid!") - for error in schema.error_log: - print(error.message) - return False +import runtime.runtime as runtime -def check_node_exists(xml_file: str, node_name: str) -> bool: - """Check if input node node_name is present in xml document specified in xml_file - Args: - xml_file (str): XML file path to inspect - node_name (str): Name of the node to search in the xml doc +def on_windows() -> bool: + """Check if script is executed in Windows OS Returns: - bool: True if node at least 1 node called node_name is found in the xml file + bool: True if executing the script on Windows """ - # Parse the XML document - tree = etree.parse(xml_file) - # Use XPath to search for the node - nodes = tree.xpath(f'//*[local-name()="{node_name}"]') - return len(nodes) > 0 + return os.name == "nt" -def test_runtime_execution(): - - start_wd = os.getcwd() - install_dir = os.path.join("..", "bin") - os.chdir(install_dir) - - config_xml = os.path.join( - "..", "runtime", "tests", "test_data", "DemoCheckerBundle_config.xml" +def launch_main(monkeypatch, config_file_path: str, manifest_file_path: str): + monkeypatch.setattr( + sys, + "argv", + ["main.py", f"--config={config_file_path}", f"--manifest={manifest_file_path}"], ) + runtime.main() - runtime_script = os.path.join("..", "runtime", "runtime", "runtime.py") - - process = subprocess.Popen( - f"python3 {runtime_script} --config={config_xml} --install_dir={os.getcwd()}", - shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - cwd=os.getcwd(), - ) - stdout, stderr = process.communicate() - exit_code = process.returncode - if exit_code == 0: - print("Command executed successfully.") - print("Output:") - print(stdout.decode()) - else: - print("Error occurred while executing the command.") - print("Error message:") - print(stderr.decode()) - # Check that result file is correctly generated - result_file = os.path.join("Result.xqar") - assert os.path.isfile(result_file) - # Check that at least one node called "Issue" is present in the result - node_name = "Issue" - assert check_node_exists(result_file, node_name) - os.chdir(start_wd) +# The test expects the binaries configured in the `3step_config.xml` are present +# in the `../bin` relative path as configured in the +# `test_data/framework_manifest.json` +def test_3steps_manifest(monkeypatch): + cwd = os.getcwd() + config_xml = os.path.join(cwd, "tests", "test_data", "3step_config.xml") + os_path = "linux" + if on_windows(): + os_path = "windows" -def test_3steps_config(): + manifest_json = os.path.join( + cwd, "tests", "test_data", os_path, "framework_manifest.json" + ) - start_wd = os.getcwd() - install_dir = os.path.join("..", "bin") - os.chdir(install_dir) + launch_main(monkeypatch, config_xml, manifest_json) - config_xml = os.path.join("..", "runtime", "tests", "test_data", "3steps_config.xml") + checker_bundle_output_generated = False + result_xqar_generated = False + report_txt_generated = False - runtime_script = os.path.join("..", "runtime", "runtime", "runtime.py") + for _, _, files in os.walk("./"): + for file in files: + if "DemoCheckerBundle.xqar" in file: + checker_bundle_output_generated = True + if "Result.xqar" in file: + result_xqar_generated = True + if "Report.txt" in file: + report_txt_generated = True - process = subprocess.Popen( - f"python3 {runtime_script} --config={config_xml} --install_dir={os.getcwd()}", - shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - cwd=os.getcwd(), - ) - stdout, stderr = process.communicate() - exit_code = process.returncode - if exit_code == 0: - print("Command executed successfully.") - print("Output:") - print(stdout.decode()) - else: - print("Error occurred while executing the command.") - print("Error message:") - print(stderr.decode()) + # Check that checker bundle output file is correctly generated + assert checker_bundle_output_generated == True # Check that result file is correctly generated - result_file = os.path.join("Result.xqar") - assert os.path.isfile(result_file) + assert result_xqar_generated == True # Check that report txt file is correctly generated - result_file = os.path.join("Report.txt") - assert os.path.isfile(result_file) - - os.chdir(start_wd) + assert report_txt_generated == True