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