From bf7b293e4b0c7cc6d4d0117e195e7c86eca015d9 Mon Sep 17 00:00:00 2001 From: coder Date: Sat, 3 Jan 2026 14:52:53 -0800 Subject: [PATCH 1/7] update tests --- Makefile | 2 +- pyproject.toml | 2 + tests/test_fmi2.py | 230 ++++++++++++++++++++++++++++++--------------- uv.lock | 159 +++++++++++++++++++++++++++++++ 4 files changed, 318 insertions(+), 75 deletions(-) diff --git a/Makefile b/Makefile index 1b14888..e42d13e 100644 --- a/Makefile +++ b/Makefile @@ -56,7 +56,7 @@ publish-dryrun: build ## Dry run of publishing the package to PyPI # TODO: Check if it will run the pytest from the correct venv test: ## Run tests using pytest @echo "🚀 Running tests with pytest" - @uv run --frozen pytest --doctest-modules + @uv run --frozen pytest --doctest-modules --cov=mdreader --cov-report=term --cov-report=html .PHONY: help help: diff --git a/pyproject.toml b/pyproject.toml index 79099c5..fac8177 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,8 +22,10 @@ build-backend = "uv_build" [dependency-groups] dev = [ "bump-my-version>=1.2.6", + "httpx>=0.28.1", "pyright>=1.1.407", "pytest>=9.0.2", + "pytest-cov>=7.0.0", ] [tool.pyright] diff --git a/tests/test_fmi2.py b/tests/test_fmi2.py index d1b0d02..9b82827 100644 --- a/tests/test_fmi2.py +++ b/tests/test_fmi2.py @@ -1,88 +1,170 @@ -import pytest -from pathlib import Path -from mdreader.fmi2 import read_model_description - -# Get the directory where this test file is located -TEST_DIR = Path(__file__).parent -SAMPLES_DIR = TEST_DIR / "samples" / "fmi2" -XML_FILES = list(SAMPLES_DIR.glob("*.xml")) +import pathlib +import zipfile +import httpx +import pytest -@pytest.mark.parametrize("xml_file", XML_FILES, ids=[f.name for f in XML_FILES]) -def test_parse_xml_file(xml_file): - """Test parsing a single XML file""" - model = read_model_description(xml_file) +from mdreader.fmi2 import read_model_description - # Basic assertions - assert model.model_name - assert model.fmi_version - assert model.model_variables +REFERENCE_FMUS_URL = "https://github.com/modelica/Reference-FMUs/releases/download/v0.0.38/Reference-FMUs-0.0.38.zip" - # Validate the model using Pydantic's validation - # This ensures the model can be dumped and re-validated - model.model_validate(model.model_dump()) +@pytest.fixture(scope="session") +def reference_fmus_dir(tmp_path_factory): + """Download and extract Reference-FMUs once per test session.""" + tmpdir = tmp_path_factory.mktemp("reference_fmus") -@pytest.mark.parametrize("xml_file", XML_FILES, ids=[f.name for f in XML_FILES]) -def test_model_serialization(xml_file): - """Test serializing the model back to dict""" - model = read_model_description(xml_file) + # Download the reference FMU zip file + response = httpx.get(REFERENCE_FMUS_URL, follow_redirects=True) + response.raise_for_status() - # Convert back to dict - model_dict = model.model_dump() + zip_path = tmpdir / "Reference-FMUs.zip" + with open(zip_path, "wb") as f: + f.write(response.content) - # Check consistency - assert model_dict["fmi_version"] == model.fmi_version - assert model_dict["model_name"] == model.model_name + # Extract the zip file + with zipfile.ZipFile(zip_path, "r") as zip_ref: + zip_ref.extractall(tmpdir) + return tmpdir -@pytest.mark.parametrize("xml_file", XML_FILES, ids=[f.name for f in XML_FILES]) -def test_model_details(xml_file): - """Test detailed properties of the model""" - model = read_model_description(xml_file) - # Check required attributes - assert model.guid is not None - assert len(model.guid) > 0 - assert model.generation_tool is not None +@pytest.mark.parametrize( + "reference_fmu, expected_fmi_version", + [ + ("2.0/Feedthrough.fmu", "2.0"), + ("2.0/BouncingBall.fmu", "2.0"), + ("2.0/VanDerPol.fmu", "2.0"), + ("2.0/Dahlquist.fmu", "2.0"), + ("2.0/Stair.fmu", "2.0"), + ("2.0/Resource.fmu", "2.0"), + ], +) +def test_fmi_version(reference_fmu, expected_fmi_version, reference_fmus_dir): + filename = (reference_fmus_dir / reference_fmu).absolute() + md = read_model_description(filename) + assert md.fmi_version == expected_fmi_version - # Check Model Variables - assert model.model_variables is not None - assert len(model.model_variables.variables) > 0 - # Check that every variable has a name and value reference - for var in model.model_variables.variables: - assert var.name is not None - assert var.value_reference is not None - # Check that one of the types is set (Real, Integer, Boolean, String, Enumeration) - assert any( +@pytest.mark.parametrize( + "reference_fmu, expected_inputs, expected_outputs", + [ + ( + "2.0/Feedthrough.fmu", + [ + "Float64_continuous_input", + "Float64_discrete_input", + "Int32_input", + "Boolean_input", + "Enumeration_input", + ], [ - var.real is not None, - var.integer is not None, - var.boolean is not None, - var.string is not None, - var.enumeration is not None, - ] - ) - - # Check Model Structure if present - # if model.model_structure: - # if model.model_structure.outputs: - # for output in model.model_structure.outputs: - # assert output.index > 0 - - # if model.model_structure.derivatives: - - # Specific checks for BouncingBall (test1.xml) - if model.model_name == "BouncingBall": - # Check specific variables exist - var_names = [v.name for v in model.model_variables.variables] - assert "time" in var_names - assert "h" in var_names - assert "v" in var_names - - # Check unit definitions - assert model.unit_definitions is not None - unit_names = [u.name for u in model.unit_definitions.units] - assert "m" in unit_names - assert "m/s" in unit_names + "Float64_continuous_output", + "Float64_discrete_output", + "Int32_output", + "Boolean_output", + "Enumeration_output", + ], + ), + ("2.0/BouncingBall.fmu", [], ["h", "v"]), + ("2.0/VanDerPol.fmu", [], ["x0", "x1"]), + ("2.0/Dahlquist.fmu", [], ["x"]), + ("2.0/Stair.fmu", [], ["counter"]), + ("2.0/Resource.fmu", [], ["y"]), + ], +) +def test_scarlar_variables( + reference_fmu, expected_inputs, expected_outputs, reference_fmus_dir +): + filename = (reference_fmus_dir / reference_fmu).absolute() + md = read_model_description(filename) + input_vars = [ + var.name for var in md.model_variables.variables if var.causality == "input" + ] + output_vars = [ + var.name for var in md.model_variables.variables if var.causality == "output" + ] + print("Input Vars:", input_vars) + print("Output Vars:", output_vars) + assert sorted(input_vars) == sorted(expected_inputs) + assert sorted(output_vars) == sorted(expected_outputs) + + +# @pytest.mark.parametrize("xml_file", XML_FILES, ids=[f.name for f in XML_FILES]) +# def test_parse_xml_file(xml_file): +# """Test parsing a single XML file""" +# model = read_model_description(xml_file) + +# # Basic assertions +# assert model.model_name +# assert model.fmi_version +# assert model.model_variables + +# # Validate the model using Pydantic's validation +# # This ensures the model can be dumped and re-validated +# model.model_validate(model.model_dump()) + + +# @pytest.mark.parametrize("xml_file", XML_FILES, ids=[f.name for f in XML_FILES]) +# def test_model_serialization(xml_file): +# """Test serializing the model back to dict""" +# model = read_model_description(xml_file) + +# # Convert back to dict +# model_dict = model.model_dump() + +# # Check consistency +# assert model_dict["fmi_version"] == model.fmi_version +# assert model_dict["model_name"] == model.model_name + + +# @pytest.mark.parametrize("xml_file", XML_FILES, ids=[f.name for f in XML_FILES]) +# def test_model_details(xml_file): +# """Test detailed properties of the model""" +# model = read_model_description(xml_file) + +# # Check required attributes +# assert model.guid is not None +# assert len(model.guid) > 0 +# assert model.generation_tool is not None + +# # Check Model Variables +# assert model.model_variables is not None +# assert len(model.model_variables.variables) > 0 + +# # Check that every variable has a name and value reference +# for var in model.model_variables.variables: +# assert var.name is not None +# assert var.value_reference is not None +# # Check that one of the types is set (Real, Integer, Boolean, String, Enumeration) +# assert any( +# [ +# var.real is not None, +# var.integer is not None, +# var.boolean is not None, +# var.string is not None, +# var.enumeration is not None, +# ] +# ) + +# # Check Model Structure if present +# # if model.model_structure: +# # if model.model_structure.outputs: +# # for output in model.model_structure.outputs: +# # assert output.index > 0 + +# # if model.model_structure.derivatives: + +# # Specific checks for BouncingBall (test1.xml) +# if model.model_name == "BouncingBall": +# # Check specific variables exist +# var_names = [v.name for v in model.model_variables.variables] +# assert "time" in var_names +# assert "h" in var_names +# assert "v" in var_names + +# # Check unit definitions +# assert model.unit_definitions is not None +# unit_names = [u.name for u in model.unit_definitions.units] +# assert "m" in unit_names +# assert "m/s" in unit_names diff --git a/uv.lock b/uv.lock index e01f7e0..53e2edc 100644 --- a/uv.lock +++ b/uv.lock @@ -83,6 +83,98 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "coverage" +version = "7.13.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/f9/e92df5e07f3fc8d4c7f9a0f146ef75446bf870351cd37b788cf5897f8079/coverage-7.13.1.tar.gz", hash = "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd", size = 825862, upload-time = "2025-12-28T15:42:56.969Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/9b/77baf488516e9ced25fc215a6f75d803493fc3f6a1a1227ac35697910c2a/coverage-7.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a55d509a1dc5a5b708b5dad3b5334e07a16ad4c2185e27b40e4dba796ab7f88", size = 218755, upload-time = "2025-12-28T15:40:30.812Z" }, + { url = "https://files.pythonhosted.org/packages/d7/cd/7ab01154e6eb79ee2fab76bf4d89e94c6648116557307ee4ebbb85e5c1bf/coverage-7.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d010d080c4888371033baab27e47c9df7d6fb28d0b7b7adf85a4a49be9298b3", size = 219257, upload-time = "2025-12-28T15:40:32.333Z" }, + { url = "https://files.pythonhosted.org/packages/01/d5/b11ef7863ffbbdb509da0023fad1e9eda1c0eaea61a6d2ea5b17d4ac706e/coverage-7.13.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d938b4a840fb1523b9dfbbb454f652967f18e197569c32266d4d13f37244c3d9", size = 249657, upload-time = "2025-12-28T15:40:34.1Z" }, + { url = "https://files.pythonhosted.org/packages/f7/7c/347280982982383621d29b8c544cf497ae07ac41e44b1ca4903024131f55/coverage-7.13.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf100a3288f9bb7f919b87eb84f87101e197535b9bd0e2c2b5b3179633324fee", size = 251581, upload-time = "2025-12-28T15:40:36.131Z" }, + { url = "https://files.pythonhosted.org/packages/82/f6/ebcfed11036ade4c0d75fa4453a6282bdd225bc073862766eec184a4c643/coverage-7.13.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef6688db9bf91ba111ae734ba6ef1a063304a881749726e0d3575f5c10a9facf", size = 253691, upload-time = "2025-12-28T15:40:37.626Z" }, + { url = "https://files.pythonhosted.org/packages/02/92/af8f5582787f5d1a8b130b2dcba785fa5e9a7a8e121a0bb2220a6fdbdb8a/coverage-7.13.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b609fc9cdbd1f02e51f67f51e5aee60a841ef58a68d00d5ee2c0faf357481a3", size = 249799, upload-time = "2025-12-28T15:40:39.47Z" }, + { url = "https://files.pythonhosted.org/packages/24/aa/0e39a2a3b16eebf7f193863323edbff38b6daba711abaaf807d4290cf61a/coverage-7.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c43257717611ff5e9a1d79dce8e47566235ebda63328718d9b65dd640bc832ef", size = 251389, upload-time = "2025-12-28T15:40:40.954Z" }, + { url = "https://files.pythonhosted.org/packages/73/46/7f0c13111154dc5b978900c0ccee2e2ca239b910890e674a77f1363d483e/coverage-7.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e09fbecc007f7b6afdfb3b07ce5bd9f8494b6856dd4f577d26c66c391b829851", size = 249450, upload-time = "2025-12-28T15:40:42.489Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ca/e80da6769e8b669ec3695598c58eef7ad98b0e26e66333996aee6316db23/coverage-7.13.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a03a4f3a19a189919c7055098790285cc5c5b0b3976f8d227aea39dbf9f8bfdb", size = 249170, upload-time = "2025-12-28T15:40:44.279Z" }, + { url = "https://files.pythonhosted.org/packages/af/18/9e29baabdec1a8644157f572541079b4658199cfd372a578f84228e860de/coverage-7.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3820778ea1387c2b6a818caec01c63adc5b3750211af6447e8dcfb9b6f08dbba", size = 250081, upload-time = "2025-12-28T15:40:45.748Z" }, + { url = "https://files.pythonhosted.org/packages/00/f8/c3021625a71c3b2f516464d322e41636aea381018319050a8114105872ee/coverage-7.13.1-cp311-cp311-win32.whl", hash = "sha256:ff10896fa55167371960c5908150b434b71c876dfab97b69478f22c8b445ea19", size = 221281, upload-time = "2025-12-28T15:40:47.232Z" }, + { url = "https://files.pythonhosted.org/packages/27/56/c216625f453df6e0559ed666d246fcbaaa93f3aa99eaa5080cea1229aa3d/coverage-7.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:a998cc0aeeea4c6d5622a3754da5a493055d2d95186bad877b0a34ea6e6dbe0a", size = 222215, upload-time = "2025-12-28T15:40:49.19Z" }, + { url = "https://files.pythonhosted.org/packages/5c/9a/be342e76f6e531cae6406dc46af0d350586f24d9b67fdfa6daee02df71af/coverage-7.13.1-cp311-cp311-win_arm64.whl", hash = "sha256:fea07c1a39a22614acb762e3fbbb4011f65eedafcb2948feeef641ac78b4ee5c", size = 220886, upload-time = "2025-12-28T15:40:51.067Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8a/87af46cccdfa78f53db747b09f5f9a21d5fc38d796834adac09b30a8ce74/coverage-7.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6f34591000f06e62085b1865c9bc5f7858df748834662a51edadfd2c3bfe0dd3", size = 218927, upload-time = "2025-12-28T15:40:52.814Z" }, + { url = "https://files.pythonhosted.org/packages/82/a8/6e22fdc67242a4a5a153f9438d05944553121c8f4ba70cb072af4c41362e/coverage-7.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b67e47c5595b9224599016e333f5ec25392597a89d5744658f837d204e16c63e", size = 219288, upload-time = "2025-12-28T15:40:54.262Z" }, + { url = "https://files.pythonhosted.org/packages/d0/0a/853a76e03b0f7c4375e2ca025df45c918beb367f3e20a0a8e91967f6e96c/coverage-7.13.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e7b8bd70c48ffb28461ebe092c2345536fb18bbbf19d287c8913699735f505c", size = 250786, upload-time = "2025-12-28T15:40:56.059Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b4/694159c15c52b9f7ec7adf49d50e5f8ee71d3e9ef38adb4445d13dd56c20/coverage-7.13.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c223d078112e90dc0e5c4e35b98b9584164bea9fbbd221c0b21c5241f6d51b62", size = 253543, upload-time = "2025-12-28T15:40:57.585Z" }, + { url = "https://files.pythonhosted.org/packages/96/b2/7f1f0437a5c855f87e17cf5d0dc35920b6440ff2b58b1ba9788c059c26c8/coverage-7.13.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:794f7c05af0763b1bbd1b9e6eff0e52ad068be3b12cd96c87de037b01390c968", size = 254635, upload-time = "2025-12-28T15:40:59.443Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d1/73c3fdb8d7d3bddd9473c9c6a2e0682f09fc3dfbcb9c3f36412a7368bcab/coverage-7.13.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0642eae483cc8c2902e4af7298bf886d605e80f26382124cddc3967c2a3df09e", size = 251202, upload-time = "2025-12-28T15:41:01.328Z" }, + { url = "https://files.pythonhosted.org/packages/66/3c/f0edf75dcc152f145d5598329e864bbbe04ab78660fe3e8e395f9fff010f/coverage-7.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5e772ed5fef25b3de9f2008fe67b92d46831bd2bc5bdc5dd6bfd06b83b316f", size = 252566, upload-time = "2025-12-28T15:41:03.319Z" }, + { url = "https://files.pythonhosted.org/packages/17/b3/e64206d3c5f7dcbceafd14941345a754d3dbc78a823a6ed526e23b9cdaab/coverage-7.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:45980ea19277dc0a579e432aef6a504fe098ef3a9032ead15e446eb0f1191aee", size = 250711, upload-time = "2025-12-28T15:41:06.411Z" }, + { url = "https://files.pythonhosted.org/packages/dc/ad/28a3eb970a8ef5b479ee7f0c484a19c34e277479a5b70269dc652b730733/coverage-7.13.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f18eca6028ffa62adbd185a8f1e1dd242f2e68164dba5c2b74a5204850b4cf", size = 250278, upload-time = "2025-12-28T15:41:08.285Z" }, + { url = "https://files.pythonhosted.org/packages/54/e3/c8f0f1a93133e3e1291ca76cbb63565bd4b5c5df63b141f539d747fff348/coverage-7.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8dca5590fec7a89ed6826fce625595279e586ead52e9e958d3237821fbc750c", size = 252154, upload-time = "2025-12-28T15:41:09.969Z" }, + { url = "https://files.pythonhosted.org/packages/d0/bf/9939c5d6859c380e405b19e736321f1c7d402728792f4c752ad1adcce005/coverage-7.13.1-cp312-cp312-win32.whl", hash = "sha256:ff86d4e85188bba72cfb876df3e11fa243439882c55957184af44a35bd5880b7", size = 221487, upload-time = "2025-12-28T15:41:11.468Z" }, + { url = "https://files.pythonhosted.org/packages/fa/dc/7282856a407c621c2aad74021680a01b23010bb8ebf427cf5eacda2e876f/coverage-7.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:16cc1da46c04fb0fb128b4dc430b78fa2aba8a6c0c9f8eb391fd5103409a6ac6", size = 222299, upload-time = "2025-12-28T15:41:13.386Z" }, + { url = "https://files.pythonhosted.org/packages/10/79/176a11203412c350b3e9578620013af35bcdb79b651eb976f4a4b32044fa/coverage-7.13.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d9bc218650022a768f3775dd7fdac1886437325d8d295d923ebcfef4892ad5c", size = 220941, upload-time = "2025-12-28T15:41:14.975Z" }, + { url = "https://files.pythonhosted.org/packages/a3/a4/e98e689347a1ff1a7f67932ab535cef82eb5e78f32a9e4132e114bbb3a0a/coverage-7.13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cb237bfd0ef4d5eb6a19e29f9e528ac67ac3be932ea6b44fb6cc09b9f3ecff78", size = 218951, upload-time = "2025-12-28T15:41:16.653Z" }, + { url = "https://files.pythonhosted.org/packages/32/33/7cbfe2bdc6e2f03d6b240d23dc45fdaf3fd270aaf2d640be77b7f16989ab/coverage-7.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1dcb645d7e34dcbcc96cd7c132b1fc55c39263ca62eb961c064eb3928997363b", size = 219325, upload-time = "2025-12-28T15:41:18.609Z" }, + { url = "https://files.pythonhosted.org/packages/59/f6/efdabdb4929487baeb7cb2a9f7dac457d9356f6ad1b255be283d58b16316/coverage-7.13.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3d42df8201e00384736f0df9be2ced39324c3907607d17d50d50116c989d84cd", size = 250309, upload-time = "2025-12-28T15:41:20.629Z" }, + { url = "https://files.pythonhosted.org/packages/12/da/91a52516e9d5aea87d32d1523f9cdcf7a35a3b298e6be05d6509ba3cfab2/coverage-7.13.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa3edde1aa8807de1d05934982416cb3ec46d1d4d91e280bcce7cca01c507992", size = 252907, upload-time = "2025-12-28T15:41:22.257Z" }, + { url = "https://files.pythonhosted.org/packages/75/38/f1ea837e3dc1231e086db1638947e00d264e7e8c41aa8ecacf6e1e0c05f4/coverage-7.13.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9edd0e01a343766add6817bc448408858ba6b489039eaaa2018474e4001651a4", size = 254148, upload-time = "2025-12-28T15:41:23.87Z" }, + { url = "https://files.pythonhosted.org/packages/7f/43/f4f16b881aaa34954ba446318dea6b9ed5405dd725dd8daac2358eda869a/coverage-7.13.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:985b7836931d033570b94c94713c6dba5f9d3ff26045f72c3e5dbc5fe3361e5a", size = 250515, upload-time = "2025-12-28T15:41:25.437Z" }, + { url = "https://files.pythonhosted.org/packages/84/34/8cba7f00078bd468ea914134e0144263194ce849ec3baad187ffb6203d1c/coverage-7.13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ffed1e4980889765c84a5d1a566159e363b71d6b6fbaf0bebc9d3c30bc016766", size = 252292, upload-time = "2025-12-28T15:41:28.459Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a4/cffac66c7652d84ee4ac52d3ccb94c015687d3b513f9db04bfcac2ac800d/coverage-7.13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8842af7f175078456b8b17f1b73a0d16a65dcbdc653ecefeb00a56b3c8c298c4", size = 250242, upload-time = "2025-12-28T15:41:30.02Z" }, + { url = "https://files.pythonhosted.org/packages/f4/78/9a64d462263dde416f3c0067efade7b52b52796f489b1037a95b0dc389c9/coverage-7.13.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:ccd7a6fca48ca9c131d9b0a2972a581e28b13416fc313fb98b6d24a03ce9a398", size = 250068, upload-time = "2025-12-28T15:41:32.007Z" }, + { url = "https://files.pythonhosted.org/packages/69/c8/a8994f5fece06db7c4a97c8fc1973684e178599b42e66280dded0524ef00/coverage-7.13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0403f647055de2609be776965108447deb8e384fe4a553c119e3ff6bfbab4784", size = 251846, upload-time = "2025-12-28T15:41:33.946Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f7/91fa73c4b80305c86598a2d4e54ba22df6bf7d0d97500944af7ef155d9f7/coverage-7.13.1-cp313-cp313-win32.whl", hash = "sha256:549d195116a1ba1e1ae2f5ca143f9777800f6636eab917d4f02b5310d6d73461", size = 221512, upload-time = "2025-12-28T15:41:35.519Z" }, + { url = "https://files.pythonhosted.org/packages/45/0b/0768b4231d5a044da8f75e097a8714ae1041246bb765d6b5563bab456735/coverage-7.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:5899d28b5276f536fcf840b18b61a9fce23cc3aec1d114c44c07fe94ebeaa500", size = 222321, upload-time = "2025-12-28T15:41:37.371Z" }, + { url = "https://files.pythonhosted.org/packages/9b/b8/bdcb7253b7e85157282450262008f1366aa04663f3e3e4c30436f596c3e2/coverage-7.13.1-cp313-cp313-win_arm64.whl", hash = "sha256:868a2fae76dfb06e87291bcbd4dcbcc778a8500510b618d50496e520bd94d9b9", size = 220949, upload-time = "2025-12-28T15:41:39.553Z" }, + { url = "https://files.pythonhosted.org/packages/70/52/f2be52cc445ff75ea8397948c96c1b4ee14f7f9086ea62fc929c5ae7b717/coverage-7.13.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:67170979de0dacac3f3097d02b0ad188d8edcea44ccc44aaa0550af49150c7dc", size = 219643, upload-time = "2025-12-28T15:41:41.567Z" }, + { url = "https://files.pythonhosted.org/packages/47/79/c85e378eaa239e2edec0c5523f71542c7793fe3340954eafb0bc3904d32d/coverage-7.13.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f80e2bb21bfab56ed7405c2d79d34b5dc0bc96c2c1d2a067b643a09fb756c43a", size = 219997, upload-time = "2025-12-28T15:41:43.418Z" }, + { url = "https://files.pythonhosted.org/packages/fe/9b/b1ade8bfb653c0bbce2d6d6e90cc6c254cbb99b7248531cc76253cb4da6d/coverage-7.13.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f83351e0f7dcdb14d7326c3d8d8c4e915fa685cbfdc6281f9470d97a04e9dfe4", size = 261296, upload-time = "2025-12-28T15:41:45.207Z" }, + { url = "https://files.pythonhosted.org/packages/1f/af/ebf91e3e1a2473d523e87e87fd8581e0aa08741b96265730e2d79ce78d8d/coverage-7.13.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb3f6562e89bad0110afbe64e485aac2462efdce6232cdec7862a095dc3412f6", size = 263363, upload-time = "2025-12-28T15:41:47.163Z" }, + { url = "https://files.pythonhosted.org/packages/c4/8b/fb2423526d446596624ac7fde12ea4262e66f86f5120114c3cfd0bb2befa/coverage-7.13.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77545b5dcda13b70f872c3b5974ac64c21d05e65b1590b441c8560115dc3a0d1", size = 265783, upload-time = "2025-12-28T15:41:49.03Z" }, + { url = "https://files.pythonhosted.org/packages/9b/26/ef2adb1e22674913b89f0fe7490ecadcef4a71fa96f5ced90c60ec358789/coverage-7.13.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4d240d260a1aed814790bbe1f10a5ff31ce6c21bc78f0da4a1e8268d6c80dbd", size = 260508, upload-time = "2025-12-28T15:41:51.035Z" }, + { url = "https://files.pythonhosted.org/packages/ce/7d/f0f59b3404caf662e7b5346247883887687c074ce67ba453ea08c612b1d5/coverage-7.13.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d2287ac9360dec3837bfdad969963a5d073a09a85d898bd86bea82aa8876ef3c", size = 263357, upload-time = "2025-12-28T15:41:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b1/29896492b0b1a047604d35d6fa804f12818fa30cdad660763a5f3159e158/coverage-7.13.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0d2c11f3ea4db66b5cbded23b20185c35066892c67d80ec4be4bab257b9ad1e0", size = 260978, upload-time = "2025-12-28T15:41:54.589Z" }, + { url = "https://files.pythonhosted.org/packages/48/f2/971de1238a62e6f0a4128d37adadc8bb882ee96afbe03ff1570291754629/coverage-7.13.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:3fc6a169517ca0d7ca6846c3c5392ef2b9e38896f61d615cb75b9e7134d4ee1e", size = 259877, upload-time = "2025-12-28T15:41:56.263Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fc/0474efcbb590ff8628830e9aaec5f1831594874360e3251f1fdec31d07a3/coverage-7.13.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d10a2ed46386e850bb3de503a54f9fe8192e5917fcbb143bfef653a9355e9a53", size = 262069, upload-time = "2025-12-28T15:41:58.093Z" }, + { url = "https://files.pythonhosted.org/packages/88/4f/3c159b7953db37a7b44c0eab8a95c37d1aa4257c47b4602c04022d5cb975/coverage-7.13.1-cp313-cp313t-win32.whl", hash = "sha256:75a6f4aa904301dab8022397a22c0039edc1f51e90b83dbd4464b8a38dc87842", size = 222184, upload-time = "2025-12-28T15:41:59.763Z" }, + { url = "https://files.pythonhosted.org/packages/58/a5/6b57d28f81417f9335774f20679d9d13b9a8fb90cd6160957aa3b54a2379/coverage-7.13.1-cp313-cp313t-win_amd64.whl", hash = "sha256:309ef5706e95e62578cda256b97f5e097916a2c26247c287bbe74794e7150df2", size = 223250, upload-time = "2025-12-28T15:42:01.52Z" }, + { url = "https://files.pythonhosted.org/packages/81/7c/160796f3b035acfbb58be80e02e484548595aa67e16a6345e7910ace0a38/coverage-7.13.1-cp313-cp313t-win_arm64.whl", hash = "sha256:92f980729e79b5d16d221038dbf2e8f9a9136afa072f9d5d6ed4cb984b126a09", size = 221521, upload-time = "2025-12-28T15:42:03.275Z" }, + { url = "https://files.pythonhosted.org/packages/aa/8e/ba0e597560c6563fc0adb902fda6526df5d4aa73bb10adf0574d03bd2206/coverage-7.13.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:97ab3647280d458a1f9adb85244e81587505a43c0c7cff851f5116cd2814b894", size = 218996, upload-time = "2025-12-28T15:42:04.978Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8e/764c6e116f4221dc7aa26c4061181ff92edb9c799adae6433d18eeba7a14/coverage-7.13.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8f572d989142e0908e6acf57ad1b9b86989ff057c006d13b76c146ec6a20216a", size = 219326, upload-time = "2025-12-28T15:42:06.691Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a6/6130dc6d8da28cdcbb0f2bf8865aeca9b157622f7c0031e48c6cf9a0e591/coverage-7.13.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d72140ccf8a147e94274024ff6fd8fb7811354cf7ef88b1f0a988ebaa5bc774f", size = 250374, upload-time = "2025-12-28T15:42:08.786Z" }, + { url = "https://files.pythonhosted.org/packages/82/2b/783ded568f7cd6b677762f780ad338bf4b4750205860c17c25f7c708995e/coverage-7.13.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3c9f051b028810f5a87c88e5d6e9af3c0ff32ef62763bf15d29f740453ca909", size = 252882, upload-time = "2025-12-28T15:42:10.515Z" }, + { url = "https://files.pythonhosted.org/packages/cd/b2/9808766d082e6a4d59eb0cc881a57fc1600eb2c5882813eefff8254f71b5/coverage-7.13.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f398ba4df52d30b1763f62eed9de5620dcde96e6f491f4c62686736b155aa6e4", size = 254218, upload-time = "2025-12-28T15:42:12.208Z" }, + { url = "https://files.pythonhosted.org/packages/44/ea/52a985bb447c871cb4d2e376e401116520991b597c85afdde1ea9ef54f2c/coverage-7.13.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:132718176cc723026d201e347f800cd1a9e4b62ccd3f82476950834dad501c75", size = 250391, upload-time = "2025-12-28T15:42:14.21Z" }, + { url = "https://files.pythonhosted.org/packages/7f/1d/125b36cc12310718873cfc8209ecfbc1008f14f4f5fa0662aa608e579353/coverage-7.13.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e549d642426e3579b3f4b92d0431543b012dcb6e825c91619d4e93b7363c3f9", size = 252239, upload-time = "2025-12-28T15:42:16.292Z" }, + { url = "https://files.pythonhosted.org/packages/6a/16/10c1c164950cade470107f9f14bbac8485f8fb8515f515fca53d337e4a7f/coverage-7.13.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:90480b2134999301eea795b3a9dbf606c6fbab1b489150c501da84a959442465", size = 250196, upload-time = "2025-12-28T15:42:18.54Z" }, + { url = "https://files.pythonhosted.org/packages/2a/c6/cd860fac08780c6fd659732f6ced1b40b79c35977c1356344e44d72ba6c4/coverage-7.13.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e825dbb7f84dfa24663dd75835e7257f8882629fc11f03ecf77d84a75134b864", size = 250008, upload-time = "2025-12-28T15:42:20.365Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/a8c58d3d38f82a5711e1e0a67268362af48e1a03df27c03072ac30feefcf/coverage-7.13.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:623dcc6d7a7ba450bbdbeedbaa0c42b329bdae16491af2282f12a7e809be7eb9", size = 251671, upload-time = "2025-12-28T15:42:22.114Z" }, + { url = "https://files.pythonhosted.org/packages/f0/bc/fd4c1da651d037a1e3d53e8cb3f8182f4b53271ffa9a95a2e211bacc0349/coverage-7.13.1-cp314-cp314-win32.whl", hash = "sha256:6e73ebb44dca5f708dc871fe0b90cf4cff1a13f9956f747cc87b535a840386f5", size = 221777, upload-time = "2025-12-28T15:42:23.919Z" }, + { url = "https://files.pythonhosted.org/packages/4b/50/71acabdc8948464c17e90b5ffd92358579bd0910732c2a1c9537d7536aa6/coverage-7.13.1-cp314-cp314-win_amd64.whl", hash = "sha256:be753b225d159feb397bd0bf91ae86f689bad0da09d3b301478cd39b878ab31a", size = 222592, upload-time = "2025-12-28T15:42:25.619Z" }, + { url = "https://files.pythonhosted.org/packages/f7/c8/a6fb943081bb0cc926499c7907731a6dc9efc2cbdc76d738c0ab752f1a32/coverage-7.13.1-cp314-cp314-win_arm64.whl", hash = "sha256:228b90f613b25ba0019361e4ab81520b343b622fc657daf7e501c4ed6a2366c0", size = 221169, upload-time = "2025-12-28T15:42:27.629Z" }, + { url = "https://files.pythonhosted.org/packages/16/61/d5b7a0a0e0e40d62e59bc8c7aa1afbd86280d82728ba97f0673b746b78e2/coverage-7.13.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:60cfb538fe9ef86e5b2ab0ca8fc8d62524777f6c611dcaf76dc16fbe9b8e698a", size = 219730, upload-time = "2025-12-28T15:42:29.306Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2c/8881326445fd071bb49514d1ce97d18a46a980712b51fee84f9ab42845b4/coverage-7.13.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:57dfc8048c72ba48a8c45e188d811e5efd7e49b387effc8fb17e97936dde5bf6", size = 220001, upload-time = "2025-12-28T15:42:31.319Z" }, + { url = "https://files.pythonhosted.org/packages/b5/d7/50de63af51dfa3a7f91cc37ad8fcc1e244b734232fbc8b9ab0f3c834a5cd/coverage-7.13.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3f2f725aa3e909b3c5fdb8192490bdd8e1495e85906af74fe6e34a2a77ba0673", size = 261370, upload-time = "2025-12-28T15:42:32.992Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2c/d31722f0ec918fd7453b2758312729f645978d212b410cd0f7c2aed88a94/coverage-7.13.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ee68b21909686eeb21dfcba2c3b81fee70dcf38b140dcd5aa70680995fa3aa5", size = 263485, upload-time = "2025-12-28T15:42:34.759Z" }, + { url = "https://files.pythonhosted.org/packages/fa/7a/2c114fa5c5fc08ba0777e4aec4c97e0b4a1afcb69c75f1f54cff78b073ab/coverage-7.13.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724b1b270cb13ea2e6503476e34541a0b1f62280bc997eab443f87790202033d", size = 265890, upload-time = "2025-12-28T15:42:36.517Z" }, + { url = "https://files.pythonhosted.org/packages/65/d9/f0794aa1c74ceabc780fe17f6c338456bbc4e96bd950f2e969f48ac6fb20/coverage-7.13.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:916abf1ac5cf7eb16bc540a5bf75c71c43a676f5c52fcb9fe75a2bd75fb944e8", size = 260445, upload-time = "2025-12-28T15:42:38.646Z" }, + { url = "https://files.pythonhosted.org/packages/49/23/184b22a00d9bb97488863ced9454068c79e413cb23f472da6cbddc6cfc52/coverage-7.13.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:776483fd35b58d8afe3acbd9988d5de592ab6da2d2a865edfdbc9fdb43e7c486", size = 263357, upload-time = "2025-12-28T15:42:40.788Z" }, + { url = "https://files.pythonhosted.org/packages/7d/bd/58af54c0c9199ea4190284f389005779d7daf7bf3ce40dcd2d2b2f96da69/coverage-7.13.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b6f3b96617e9852703f5b633ea01315ca45c77e879584f283c44127f0f1ec564", size = 260959, upload-time = "2025-12-28T15:42:42.808Z" }, + { url = "https://files.pythonhosted.org/packages/4b/2a/6839294e8f78a4891bf1df79d69c536880ba2f970d0ff09e7513d6e352e9/coverage-7.13.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd63e7b74661fed317212fab774e2a648bc4bb09b35f25474f8e3325d2945cd7", size = 259792, upload-time = "2025-12-28T15:42:44.818Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c3/528674d4623283310ad676c5af7414b9850ab6d55c2300e8aa4b945ec554/coverage-7.13.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:933082f161bbb3e9f90d00990dc956120f608cdbcaeea15c4d897f56ef4fe416", size = 262123, upload-time = "2025-12-28T15:42:47.108Z" }, + { url = "https://files.pythonhosted.org/packages/06/c5/8c0515692fb4c73ac379d8dc09b18eaf0214ecb76ea6e62467ba7a1556ff/coverage-7.13.1-cp314-cp314t-win32.whl", hash = "sha256:18be793c4c87de2965e1c0f060f03d9e5aff66cfeae8e1dbe6e5b88056ec153f", size = 222562, upload-time = "2025-12-28T15:42:49.144Z" }, + { url = "https://files.pythonhosted.org/packages/05/0e/c0a0c4678cb30dac735811db529b321d7e1c9120b79bd728d4f4d6b010e9/coverage-7.13.1-cp314-cp314t-win_amd64.whl", hash = "sha256:0e42e0ec0cd3e0d851cb3c91f770c9301f48647cb2877cb78f74bdaa07639a79", size = 223670, upload-time = "2025-12-28T15:42:51.218Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5f/b177aa0011f354abf03a8f30a85032686d290fdeed4222b27d36b4372a50/coverage-7.13.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eaecf47ef10c72ece9a2a92118257da87e460e113b83cc0d2905cbbe931792b4", size = 221707, upload-time = "2025-12-28T15:42:53.034Z" }, + { url = "https://files.pythonhosted.org/packages/cc/48/d9f421cb8da5afaa1a64570d9989e00fb7955e6acddc5a12979f7666ef60/coverage-7.13.1-py3-none-any.whl", hash = "sha256:2016745cb3ba554469d02819d78958b571792bb68e31302610e898f80dd3a573", size = 210722, upload-time = "2025-12-28T15:42:54.901Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -161,8 +253,10 @@ dependencies = [ [package.dev-dependencies] dev = [ { name = "bump-my-version" }, + { name = "httpx" }, { name = "pyright" }, { name = "pytest" }, + { name = "pytest-cov" }, ] [package.metadata] @@ -171,8 +265,10 @@ requires-dist = [{ name = "pydantic", specifier = ">=2.11.5,<3.0.0" }] [package.metadata.requires-dev] dev = [ { name = "bump-my-version", specifier = ">=1.2.6" }, + { name = "httpx", specifier = ">=0.28.1" }, { name = "pyright", specifier = ">=1.1.407" }, { name = "pytest", specifier = ">=9.0.2" }, + { name = "pytest-cov", specifier = ">=7.0.0" }, ] [[package]] @@ -387,6 +483,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] +[[package]] +name = "pytest-cov" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, +] + [[package]] name = "python-dotenv" version = "1.2.1" @@ -435,6 +545,55 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/25/0a/d865895e1e5d88a60baee0fc3703eb111c502ee10c8c107516bc7623abf8/rich_click-1.9.5-py3-none-any.whl", hash = "sha256:9b195721a773b1acf0e16ff9ec68cef1e7d237e53471e6e3f7ade462f86c403a", size = 70580, upload-time = "2025-12-21T14:49:42.905Z" }, ] +[[package]] +name = "tomli" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, + { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, + { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, + { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, + { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, + { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, + { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, + { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, + { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, + { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, + { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, + { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, + { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, + { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, + { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, + { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, + { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, + { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, + { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, + { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, + { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, + { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, + { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, + { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, + { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, + { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, + { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, + { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, + { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, + { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, + { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, + { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, + { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, + { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, +] + [[package]] name = "tomlkit" version = "0.13.3" From 1817c12d424b708f697db834cd0ead14df141bdf Mon Sep 17 00:00:00 2001 From: coder Date: Sat, 3 Jan 2026 14:53:13 -0800 Subject: [PATCH 2/7] update tests --- .DS_Store | Bin 0 -> 6148 bytes tests/__init__.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 .DS_Store create mode 100644 tests/__init__.py diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..780251b2979dbdf3de62aaffdde3b0f4926b7922 GIT binary patch literal 6148 zcmeHK%}T>S5T3QwrWBzEg&r5Y7Obrl#Y?F51&ruHr8cJ6V45v$+8jzDXMG``#OHBl zcLP>?@FZeqVE3DypWVy{*&hH9omp@MPz3-78=)X&jgWb*Ys&@`3O&aoD46@{bnHiK zCi;se`t}kGA%PJ@@ag-NkDfPw9EH=Q(RdeIh2r*3$tgJ%=iVEt)JyuwY})BhZfSI- zROIJ+-@gtAX|K9>uHvL0#)FAY2!jEJ+}(t6pwf<-#lckPddA_DopP^QTP&Kble%oR zo6EXfoHiSEdE9C*mt|-F;PC9S_Z*F4^a7Dix;~S?NJxS@y(I{xMb~0x z5Ircuq#~MBVV@Ynq@!KhIM-rk(4>RVE8{$N<^1s?^y+ArIvj*+kb7o;8CYeYV1`vX z|1a>DS^LOeP2mwUzzqB|21K#zbvsy;JzKw(M`x|X_JECq;xbZDP+z$O;DGj#nsyq$ bBpu^ii1h3q;VkuL(85bl_PUtr(^XdO(w literal 0 HcmV?d00001 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 From f715b6bdceea3b3cd632c83886f09dfb66f0ac11 Mon Sep 17 00:00:00 2001 From: coder Date: Sat, 3 Jan 2026 14:54:38 -0800 Subject: [PATCH 3/7] update tests --- tests/test_fmi2.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/test_fmi2.py b/tests/test_fmi2.py index 9b82827..9f2a7de 100644 --- a/tests/test_fmi2.py +++ b/tests/test_fmi2.py @@ -30,17 +30,17 @@ def reference_fmus_dir(tmp_path_factory): @pytest.mark.parametrize( - "reference_fmu, expected_fmi_version", + "reference_fmu, expected_metadata", [ - ("2.0/Feedthrough.fmu", "2.0"), - ("2.0/BouncingBall.fmu", "2.0"), - ("2.0/VanDerPol.fmu", "2.0"), - ("2.0/Dahlquist.fmu", "2.0"), - ("2.0/Stair.fmu", "2.0"), - ("2.0/Resource.fmu", "2.0"), + ("2.0/Feedthrough.fmu", ("2.0", "Feedthrough")), + ("2.0/BouncingBall.fmu", ("2.0", "BouncingBall")), + ("2.0/VanDerPol.fmu", ("2.0", "VanDerPol")), + ("2.0/Dahlquist.fmu", ("2.0", "Dahlquist")), + ("2.0/Stair.fmu", ("2.0", "Stair")), + ("2.0/Resource.fmu", ("2.0", "Resource")), ], ) -def test_fmi_version(reference_fmu, expected_fmi_version, reference_fmus_dir): +def test_metadata(reference_fmu, expected_fmi_version, reference_fmus_dir): filename = (reference_fmus_dir / reference_fmu).absolute() md = read_model_description(filename) assert md.fmi_version == expected_fmi_version From 95b8d0ddcbe63a07e6b78fe1ceb6a71ec90ec3fe Mon Sep 17 00:00:00 2001 From: coder Date: Sat, 3 Jan 2026 15:18:18 -0800 Subject: [PATCH 4/7] update tests --- .DS_Store | Bin 6148 -> 6148 bytes .gitignore | 2 + tests/test_fmi2.py | 189 ++++++++++++++++++++++++--------------------- 3 files changed, 103 insertions(+), 88 deletions(-) diff --git a/.DS_Store b/.DS_Store index 780251b2979dbdf3de62aaffdde3b0f4926b7922..a6cd84d99f2ff4628128018d8afd004411b029b6 100644 GIT binary patch delta 162 zcmZoMXfc=|#>B!ku~2NHo+2a5#DLw41(=u_Stj!^ewXEAC}K!uNCRRJoyU+2B;$en zT!vDH;>mK1jqGL?Its?d#*>#Z+H2xfT^3xFmy@5D&cMLHxS5gZJL6_{4t@@x%^(i* XWPTA#0Y;z{P#Q!qf#}TB`mu~2NHo+2aD#DLwC4MbQb^Rs-}EW#nova#VK<7Rdaeh#3T&4L`? ZnJ4p$SPC!z0V4wg6O?Az93irX836LL5Uv0K diff --git a/.gitignore b/.gitignore index 42f20c5..fc5e5ad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +reference_fmus/ + # Byte-compiled / optimized / DLL files __pycache__/ *.py[codz] diff --git a/tests/test_fmi2.py b/tests/test_fmi2.py index 9f2a7de..c43e310 100644 --- a/tests/test_fmi2.py +++ b/tests/test_fmi2.py @@ -32,18 +32,111 @@ def reference_fmus_dir(tmp_path_factory): @pytest.mark.parametrize( "reference_fmu, expected_metadata", [ - ("2.0/Feedthrough.fmu", ("2.0", "Feedthrough")), - ("2.0/BouncingBall.fmu", ("2.0", "BouncingBall")), - ("2.0/VanDerPol.fmu", ("2.0", "VanDerPol")), - ("2.0/Dahlquist.fmu", ("2.0", "Dahlquist")), - ("2.0/Stair.fmu", ("2.0", "Stair")), - ("2.0/Resource.fmu", ("2.0", "Resource")), + ( + "2.0/Feedthrough.fmu", + { + "fmi_version": "2.0", + "model_name": "Feedthrough", + "guid": "{37B954F1-CC86-4D8F-B97F-C7C36F6670D2}", + "description": "A model to test different variable types, causalities, and variabilities", + "author": None, + "version": None, + "copyright": None, + "license": None, + "generation_tool": "Reference FMUs (v0.0.38)", + "generation_date_and_time": "2025-02-04T04:14:59.281054+00:00", + }, + ), + ( + "2.0/BouncingBall.fmu", + { + "fmi_version": "2.0", + "model_name": "BouncingBall", + "guid": "{1AE5E10D-9521-4DE3-80B9-D0EAAA7D5AF1}", + "description": "This model calculates the trajectory, over time, of a ball dropped from a height of 1 m", + "author": None, + "version": None, + "copyright": None, + "license": None, + "generation_tool": "Reference FMUs (v0.0.38)", + "generation_date_and_time": "2025-02-04T04:14:58.875645+00:00", + }, + ), + ( + "2.0/VanDerPol.fmu", + { + "fmi_version": "2.0", + "model_name": "Van der Pol oscillator", + "guid": "{BD403596-3166-4232-ABC2-132BDF73E644}", + "description": "This model implements the van der Pol oscillator", + "author": None, + "version": None, + "copyright": None, + "license": None, + "generation_tool": "Reference FMUs (v0.0.38)", + "generation_date_and_time": "2025-02-04T04:14:59.010953+00:00", + }, + ), + ( + "2.0/Dahlquist.fmu", + { + "fmi_version": "2.0", + "model_name": "Dahlquist", + "guid": "{221063D2-EF4A-45FE-B954-B5BFEEA9A59B}", + "description": "This model implements the Dahlquist test equation", + "author": None, + "version": None, + "copyright": None, + "license": None, + "generation_tool": "Reference FMUs (v0.0.38)", + "generation_date_and_time": "2025-02-04T04:14:59.489702+00:00", + }, + ), + ( + "2.0/Stair.fmu", + { + "fmi_version": "2.0", + "model_name": "Stair", + "guid": "{BD403596-3166-4232-ABC2-132BDF73E644}", + "description": "This model generates a stair signal using time events", + "author": None, + "version": None, + "copyright": None, + "license": None, + "generation_tool": "Reference FMUs (v0.0.38)", + "generation_date_and_time": "2025-02-04T04:14:59.396420+00:00", + }, + ), + ( + "2.0/Resource.fmu", + { + "fmi_version": "2.0", + "model_name": "Resource", + "guid": "{7b9c2114-2ce5-4076-a138-2cbc69e069e5}", + "description": "This model loads data from a resource file", + "author": None, + "version": None, + "copyright": None, + "license": None, + "generation_tool": "Reference FMUs (v0.0.38)", + "generation_date_and_time": "2025-02-04T04:14:59.098389+00:00", + }, + ), ], ) -def test_metadata(reference_fmu, expected_fmi_version, reference_fmus_dir): +def test_metadata(reference_fmu, expected_metadata, reference_fmus_dir): filename = (reference_fmus_dir / reference_fmu).absolute() md = read_model_description(filename) - assert md.fmi_version == expected_fmi_version + assert md.fmi_version == expected_metadata["fmi_version"] + assert md.model_name == expected_metadata["model_name"] + assert md.guid == expected_metadata["guid"] + assert md.description == expected_metadata["description"] + assert md.author == expected_metadata["author"] + assert md.version == expected_metadata["version"] + assert md.copyright == expected_metadata["copyright"] + assert md.license == expected_metadata["license"] + assert md.generation_tool == expected_metadata["generation_tool"] + assert md.generation_date_and_time == expected_metadata["generation_date_and_time"] @pytest.mark.parametrize( @@ -88,83 +181,3 @@ def test_scarlar_variables( print("Output Vars:", output_vars) assert sorted(input_vars) == sorted(expected_inputs) assert sorted(output_vars) == sorted(expected_outputs) - - -# @pytest.mark.parametrize("xml_file", XML_FILES, ids=[f.name for f in XML_FILES]) -# def test_parse_xml_file(xml_file): -# """Test parsing a single XML file""" -# model = read_model_description(xml_file) - -# # Basic assertions -# assert model.model_name -# assert model.fmi_version -# assert model.model_variables - -# # Validate the model using Pydantic's validation -# # This ensures the model can be dumped and re-validated -# model.model_validate(model.model_dump()) - - -# @pytest.mark.parametrize("xml_file", XML_FILES, ids=[f.name for f in XML_FILES]) -# def test_model_serialization(xml_file): -# """Test serializing the model back to dict""" -# model = read_model_description(xml_file) - -# # Convert back to dict -# model_dict = model.model_dump() - -# # Check consistency -# assert model_dict["fmi_version"] == model.fmi_version -# assert model_dict["model_name"] == model.model_name - - -# @pytest.mark.parametrize("xml_file", XML_FILES, ids=[f.name for f in XML_FILES]) -# def test_model_details(xml_file): -# """Test detailed properties of the model""" -# model = read_model_description(xml_file) - -# # Check required attributes -# assert model.guid is not None -# assert len(model.guid) > 0 -# assert model.generation_tool is not None - -# # Check Model Variables -# assert model.model_variables is not None -# assert len(model.model_variables.variables) > 0 - -# # Check that every variable has a name and value reference -# for var in model.model_variables.variables: -# assert var.name is not None -# assert var.value_reference is not None -# # Check that one of the types is set (Real, Integer, Boolean, String, Enumeration) -# assert any( -# [ -# var.real is not None, -# var.integer is not None, -# var.boolean is not None, -# var.string is not None, -# var.enumeration is not None, -# ] -# ) - -# # Check Model Structure if present -# # if model.model_structure: -# # if model.model_structure.outputs: -# # for output in model.model_structure.outputs: -# # assert output.index > 0 - -# # if model.model_structure.derivatives: - -# # Specific checks for BouncingBall (test1.xml) -# if model.model_name == "BouncingBall": -# # Check specific variables exist -# var_names = [v.name for v in model.model_variables.variables] -# assert "time" in var_names -# assert "h" in var_names -# assert "v" in var_names - -# # Check unit definitions -# assert model.unit_definitions is not None -# unit_names = [u.name for u in model.unit_definitions.units] -# assert "m" in unit_names -# assert "m/s" in unit_names From e00709985baf69c97e7cc5fe09ede5cfb2f85024 Mon Sep 17 00:00:00 2001 From: coder Date: Sat, 3 Jan 2026 15:44:01 -0800 Subject: [PATCH 5/7] increase coverage --- tests/test_fmi2.py | 573 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 557 insertions(+), 16 deletions(-) diff --git a/tests/test_fmi2.py b/tests/test_fmi2.py index c43e310..34f6864 100644 --- a/tests/test_fmi2.py +++ b/tests/test_fmi2.py @@ -6,7 +6,7 @@ from mdreader.fmi2 import read_model_description -REFERENCE_FMUS_URL = "https://github.com/modelica/Reference-FMUs/releases/download/v0.0.38/Reference-FMUs-0.0.38.zip" +REFERENCE_FMUS_URL = "https://github.com/modelica/Reference-FMUs/releases/download/v0.0.39/Reference-FMUs-0.0.39.zip" @pytest.fixture(scope="session") @@ -43,8 +43,8 @@ def reference_fmus_dir(tmp_path_factory): "version": None, "copyright": None, "license": None, - "generation_tool": "Reference FMUs (v0.0.38)", - "generation_date_and_time": "2025-02-04T04:14:59.281054+00:00", + "generation_tool": "Reference FMUs (v0.0.39)", + "number_of_event_indicators": 0, }, ), ( @@ -58,8 +58,8 @@ def reference_fmus_dir(tmp_path_factory): "version": None, "copyright": None, "license": None, - "generation_tool": "Reference FMUs (v0.0.38)", - "generation_date_and_time": "2025-02-04T04:14:58.875645+00:00", + "generation_tool": "Reference FMUs (v0.0.39)", + "number_of_event_indicators": 1, }, ), ( @@ -73,8 +73,8 @@ def reference_fmus_dir(tmp_path_factory): "version": None, "copyright": None, "license": None, - "generation_tool": "Reference FMUs (v0.0.38)", - "generation_date_and_time": "2025-02-04T04:14:59.010953+00:00", + "generation_tool": "Reference FMUs (v0.0.39)", + "number_of_event_indicators": 0, }, ), ( @@ -88,8 +88,8 @@ def reference_fmus_dir(tmp_path_factory): "version": None, "copyright": None, "license": None, - "generation_tool": "Reference FMUs (v0.0.38)", - "generation_date_and_time": "2025-02-04T04:14:59.489702+00:00", + "generation_tool": "Reference FMUs (v0.0.39)", + "number_of_event_indicators": 0, }, ), ( @@ -103,8 +103,8 @@ def reference_fmus_dir(tmp_path_factory): "version": None, "copyright": None, "license": None, - "generation_tool": "Reference FMUs (v0.0.38)", - "generation_date_and_time": "2025-02-04T04:14:59.396420+00:00", + "generation_tool": "Reference FMUs (v0.0.39)", + "number_of_event_indicators": 0, }, ), ( @@ -118,8 +118,8 @@ def reference_fmus_dir(tmp_path_factory): "version": None, "copyright": None, "license": None, - "generation_tool": "Reference FMUs (v0.0.38)", - "generation_date_and_time": "2025-02-04T04:14:59.098389+00:00", + "generation_tool": "Reference FMUs (v0.0.39)", + "number_of_event_indicators": 0, }, ), ], @@ -136,7 +136,9 @@ def test_metadata(reference_fmu, expected_metadata, reference_fmus_dir): assert md.copyright == expected_metadata["copyright"] assert md.license == expected_metadata["license"] assert md.generation_tool == expected_metadata["generation_tool"] - assert md.generation_date_and_time == expected_metadata["generation_date_and_time"] + assert ( + md.number_of_event_indicators == expected_metadata["number_of_event_indicators"] + ) @pytest.mark.parametrize( @@ -150,6 +152,7 @@ def test_metadata(reference_fmu, expected_metadata, reference_fmus_dir): "Int32_input", "Boolean_input", "Enumeration_input", + "String_input", ], [ "Float64_continuous_output", @@ -157,6 +160,7 @@ def test_metadata(reference_fmu, expected_metadata, reference_fmus_dir): "Int32_output", "Boolean_output", "Enumeration_output", + "String_output", ], ), ("2.0/BouncingBall.fmu", [], ["h", "v"]), @@ -177,7 +181,544 @@ def test_scarlar_variables( output_vars = [ var.name for var in md.model_variables.variables if var.causality == "output" ] - print("Input Vars:", input_vars) - print("Output Vars:", output_vars) assert sorted(input_vars) == sorted(expected_inputs) assert sorted(output_vars) == sorted(expected_outputs) + + +@pytest.mark.parametrize( + "reference_fmu, expected_units", + [ + ( + "2.0/BouncingBall.fmu", + [ + {"name": "m", "base_unit": {"m": 1}}, + {"name": "m/s", "base_unit": {"m": 1, "s": -1}}, + {"name": "m/s2", "base_unit": {"m": 1, "s": -2}}, + ], + ), + # Feedthrough and others may not have unit definitions + ("2.0/Feedthrough.fmu", []), + ("2.0/VanDerPol.fmu", []), + ("2.0/Dahlquist.fmu", []), + ("2.0/Stair.fmu", []), + ("2.0/Resource.fmu", []), + ], +) +def test_unit_definitions(reference_fmu, expected_units, reference_fmus_dir): + """Test that unit definitions are properly parsed with correct values""" + filename = (reference_fmus_dir / reference_fmu).absolute() + md = read_model_description(filename) + + if expected_units: + assert md.unit_definitions is not None + assert len(md.unit_definitions.units) == len(expected_units) + + for i, expected_unit in enumerate(expected_units): + unit = md.unit_definitions.units[i] + assert unit.name == expected_unit["name"] + + if "base_unit" in expected_unit: + base_unit = unit.base_unit + expected_base = expected_unit["base_unit"] + for attr, expected_val in expected_base.items(): + assert getattr(base_unit, attr) == expected_val + else: + # For FMUs without unit definitions, ensure it's None or empty + if md.unit_definitions: + assert len(md.unit_definitions.units) == 0 + + +@pytest.mark.parametrize( + "reference_fmu, expected_types", + [ + ( + "2.0/Feedthrough.fmu", + [ + { + "name": "Option", + "type_category": "Enumeration", + "items": [ + {"name": "Option 1", "value": 1, "description": "First option"}, + { + "name": "Option 2", + "value": 2, + "description": "Second option", + }, + ], + } + ], + ), + ( + "2.0/BouncingBall.fmu", + [ + { + "name": "Position", + "type_category": "Real", + "quantity": "Position", + "unit": "m", + }, + { + "name": "Velocity", + "type_category": "Real", + "quantity": "Velocity", + "unit": "m/s", + }, + { + "name": "Acceleration", + "type_category": "Real", + "quantity": "Acceleration", + "unit": "m/s2", + }, + ], + ), + # Other FMUs may not have type definitions + ("2.0/VanDerPol.fmu", []), + ("2.0/Dahlquist.fmu", []), + ("2.0/Stair.fmu", []), + ("2.0/Resource.fmu", []), + ], +) +def test_type_definitions(reference_fmu, expected_types, reference_fmus_dir): + """Test that type definitions are properly parsed with correct values""" + filename = (reference_fmus_dir / reference_fmu).absolute() + md = read_model_description(filename) + + if expected_types: + assert md.type_definitions is not None + assert len(md.type_definitions.simple_types) == len(expected_types) + + for i, expected_type in enumerate(expected_types): + simple_type = md.type_definitions.simple_types[i] + assert simple_type.name == expected_type["name"] + assert simple_type.get_type_category() == expected_type["type_category"] + + if "items" in expected_type and simple_type.enumeration: + for j, expected_item in enumerate(expected_type["items"]): + item = simple_type.enumeration.items[j] + assert item.name == expected_item["name"] + assert item.value == expected_item["value"] + assert item.description == expected_item["description"] + elif "quantity" in expected_type: + if simple_type.real: + assert simple_type.real.quantity == expected_type["quantity"] + if "unit" in expected_type: + assert simple_type.real.unit == expected_type["unit"] + else: + # For FMUs without type definitions, ensure it's None or empty + if md.type_definitions: + assert len(md.type_definitions.simple_types) == 0 + + +@pytest.mark.parametrize( + "reference_fmu, expected_variables", + [ + # Test specific variables from Feedthrough + ( + "2.0/Feedthrough.fmu", + [ + { + "name": "time", + "value_reference": 0, + "causality": "independent", + "variability": "continuous", + "type": "Real", + }, + { + "name": "Float64_continuous_input", + "value_reference": 7, + "causality": "input", + "variability": "continuous", + "type": "Real", + "start": 0.0, + }, + { + "name": "Int32_input", + "value_reference": 19, + "causality": "input", + "variability": "discrete", + "type": "Integer", + "start": 0, + }, + { + "name": "Boolean_input", + "value_reference": 27, + "causality": "input", + "variability": "discrete", + "type": "Boolean", + "start": False, + }, + { + "name": "String_input", + "value_reference": 29, + "causality": "input", + "variability": "discrete", + "type": "String", + "start": "Set me!", + }, + { + "name": "Enumeration_input", + "value_reference": 33, + "causality": "input", + "variability": "discrete", + "type": "Enumeration", + "declared_type": "Option", + "start": 1, + }, + ], + ), + # Test specific variables from BouncingBall + ( + "2.0/BouncingBall.fmu", + [ + { + "name": "time", + "value_reference": 0, + "causality": "independent", + "variability": "continuous", + "description": "Simulation time", + "type": "Real", + }, + { + "name": "h", + "value_reference": 1, + "causality": "output", + "variability": "continuous", + "initial": "exact", + "description": "Position of the ball", + "type": "Real", + "start": 1.0, + "reinit": True, + "declared_type": "Position", + }, + { + "name": "g", + "value_reference": 5, + "causality": "parameter", + "variability": "fixed", + "initial": "exact", + "description": "Gravity acting on the ball", + "type": "Real", + "start": -9.81, + "declared_type": "Acceleration", + }, + ], + ), + ], +) +def test_variable_properties(reference_fmu, expected_variables, reference_fmus_dir): + """Test that variable properties are correctly parsed from XML""" + filename = (reference_fmus_dir / reference_fmu).absolute() + md = read_model_description(filename) + + # Create a mapping of variable names to variables for easy lookup + var_map = {var.name: var for var in md.model_variables.variables} + + for expected_var in expected_variables: + var_name = expected_var["name"] + assert var_name in var_map, f"Variable {var_name} not found in model" + + var = var_map[var_name] + + # Check common properties + assert var.name == expected_var["name"] + assert var.value_reference == expected_var["value_reference"] + assert var.causality.value == expected_var["causality"] + assert var.variability.value == expected_var["variability"] + + if "description" in expected_var: + assert var.description == expected_var["description"] + + if "initial" in expected_var: + assert var.initial.value == expected_var["initial"] + + # Check variable-specific properties + var_type = var.get_variable_type() + assert var_type == expected_var["type"] + + if var_type == "Real": + real_var = var.real + if "start" in expected_var: + assert real_var.start == expected_var["start"] + if "declared_type" in expected_var: + assert real_var.declared_type == expected_var["declared_type"] + if "reinit" in expected_var: + assert real_var.reinit == expected_var["reinit"] + elif var_type == "Integer": + integer_var = var.integer + if "start" in expected_var: + assert integer_var.start == expected_var["start"] + elif var_type == "Boolean": + boolean_var = var.boolean + if "start" in expected_var: + assert boolean_var.start == expected_var["start"] + elif var_type == "String": + string_var = var.string + if "start" in expected_var: + assert string_var.start == expected_var["start"] + elif var_type == "Enumeration": + enumeration_var = var.enumeration + if "declared_type" in expected_var: + assert enumeration_var.declared_type == expected_var["declared_type"] + if "start" in expected_var: + assert enumeration_var.start == expected_var["start"] + + +@pytest.mark.parametrize( + "reference_fmu, expected_model_exchange", + [ + ( + "2.0/Feedthrough.fmu", + { + "model_identifier": "Feedthrough", + "can_not_use_memory_management_functions": True, + "can_get_and_set_fmu_state": True, + }, + ), + ( + "2.0/BouncingBall.fmu", + { + "model_identifier": "BouncingBall", + "can_not_use_memory_management_functions": True, + "can_get_and_set_fmu_state": True, + }, + ), + ], +) +def test_model_exchange_interface( + reference_fmu, expected_model_exchange, reference_fmus_dir +): + """Test that Model Exchange interface is correctly parsed""" + filename = (reference_fmus_dir / reference_fmu).absolute() + md = read_model_description(filename) + + assert md.model_exchange is not None + assert ( + md.model_exchange.model_identifier + == expected_model_exchange["model_identifier"] + ) + assert ( + md.model_exchange.can_not_use_memory_management_functions + == expected_model_exchange["can_not_use_memory_management_functions"] + ) + assert ( + md.model_exchange.can_get_and_set_fmu_state + == expected_model_exchange["can_get_and_set_fmu_state"] + ) + + +@pytest.mark.parametrize( + "reference_fmu, expected_co_simulation", + [ + ( + "2.0/Feedthrough.fmu", + { + "model_identifier": "Feedthrough", + "can_handle_variable_communication_step_size": True, + "can_not_use_memory_management_functions": True, + }, + ), + ( + "2.0/BouncingBall.fmu", + { + "model_identifier": "BouncingBall", + "can_handle_variable_communication_step_size": True, + "can_not_use_memory_management_functions": True, + }, + ), + ], +) +def test_co_simulation_interface( + reference_fmu, expected_co_simulation, reference_fmus_dir +): + """Test that Co-Simulation interface is correctly parsed""" + filename = (reference_fmus_dir / reference_fmu).absolute() + md = read_model_description(filename) + + assert md.co_simulation is not None + assert ( + md.co_simulation.model_identifier == expected_co_simulation["model_identifier"] + ) + assert ( + md.co_simulation.can_handle_variable_communication_step_size + == expected_co_simulation["can_handle_variable_communication_step_size"] + ) + assert ( + md.co_simulation.can_not_use_memory_management_functions + == expected_co_simulation["can_not_use_memory_management_functions"] + ) + + +@pytest.mark.parametrize( + "reference_fmu, expected_default_experiment", + [ + ( + "2.0/Feedthrough.fmu", + {"stop_time": 2.0}, + ), + ( + "2.0/BouncingBall.fmu", + {"start_time": 0.0, "stop_time": 3.0, "step_size": 1e-2}, + ), + ( + "2.0/VanDerPol.fmu", + {"start_time": 0.0, "stop_time": 20.0, "step_size": 1e-2}, + ), + ], +) +def test_default_experiment_values( + reference_fmu, expected_default_experiment, reference_fmus_dir +): + """Test that DefaultExperiment values are correctly parsed""" + filename = (reference_fmus_dir / reference_fmu).absolute() + md = read_model_description(filename) + + assert md.default_experiment is not None + + if "start_time" in expected_default_experiment: + assert ( + md.default_experiment.start_time + == expected_default_experiment["start_time"] + ) + if "stop_time" in expected_default_experiment: + assert ( + md.default_experiment.stop_time == expected_default_experiment["stop_time"] + ) + if "step_size" in expected_default_experiment: + assert ( + md.default_experiment.step_size == expected_default_experiment["step_size"] + ) + + +@pytest.mark.parametrize( + "reference_fmu, expected_log_categories", + [ + ( + "2.0/Feedthrough.fmu", + [ + {"name": "logEvents", "description": "Log events"}, + {"name": "logStatusError", "description": "Log error messages"}, + ], + ), + ( + "2.0/BouncingBall.fmu", + [ + {"name": "logEvents", "description": "Log events"}, + {"name": "logStatusError", "description": "Log error messages"}, + ], + ), + ], +) +def test_log_categories(reference_fmu, expected_log_categories, reference_fmus_dir): + """Test that log categories are correctly parsed""" + filename = (reference_fmus_dir / reference_fmu).absolute() + md = read_model_description(filename) + + assert md.log_categories is not None + assert len(md.log_categories.categories) == len(expected_log_categories) + + for i, expected_category in enumerate(expected_log_categories): + category = md.log_categories.categories[i] + assert category.name == expected_category["name"] + assert category.description == expected_category["description"] + + +@pytest.mark.parametrize( + "reference_fmu", + [ + "2.0/BouncingBall.fmu", + "2.0/VanDerPol.fmu", + ], +) +def test_model_structure_outputs(reference_fmu, reference_fmus_dir): + """Test that ModelStructure outputs are correctly parsed""" + filename = (reference_fmus_dir / reference_fmu).absolute() + md = read_model_description(filename) + + if md.model_structure and md.model_structure.outputs: + # Check that outputs exist and have correct structure + for unknown in md.model_structure.outputs.unknowns: + assert isinstance(unknown.index, int) + if unknown.dependencies: + assert all(isinstance(dep, int) for dep in unknown.dependencies) + if unknown.dependencies_kind: + assert all(hasattr(dep, "value") for dep in unknown.dependencies_kind) + + +@pytest.mark.parametrize( + "reference_fmu", + [ + "2.0/BouncingBall.fmu", + "2.0/VanDerPol.fmu", + ], +) +def test_model_structure_derivatives(reference_fmu, reference_fmus_dir): + """Test that ModelStructure derivatives are correctly parsed""" + filename = (reference_fmus_dir / reference_fmu).absolute() + md = read_model_description(filename) + + if md.model_structure and md.model_structure.derivatives: + # Check that derivatives exist and have correct structure + for unknown in md.model_structure.derivatives.unknowns: + assert isinstance(unknown.index, int) + if unknown.dependencies: + assert all(isinstance(dep, int) for dep in unknown.dependencies) + if unknown.dependencies_kind: + assert all(hasattr(dep, "value") for dep in unknown.dependencies_kind) + + +@pytest.mark.parametrize( + "reference_fmu", + [ + "2.0/BouncingBall.fmu", + "2.0/VanDerPol.fmu", + ], +) +def test_model_structure_initial_unknowns(reference_fmu, reference_fmus_dir): + """Test that ModelStructure initial unknowns are correctly parsed""" + filename = (reference_fmus_dir / reference_fmu).absolute() + md = read_model_description(filename) + + if md.model_structure and md.model_structure.initial_unknowns: + # Check that initial unknowns exist and have correct structure + for unknown in md.model_structure.initial_unknowns.unknowns: + assert isinstance(unknown.index, int) + if unknown.dependencies: + assert all(isinstance(dep, int) for dep in unknown.dependencies) + if unknown.dependencies_kind: + assert all(hasattr(dep, "value") for dep in unknown.dependencies_kind) + + +def test_variable_validation(): + """Test validation logic in RealSimpleType and IntegerSimpleType""" + from mdreader.fmi2 import RealSimpleType, IntegerSimpleType + + # Test RealSimpleType validation - max >= min + with pytest.raises(ValueError, match="max.*must be >= min"): + RealSimpleType(min_value=10.0, max_value=5.0) + + # Test IntegerSimpleType validation - max >= min + with pytest.raises(ValueError, match="max.*must be >= min"): + IntegerSimpleType(min_value=10, max_value=5) + + +def test_str_to_bool_function(): + """Test the _str_to_bool helper function""" + from mdreader.fmi2 import _str_to_bool + + # Test true values + assert _str_to_bool("true") is True + assert _str_to_bool("True") is True + assert _str_to_bool("1") is True + assert _str_to_bool("yes") is True + assert _str_to_bool("on") is True + + # Test false values + assert _str_to_bool("false") is False + assert _str_to_bool("False") is False + assert _str_to_bool("0") is False + assert _str_to_bool("no") is False + assert _str_to_bool("off") is False + assert _str_to_bool("") is False + + # Test None input + assert _str_to_bool(None) is None From efdd827fb85d2df8ff5f49c1f288570fae4bb56f Mon Sep 17 00:00:00 2001 From: coder Date: Sat, 3 Jan 2026 15:58:06 -0800 Subject: [PATCH 6/7] increase coverage --- Makefile | 2 +- tests/test_fmi2.py | 149 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e42d13e..7b4e278 100644 --- a/Makefile +++ b/Makefile @@ -56,7 +56,7 @@ publish-dryrun: build ## Dry run of publishing the package to PyPI # TODO: Check if it will run the pytest from the correct venv test: ## Run tests using pytest @echo "🚀 Running tests with pytest" - @uv run --frozen pytest --doctest-modules --cov=mdreader --cov-report=term --cov-report=html + @uv run --frozen pytest --doctest-modules --cov=mdreader --cov-report=term --cov-report=xml .PHONY: help help: diff --git a/tests/test_fmi2.py b/tests/test_fmi2.py index 34f6864..f3663fb 100644 --- a/tests/test_fmi2.py +++ b/tests/test_fmi2.py @@ -722,3 +722,152 @@ def test_str_to_bool_function(): # Test None input assert _str_to_bool(None) is None + + +def test_read_model_description_unsupported_file_type(): + """Test that read_model_description raises error for unsupported file types""" + from mdreader.fmi2 import read_model_description + import tempfile + import os + + # Create a temporary file with unsupported extension + with tempfile.NamedTemporaryFile(delete=False, suffix=".txt") as tmp: + tmp.write(b"dummy content") + tmp_path = tmp.name + + try: + with pytest.raises(ValueError, match="Unsupported file type"): + read_model_description(tmp_path) + finally: + os.unlink(tmp_path) + + +def test_get_type_category_methods(): + """Test the get_type_category and get_variable_type methods""" + from mdreader.fmi2 import SimpleType, RealSimpleType, ScalarVariable, RealVariable + + # Test SimpleType.get_type_category + real_simple_type = RealSimpleType() + simple_type = SimpleType(name="test", real=real_simple_type) + assert simple_type.get_type_category() == "Real" + + # Test ScalarVariable.get_variable_type + real_var = RealVariable() + scalar_var = ScalarVariable(name="test", value_reference=1, real=real_var) + assert scalar_var.get_variable_type() == "Real" + + +def test_error_conditions_in_parsing(): + """Test error conditions in XML parsing""" + from mdreader.fmi2 import ( + _parse_xml_to_model, + _parse_model_exchange, + _parse_co_simulation, + _parse_unit, + _parse_display_unit, + _parse_simple_type, + _parse_enumeration_simple_type, + ) + import xml.etree.ElementTree as ET + + # Test missing fmiVersion attribute + xml_content = """ + + """ + + with pytest.raises(ValueError, match="fmiVersion attribute is required"): + _parse_xml_to_model(ET.fromstring(xml_content)) + + # Test missing modelName attribute + xml_content = """ + + """ + + with pytest.raises(ValueError, match="modelName attribute is required"): + _parse_xml_to_model(ET.fromstring(xml_content)) + + # Test missing GUID attribute + xml_content = """ + + """ + + with pytest.raises(ValueError, match="GUID attribute is required"): + _parse_xml_to_model(ET.fromstring(xml_content)) + + # Test ModelExchange element without modelIdentifier + me_xml = ET.fromstring("") + with pytest.raises( + ValueError, match="ModelExchange element must have modelIdentifier attribute" + ): + _parse_model_exchange(me_xml) + + # Test CoSimulation element without modelIdentifier + cs_xml = ET.fromstring("") + with pytest.raises( + ValueError, match="CoSimulation element must have modelIdentifier attribute" + ): + _parse_co_simulation(cs_xml) + + # Test Unit element without name attribute + unit_xml = ET.fromstring("") + with pytest.raises(ValueError, match="Unit element.*must have name attribute"): + _parse_unit(unit_xml) + + # Test DisplayUnit element without name attribute + display_unit_xml = ET.fromstring("") + with pytest.raises( + ValueError, match="DisplayUnit element.*must have name attribute" + ): + _parse_display_unit(display_unit_xml) + + # Test SimpleType element without name attribute + simple_type_xml = ET.fromstring("") + with pytest.raises( + ValueError, match="SimpleType element.*must have name attribute" + ): + _parse_simple_type(simple_type_xml) + + # Test Item element without name attribute + item_xml = ET.fromstring('') + with pytest.raises(ValueError, match="Item element.*must have name attribute"): + # We need to test this inside an enumeration context + enum_xml = ET.fromstring('') + _parse_enumeration_simple_type(enum_xml) + + # Test Item element without value attribute + item_xml = ET.fromstring('') + with pytest.raises(ValueError, match="Item element.*must have value attribute"): + # We need to test this inside an enumeration context + enum_xml = ET.fromstring('') + _parse_enumeration_simple_type(enum_xml) + + +def test_edge_cases_and_validators(): + """Test edge cases and validation logic""" + from mdreader.fmi2 import RealSimpleType, IntegerSimpleType, _str_to_bool + + # Test RealSimpleType with max < min (should raise error) + with pytest.raises(ValueError, match="max.*must be >= min"): + RealSimpleType(min_value=10.0, max_value=5.0) + + # Test IntegerSimpleType with max < min (should raise error) + with pytest.raises(ValueError, match="max.*must be >= min"): + IntegerSimpleType(min_value=10, max_value=5) + + # Test _str_to_bool with invalid value + with pytest.raises(ValueError, match="Cannot convert.*to boolean"): + _str_to_bool("invalid_boolean_value") + + # Test _str_to_bool with various valid values + assert _str_to_bool("true") is True + assert _str_to_bool("false") is False + assert _str_to_bool("1") is True + assert _str_to_bool("0") is False + assert _str_to_bool("yes") is True + assert _str_to_bool("no") is False + assert _str_to_bool("on") is True + assert _str_to_bool("off") is False + assert _str_to_bool("") is False + assert _str_to_bool(None) is None + assert _str_to_bool(False) is False + assert _str_to_bool(True) is True From a640a335c47e09f3d06a31e4402058253217a916 Mon Sep 17 00:00:00 2001 From: coder Date: Sat, 3 Jan 2026 16:21:07 -0800 Subject: [PATCH 7/7] version bump --- .bumpversion.toml | 2 +- pyproject.toml | 2 +- src/mdreader/__init__.py | 2 +- uv.lock | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.bumpversion.toml b/.bumpversion.toml index 659df17..550e005 100644 --- a/.bumpversion.toml +++ b/.bumpversion.toml @@ -1,5 +1,5 @@ [tool.bumpversion] -current_version = "0.1.0" +current_version = "0.1.1" parse = "(?P\\d+)\\.(?P\\d+)\\.(?P\\d+)" serialize = ["{major}.{minor}.{patch}"] search = "{current_version}" diff --git a/pyproject.toml b/pyproject.toml index fac8177..d2e3a57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "mdreader" -version = "0.1.0" +version = "0.1.1" description = "A Python library for reading and parsing Functional Mock-up Interface model description XML file." readme = "README.md" authors = [ diff --git a/src/mdreader/__init__.py b/src/mdreader/__init__.py index 3dc1f76..485f44a 100644 --- a/src/mdreader/__init__.py +++ b/src/mdreader/__init__.py @@ -1 +1 @@ -__version__ = "0.1.0" +__version__ = "0.1.1" diff --git a/uv.lock b/uv.lock index 53e2edc..760f352 100644 --- a/uv.lock +++ b/uv.lock @@ -244,7 +244,7 @@ wheels = [ [[package]] name = "mdreader" -version = "0.1.0" +version = "0.1.1" source = { editable = "." } dependencies = [ { name = "pydantic" },