From d5971282d1a2cbeddab8b3594140eba7c2741354 Mon Sep 17 00:00:00 2001 From: Jonatan Kutchinsky Date: Wed, 5 Nov 2025 14:16:15 +0100 Subject: [PATCH 01/26] First unit tests in repo: Tests of config state, models common and models last run --- README.md | 14 + poetry.lock | 149 ++++++++- pyproject.toml | 12 + tests/__init__.py | 0 tests/conftest.py | 152 +++++++++ tests/unit/__init__.py | 0 tests/unit/test_config_state.py | 321 +++++++++++++++++++ tests/unit/test_models_common.py | 326 +++++++++++++++++++ tests/unit/test_models_last_run.py | 485 +++++++++++++++++++++++++++++ 9 files changed, 1447 insertions(+), 12 deletions(-) create mode 100644 tests/__init__.py create mode 100644 tests/conftest.py create mode 100644 tests/unit/__init__.py create mode 100644 tests/unit/test_config_state.py create mode 100644 tests/unit/test_models_common.py create mode 100644 tests/unit/test_models_last_run.py diff --git a/README.md b/README.md index ba43330..66d9c16 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,20 @@ qualibrate-runner start In this case, ensure that the correct address and port are specified in the configuration file. For more details, refer to the [configuration documentation](https://qua-platform.github.io/qualibrate/configuration/). +## Testing + +To run the test suite: + +```bash +poetry run pytest +``` + +For verbose output: + +```bash +poetry run pytest -v +``` + ## License QUAlibrate-Runner is licensed under the BSD-3 license. See the [LICENSE](https://github.com/qua-platform/qualibrate-runner/blob/main/LICENSE) file for more details. diff --git a/poetry.lock b/poetry.lock index 3485e40..e99dfe1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -198,7 +198,7 @@ description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.9" groups = ["main"] -markers = "(sys_platform == \"linux\" or sys_platform == \"darwin\") and (platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\")" +markers = "platform_python_implementation != \"PyPy\" and sys_platform == \"linux\" or sys_platform == \"darwin\"" files = [ {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, @@ -415,12 +415,12 @@ 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" -groups = ["main"] -markers = "os_name == \"nt\" or platform_system == \"Windows\"" +groups = ["main", "test"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "platform_system == \"Windows\" or os_name == \"nt\"", test = "sys_platform == \"win32\""} [[package]] name = "contourpy" @@ -797,7 +797,7 @@ version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "test"] markers = "python_version == \"3.10\"" files = [ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, @@ -955,6 +955,21 @@ type1 = ["xattr ; sys_platform == \"darwin\""] unicode = ["unicodedata2 (>=15.1.0) ; python_version <= \"3.12\""] woff = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "zopfli (>=0.1.4)"] +[[package]] +name = "freezegun" +version = "1.5.5" +description = "Let your Python tests travel through time" +optional = false +python-versions = ">=3.8" +groups = ["test"] +files = [ + {file = "freezegun-1.5.5-py3-none-any.whl", hash = "sha256:cd557f4a75cf074e84bc374249b9dd491eaeacd61376b9eb3c423282211619d2"}, + {file = "freezegun-1.5.5.tar.gz", hash = "sha256:ac7742a6cc6c25a2c35e9292dfd554b897b517d2dec26891a2e8debf205cb94a"}, +] + +[package.dependencies] +python-dateutil = ">=2.7" + [[package]] name = "genson" version = "1.3.0" @@ -1226,6 +1241,18 @@ enabler = ["pytest-enabler (>=2.2)"] test = ["pygments", "pytest (>=6,!=8.1.*)"] type = ["pytest-mypy"] +[[package]] +name = "iniconfig" +version = "2.3.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.10" +groups = ["test"] +files = [ + {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, + {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, +] + [[package]] name = "installer" version = "0.7.0" @@ -2091,7 +2118,7 @@ version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "test"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, @@ -2315,6 +2342,22 @@ files = [ packaging = "*" tenacity = ">=6.2.0" +[[package]] +name = "pluggy" +version = "1.6.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.9" +groups = ["test"] +files = [ + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["coverage", "pytest", "pytest-benchmark"] + [[package]] name = "poethepoet" version = "0.37.0" @@ -2409,7 +2452,7 @@ description = "C parser in Python" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "(sys_platform == \"linux\" or sys_platform == \"darwin\") and (platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and implementation_name != \"PyPy\"" +markers = "(platform_python_implementation != \"PyPy\" and sys_platform == \"linux\" or sys_platform == \"darwin\") and implementation_name != \"PyPy\"" files = [ {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"}, {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}, @@ -2573,6 +2616,21 @@ gcp-secret-manager = ["google-cloud-secret-manager (>=2.23.1)"] toml = ["tomli (>=2.0.1)"] yaml = ["pyyaml (>=6.0.1)"] +[[package]] +name = "pygments" +version = "2.19.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +groups = ["test"] +files = [ + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + [[package]] name = "pyparsing" version = "3.2.5" @@ -2600,13 +2658,55 @@ files = [ {file = "pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8"}, ] +[[package]] +name = "pytest" +version = "8.4.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.9" +groups = ["test"] +files = [ + {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, + {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, +] + +[package.dependencies] +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} +iniconfig = ">=1" +packaging = ">=20" +pluggy = ">=1.5,<2" +pygments = ">=2.7.2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-mock" +version = "3.15.1" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.9" +groups = ["test"] +files = [ + {file = "pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d"}, + {file = "pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f"}, +] + +[package.dependencies] +pytest = ">=6.2.5" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + [[package]] name = "python-dateutil" version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main"] +groups = ["main", "test"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -2876,6 +2976,30 @@ qualang-tools = {version = ">=0.20.0", markers = "python_version >= \"3.10\" and qualibrate-config = ">=0.1.7,<0.2.0" types-networkx = ">=3.4.2.20250527,<3.5.0.0" +[[package]] +name = "quam" +version = "0.4.2" +description = "Quantum Abstract Machine (QUAM) facilitates development of abstraction layers in experiments." +optional = false +python-versions = "<3.13,>=3.9" +groups = ["main"] +files = [ + {file = "quam-0.4.2-py3-none-any.whl", hash = "sha256:4651c97f3a0f601f02e7d4d980973bc9eeab47af8954a1a776f4ba6a6ca601f8"}, + {file = "quam-0.4.2.tar.gz", hash = "sha256:0804833adc1813b41d78b14183ddec5135bc97d013fce2744041712e69e668a9"}, +] + +[package.dependencies] +numpy = ">=1.21.2" +qm-qua = ">=1.2.2" +qualang-tools = ">=0.15.0" +qualibrate-config = ">=0.1.6" +typeguard = ">=4.1.0" + +[package.extras] +build = ["build (>=1.2.1)", "setuptools (>=71)", "setuptools-scm (>=8.1.0)"] +dev = ["black (>=23.7.0)", "flake8 (>=5.0.1)", "ipykernel (>=6.24.0)", "pyproject-flake8 (>=5.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=4.1.0)", "pytest-mock (>=3.6.1)"] +docs = ["mkdocs-gen-files", "mkdocs-jupyter", "mkdocstrings[python] (>=0.18)"] + [[package]] name = "rapidfuzz" version = "3.14.1" @@ -3202,7 +3326,7 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main"] +groups = ["main", "test"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -3285,7 +3409,7 @@ version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -3320,7 +3444,7 @@ files = [ {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] -markers = {main = "python_version <= \"3.11\"", dev = "python_version == \"3.10\""} +markers = {main = "python_version <= \"3.11\"", dev = "python_version == \"3.10\"", test = "python_version == \"3.10\""} [[package]] name = "tomli-w" @@ -3394,11 +3518,12 @@ version = "4.15.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, ] +markers = {test = "python_version == \"3.10\""} [[package]] name = "typing-inspection" @@ -3777,4 +3902,4 @@ cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and pyt [metadata] lock-version = "2.1" python-versions = "^3.10,<3.13" -content-hash = "4a5bb60f4cf6b98bd5c1815a1e2b00de94d894fa8c1f81969d3ab38b34fd1e68" +content-hash = "74cb247423a030ecd6aeedc032fb871730ed8e0d1c6e9a77e953a06005f33147" diff --git a/pyproject.toml b/pyproject.toml index daf793a..ad5c0e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,12 +17,18 @@ jsonpatch = "^1.33" qualibrate-core = "^0.4.0,<0.5.0" qualibrate-config = "^0.1.7,<0.2.0" websockets = "^15.0.1" +quam = "^0.4.2" [tool.poetry.group.dev.dependencies] mypy = "^1.18.2" ruff = "^0.13.1" poethepoet = "^0.37.0" +[tool.poetry.group.test.dependencies] +pytest = "^8.4.2" +pytest-mock = "^3.15.1" +freezegun = "^1.2.0" + #[tool.poetry.group.qm-dev.dependencies] #qualibrate-config = {path = "../qualibrate-config", develop = true} #qualibrate-core = {path = "../qualibrate-core", develop = true} @@ -69,6 +75,12 @@ check.sequence = ["lint", "format", "type"] fix.sequence = ["lint-fix", "format-fix", "type"] fix.ignore_fail = "return_non_zero" +[tool.pytest.ini_options] +testpaths = ["tests"] +python_files = ["test_*.py"] +python_classes = ["Test*"] +python_functions = ["test_*"] + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..606d5ce --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,152 @@ +""" +Shared fixtures for qualibrate-runner tests. + +This module provides common fixtures used across unit and integration tests, +including mock objects, sample data, and test utilities. +""" + +from datetime import datetime, timezone +from unittest.mock import Mock + +import pytest + +from qualibrate_runner.config.models import State +from qualibrate_runner.core.models.common import RunError, StateUpdate +from qualibrate_runner.core.models.enums import RunnableType, RunStatusEnum +from qualibrate_runner.core.models.last_run import LastRun + + +@pytest.fixture +def aware_datetime(): + """Provide a timezone-aware datetime for testing.""" + return datetime(2024, 1, 15, 10, 30, 0, tzinfo=timezone.utc) + + +@pytest.fixture +def later_datetime(): + """Provide a later timezone-aware datetime for testing durations.""" + return datetime(2024, 1, 15, 10, 30, 5, 500000, tzinfo=timezone.utc) + + +@pytest.fixture +def sample_traceback(): + """Provide a sample traceback as list of strings.""" + return [ + ' File "/path/to/node.py", line 42, in run\n' + " result = self.process_data()\n", + ' File "/path/to/node.py", line 55, in process_data\n' + ' raise ValueError("Invalid data")\n', + ] + + +@pytest.fixture +def sample_run_error(sample_traceback): + """Provide a sample RunError instance.""" + return RunError( + error_class="ValueError", + message="Invalid data", + traceback=sample_traceback, + ) + + +@pytest.fixture +def sample_state_update(): + """Provide a sample StateUpdate instance.""" + return StateUpdate( + key="qubit_0", + attr="resonance_frequency", + old=5.0e9, + new=5.1e9, + updated=True, + ) + + +@pytest.fixture +def sample_last_run_running(aware_datetime): + """Provide a LastRun instance with RUNNING status.""" + return LastRun( + status=RunStatusEnum.RUNNING, + started_at=aware_datetime, + name="test_node", + idx=-1, + runnable_type=RunnableType.NODE, + passed_parameters={}, + ) + + +@pytest.fixture +def sample_last_run_finished(aware_datetime, later_datetime): + """Provide a LastRun instance with FINISHED status.""" + # Use model_construct to bypass validation for mock run_result + return LastRun.model_construct( + status=RunStatusEnum.FINISHED, + started_at=aware_datetime, + completed_at=later_datetime, + name="test_node", + idx=42, + runnable_type=RunnableType.NODE, + passed_parameters={"amplitude": 0.5}, + run_result=Mock(success=True), + ) + + +@pytest.fixture +def sample_last_run_error(aware_datetime, later_datetime, sample_run_error): + """Provide a LastRun instance with ERROR status.""" + return LastRun( + status=RunStatusEnum.ERROR, + started_at=aware_datetime, + completed_at=later_datetime, + name="test_node", + idx=-1, + runnable_type=RunnableType.NODE, + passed_parameters={}, + error=sample_run_error, + ) + + +@pytest.fixture +def fresh_state(): + """Provide a fresh State instance with no execution history.""" + return State() + + +@pytest.fixture +def mock_node(): + """Provide a mock QualibrationNode. + + Note: This cannot be used directly with State() due to Pydantic + validation. Use state_with_node fixture instead. + """ + node = Mock() + node.name = "test_node" + node.snapshot_idx = 42 + node.run_summary = Mock(success=True) + node.state_updates = {} + return node + + +@pytest.fixture +def mock_workflow(): + """Provide a mock workflow (QGraph). + + Note: This cannot be used directly with State() due to Pydantic + validation. Use state_with_workflow fixture instead. + """ + workflow = Mock() + workflow.name = "test_workflow" + workflow.snapshot_idx = 100 + workflow.run_summary = Mock(success=True) + return workflow + + +@pytest.fixture +def state_with_node(mock_node): + """Provide a State with a mock node (bypassing validation).""" + return State.model_construct(run_item=mock_node) + + +@pytest.fixture +def state_with_workflow(mock_workflow): + """Provide a State with a mock workflow (bypassing validation).""" + return State.model_construct(run_item=mock_workflow) diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/test_config_state.py b/tests/unit/test_config_state.py new file mode 100644 index 0000000..874b9f8 --- /dev/null +++ b/tests/unit/test_config_state.py @@ -0,0 +1,321 @@ +""" +Tests for the State model. + +State tracks the current and last executed calibration job, serving as +the single source of truth for the runner's execution state. +""" + +import pytest + +from qualibrate_runner.config.models import State +from qualibrate_runner.core.models.enums import RunnableType, RunStatusEnum +from qualibrate_runner.core.models.last_run import LastRun + + +class TestStateCreation: + """Tests for creating State instances.""" + + def test_create_fresh_state(self): + """Test creating a fresh State with no execution history.""" + state = State() + + assert state.last_run is None + assert state.run_item is None + + def test_create_with_last_run(self, sample_last_run_running): + """Test creating State with a LastRun.""" + state = State(last_run=sample_last_run_running) + assert state.last_run is not None + assert state.last_run.status == RunStatusEnum.RUNNING + assert state.is_running is True + + +class TestIsRunningProperty: + """Tests for the is_running property.""" + + def test_not_running_when_finished(self, sample_last_run_finished): + """Test is_running is False when status is FINISHED.""" + state = State(last_run=sample_last_run_finished) + assert state.is_running is False + + def test_not_running_when_error(self, sample_last_run_error): + """Test is_running is False when status is ERROR.""" + state = State(last_run=sample_last_run_error) + assert state.is_running is False + + def test_is_running_changes_with_status( + self, aware_datetime, mock_node + ): + """Test is_running property changes as status changes.""" + state = State() + + # Initially not running + assert state.is_running is False + + # Start execution + state.last_run = LastRun( + status=RunStatusEnum.RUNNING, + started_at=aware_datetime, + name="test_node", + idx=-1, + runnable_type=RunnableType.NODE, + ) + state.run_item = mock_node + + assert state.is_running is True + + # Complete execution + state.last_run = LastRun( + status=RunStatusEnum.FINISHED, + started_at=aware_datetime, + completed_at=aware_datetime, + name="test_node", + idx=42, + runnable_type=RunnableType.NODE, + ) + + assert state.is_running is False + + +class TestClearMethod: + """Tests for the clear() method.""" + + def test_clear_finished_state(self, sample_last_run_finished, mock_node): + """Test clearing state after finished execution.""" + state = State.model_construct( + last_run=sample_last_run_finished, run_item=mock_node + ) + + # Should succeed (not running) + state.clear() + + assert state.last_run is None + assert state.run_item is None + + def test_clear_error_state(self, sample_last_run_error, mock_node): + """Test clearing state after error.""" + state = State.model_construct( + last_run=sample_last_run_error, run_item=mock_node + ) + + # Should succeed (not running) + state.clear() + + assert state.last_run is None + assert state.run_item is None + + def test_clear_while_running_raises_error( + self, sample_last_run_running, mock_node + ): + """Test that clearing while running raises RuntimeError.""" + state = State.model_construct( + last_run=sample_last_run_running, run_item=mock_node + ) + + # Should raise RuntimeError + with pytest.raises(RuntimeError, match="Can't clear while item is running"): + state.clear() + + # State should be unchanged + assert state.last_run is not None + assert state.run_item is not None + + +class TestStateLifecycle: + """Tests for the typical State lifecycle during execution.""" + + def test_full_execution_lifecycle_success( + self, aware_datetime, mock_node + ): + """Test state through full successful execution lifecycle.""" + state = State() + + # 1. Fresh state + assert state.last_run is None + assert state.run_item is None + assert state.is_running is False + + # 2. Start execution + state.last_run = LastRun( + status=RunStatusEnum.RUNNING, + started_at=aware_datetime, + name="test_node", + idx=-1, + runnable_type=RunnableType.NODE, + ) + state.run_item = mock_node + + assert state.is_running is True + + # 3. Complete execution + state.last_run = LastRun( + status=RunStatusEnum.FINISHED, + started_at=aware_datetime, + completed_at=aware_datetime, + name="test_node", + idx=42, + runnable_type=RunnableType.NODE, + ) + + assert state.is_running is False + assert state.last_run.status == RunStatusEnum.FINISHED + + # 4. Clear state + state.clear() + + assert state.last_run is None + assert state.run_item is None + + def test_full_execution_lifecycle_error( + self, aware_datetime, mock_node, sample_run_error + ): + """Test state through full error execution lifecycle.""" + state = State() + + # 1. Start execution + state.last_run = LastRun( + status=RunStatusEnum.RUNNING, + started_at=aware_datetime, + name="test_node", + idx=-1, + runnable_type=RunnableType.NODE, + ) + state.run_item = mock_node + + assert state.is_running is True + + # 2. Error occurs + state.last_run = LastRun( + status=RunStatusEnum.ERROR, + started_at=aware_datetime, + completed_at=aware_datetime, + name="test_node", + idx=-1, + runnable_type=RunnableType.NODE, + error=sample_run_error, + ) + + assert state.is_running is False + assert state.last_run.status == RunStatusEnum.ERROR + + # 3. Clear state after error + state.clear() + + assert state.last_run is None + assert state.run_item is None + + def test_multiple_sequential_executions( + self, aware_datetime, mock_node + ): + """Test state through multiple sequential executions.""" + state = State() + + # First execution + state.last_run = LastRun( + status=RunStatusEnum.RUNNING, + started_at=aware_datetime, + name="node_1", + idx=-1, + runnable_type=RunnableType.NODE, + ) + state.run_item = mock_node + assert state.is_running is True + + state.last_run = LastRun( + status=RunStatusEnum.FINISHED, + started_at=aware_datetime, + completed_at=aware_datetime, + name="node_1", + idx=1, + runnable_type=RunnableType.NODE, + ) + assert state.is_running is False + + # Second execution (without clearing) + state.last_run = LastRun( + status=RunStatusEnum.RUNNING, + started_at=aware_datetime, + name="node_2", + idx=-1, + runnable_type=RunnableType.NODE, + ) + assert state.is_running is True + assert state.last_run.name == "node_2" + + +class TestStateArbitraryTypes: + """Tests for arbitrary type support (run_item).""" + + def test_store_node_object(self, state_with_node): + """Test storing a QualibrationNode object in run_item.""" + assert state_with_node.run_item.name == "test_node" + assert state_with_node.run_item.snapshot_idx == 42 + + def test_store_workflow_object(self, state_with_workflow): + """Test storing a workflow (QGraph) object in run_item.""" + assert state_with_workflow.run_item.name == "test_workflow" + assert state_with_workflow.run_item.snapshot_idx == 100 + + def test_run_item_can_be_any_object(self): + """Test that run_item can store any object (arbitrary_types_allowed). + + Note: When using model_construct, Pydantic bypasses validation + which allows storing any object type. + """ + # Create a simple custom object + class CustomRunnable: + def __init__(self): + self.name = "custom" + + custom_obj = CustomRunnable() + state = State.model_construct(run_item=custom_obj) + + assert state.run_item.name == "custom" + + +class TestStateEdgeCases: + """Tests for edge cases and special scenarios.""" + + def test_set_last_run_to_none_explicitly(self, sample_last_run_finished): + """Test explicitly setting last_run to None.""" + state = State(last_run=sample_last_run_finished) + assert state.last_run is not None + + state.last_run = None + assert state.last_run is None + assert state.is_running is False + + def test_set_run_item_to_none_explicitly(self, state_with_node): + """Test explicitly setting run_item to None.""" + assert state_with_node.run_item is not None + + state_with_node.run_item = None + assert state_with_node.run_item is None + + def test_replace_last_run_while_not_running( + self, sample_last_run_finished, sample_last_run_error + ): + """Test replacing last_run when not running.""" + state = State(last_run=sample_last_run_finished) + + # Replace with different LastRun + state.last_run = sample_last_run_error + + assert state.last_run.status == RunStatusEnum.ERROR + + def test_state_with_none_values(self): + """Test State initialized with explicit None values.""" + state = State(last_run=None, run_item=None) + + assert state.last_run is None + assert state.run_item is None + assert state.is_running is False + + def test_is_running_check_is_safe_with_none(self): + """Test that is_running safely handles None last_run.""" + state = State() + + # Should not raise AttributeError + is_running = state.is_running + + assert is_running is False diff --git a/tests/unit/test_models_common.py b/tests/unit/test_models_common.py new file mode 100644 index 0000000..4ddbea5 --- /dev/null +++ b/tests/unit/test_models_common.py @@ -0,0 +1,326 @@ +""" +Tests for common models: RunError and StateUpdate. + +These models are used to capture error information and state changes +during calibration execution. +""" + +import pytest + +from qualibrate_runner.core.models.common import RunError, StateUpdate + + +class TestRunError: + """Tests for the RunError model.""" + + def test_create_with_all_fields(self, sample_traceback): + """Test creating RunError with all required fields.""" + error = RunError( + error_class="ValueError", + message="Test error message", + traceback=sample_traceback, + ) + + assert error.error_class == "ValueError" + assert error.message == "Test error message" + assert error.traceback == sample_traceback + assert len(error.traceback) == 2 + + def test_create_from_exception(self): + """Test creating RunError from a real exception.""" + import traceback as tb + + try: + raise ValueError("Test exception") + except ValueError as e: + error = RunError( + error_class=type(e).__name__, + message=str(e), + traceback=tb.format_tb(e.__traceback__), + ) + + assert error.error_class == "ValueError" + assert error.message == "Test exception" + assert len(error.traceback) > 0 + assert any("raise ValueError" in line for line in error.traceback) + + def test_serialization(self, sample_run_error): + """Test that RunError can be serialized to dict.""" + data = sample_run_error.model_dump() + + assert data["error_class"] == "ValueError" + assert data["message"] == "Invalid data" + assert isinstance(data["traceback"], list) + assert len(data["traceback"]) == 2 + + def test_deserialization(self, sample_traceback): + """Test that RunError can be deserialized from dict.""" + data = { + "error_class": "RuntimeError", + "message": "Something went wrong", + "traceback": sample_traceback, + } + + error = RunError(**data) + + assert error.error_class == "RuntimeError" + assert error.message == "Something went wrong" + assert error.traceback == sample_traceback + + def test_different_error_classes(self): + """Test RunError with various exception types.""" + error_types = [ + "ValueError", + "KeyError", + "RuntimeError", + "AttributeError", + "TypeError", + "CustomCalibrationError", + ] + + for error_type in error_types: + error = RunError( + error_class=error_type, + message=f"Test {error_type}", + traceback=[], + ) + assert error.error_class == error_type + + def test_multiline_error_message(self): + """Test RunError with multiline error message.""" + multiline_message = """First line of error +Second line with details +Third line with more context""" + + error = RunError( + error_class="ValueError", + message=multiline_message, + traceback=[], + ) + + assert error.message == multiline_message + assert "\n" in error.message + + def test_long_traceback(self): + """Test RunError with a long traceback (many frames).""" + long_traceback = [ + f' File "/path/frame_{i}.py", line {i*10}, in func_{i}\n' + for i in range(20) + ] + + error = RunError( + error_class="DeepCallStackError", + message="Error from deep call stack", + traceback=long_traceback, + ) + + assert len(error.traceback) == 20 + + +class TestStateUpdate: + """Tests for the StateUpdate model.""" + + def test_create_with_all_fields(self): + """Test creating StateUpdate with all fields.""" + update = StateUpdate( + key="qubit_0", + attr="resonance_frequency", + old=5.0e9, + new=5.1e9, + updated=True, + ) + + assert update.key == "qubit_0" + assert update.attr == "resonance_frequency" + assert update.old == 5.0e9 + assert update.new == 5.1e9 + assert update.updated is True + + def test_create_with_default_updated_false(self): + """Test that updated defaults to False.""" + update = StateUpdate( + key="qubit_1", + attr="pi_pulse_amplitude", + old=0.5, + new=0.6, + ) + + assert update.updated is False + + def test_string_attribute(self): + """Test StateUpdate with string attribute name.""" + update = StateUpdate( + key="resonator_0", + attr="wiring", + old="port_1", + new="port_2", + updated=True, + ) + + assert update.attr == "wiring" + assert isinstance(update.attr, str) + + def test_integer_attribute_for_list_index(self): + """Test StateUpdate with integer attribute (list index).""" + update = StateUpdate( + key="calibration_array", + attr=3, # Index in list + old=0.1, + new=0.2, + updated=True, + ) + + assert update.attr == 3 + assert isinstance(update.attr, int) + + def test_various_value_types(self): + """Test StateUpdate with different value types.""" + # Float values + update_float = StateUpdate( + key="qubit_0", attr="frequency", old=5.0e9, new=5.1e9 + ) + assert isinstance(update_float.old, float) + + # Integer values + update_int = StateUpdate( + key="qubit_0", attr="num_gates", old=10, new=15 + ) + assert isinstance(update_int.old, int) + + # String values + update_str = StateUpdate( + key="qubit_0", attr="state", old="|0>", new="|1>" + ) + assert isinstance(update_str.old, str) + + # Boolean values + update_bool = StateUpdate( + key="qubit_0", attr="enabled", old=False, new=True + ) + assert isinstance(update_bool.old, bool) + + # None values + update_none = StateUpdate( + key="qubit_0", attr="optional_param", old=None, new=42 + ) + assert update_none.old is None + + # List values + update_list = StateUpdate( + key="qubit_0", attr="calibration_points", old=[1, 2], new=[1, 2, 3] + ) + assert isinstance(update_list.old, list) + + # Dict values + update_dict = StateUpdate( + key="qubit_0", + attr="config", + old={"a": 1}, + new={"a": 1, "b": 2}, + ) + assert isinstance(update_dict.old, dict) + + def test_nested_key_path(self): + """Test StateUpdate with nested key path (dot notation).""" + update = StateUpdate( + key="qubit_0.readout.resonator", + attr="frequency", + old=7.0e9, + new=7.1e9, + updated=True, + ) + + assert update.key == "qubit_0.readout.resonator" + assert "." in update.key + + def test_serialization(self, sample_state_update): + """Test that StateUpdate can be serialized to dict.""" + data = sample_state_update.model_dump() + + assert data["key"] == "qubit_0" + assert data["attr"] == "resonance_frequency" + assert data["old"] == 5.0e9 + assert data["new"] == 5.1e9 + assert data["updated"] is True + + def test_deserialization(self): + """Test that StateUpdate can be deserialized from dict.""" + data = { + "key": "qubit_2", + "attr": "T1", + "old": 50e-6, + "new": 55e-6, + "updated": True, + } + + update = StateUpdate(**data) + + assert update.key == "qubit_2" + assert update.attr == "T1" + assert update.old == 50e-6 + assert update.new == 55e-6 + assert update.updated is True + + def test_no_change_update(self): + """Test StateUpdate where old and new values are the same.""" + update = StateUpdate( + key="qubit_0", + attr="frequency", + old=5.0e9, + new=5.0e9, + updated=False, + ) + + # Values can be the same, it's valid + assert update.old == update.new + assert update.updated is False + + def test_update_to_none(self): + """Test StateUpdate where new value is None (clearing a value).""" + update = StateUpdate( + key="qubit_0", + attr="optional_correction", + old=0.05, + new=None, + updated=True, + ) + + assert update.old == 0.05 + assert update.new is None + + def test_update_from_none(self): + """Test StateUpdate where old value is None (setting initial value).""" + update = StateUpdate( + key="qubit_0", + attr="new_parameter", + old=None, + new=0.1, + updated=True, + ) + + assert update.old is None + assert update.new == 0.1 + + def test_complex_number_values(self): + """Test StateUpdate with complex number values.""" + update = StateUpdate( + key="mixer_0", + attr="iq_imbalance", + old=complex(1.0, 0.1), + new=complex(1.0, 0.05), + updated=True, + ) + + assert isinstance(update.old, complex) + assert isinstance(update.new, complex) + + def test_very_long_key(self): + """Test StateUpdate with very long nested key path.""" + long_key = "system.rack_1.chassis_2.module_3.channel_4.qubit_5.subsystem_6" + + update = StateUpdate( + key=long_key, attr="parameter", old=0, new=1, updated=True + ) + + assert update.key == long_key + assert update.key.count(".") == 6 diff --git a/tests/unit/test_models_last_run.py b/tests/unit/test_models_last_run.py new file mode 100644 index 0000000..013472b --- /dev/null +++ b/tests/unit/test_models_last_run.py @@ -0,0 +1,485 @@ +""" +Tests for the LastRun model. + +LastRun tracks complete execution information for calibration nodes and +workflows, including status, timing, results, and errors. +""" + +from datetime import datetime, timedelta, timezone +from unittest.mock import Mock + +import pytest +from freezegun import freeze_time + +from qualibrate_runner.core.models.common import RunError, StateUpdate +from qualibrate_runner.core.models.enums import RunnableType, RunStatusEnum +from qualibrate_runner.core.models.last_run import LastRun + + +class TestLastRunCreation: + """Tests for creating LastRun instances.""" + + def test_create_running_node(self, aware_datetime): + """Test creating LastRun for a running node.""" + last_run = LastRun( + status=RunStatusEnum.RUNNING, + started_at=aware_datetime, + name="test_node", + idx=-1, + runnable_type=RunnableType.NODE, + ) + + assert last_run.status == RunStatusEnum.RUNNING + assert last_run.started_at == aware_datetime + assert last_run.completed_at is None + assert last_run.name == "test_node" + assert last_run.idx == -1 + assert last_run.runnable_type == RunnableType.NODE + assert last_run.passed_parameters == {} + assert last_run.run_result is None + assert last_run.error is None + + def test_create_finished_node(self, aware_datetime, later_datetime): + """Test creating LastRun for a finished node.""" + # Use model_construct to bypass validation for mock run_result + last_run = LastRun.model_construct( + status=RunStatusEnum.FINISHED, + started_at=aware_datetime, + completed_at=later_datetime, + name="test_node", + idx=42, + runnable_type=RunnableType.NODE, + run_result=Mock(success=True), + ) + + assert last_run.status == RunStatusEnum.FINISHED + assert last_run.completed_at == later_datetime + assert last_run.idx == 42 + assert last_run.run_result is not None + assert last_run.error is None + + def test_create_error_node( + self, aware_datetime, later_datetime, sample_run_error + ): + """Test creating LastRun for a failed node.""" + last_run = LastRun( + status=RunStatusEnum.ERROR, + started_at=aware_datetime, + completed_at=later_datetime, + name="test_node", + idx=-1, + runnable_type=RunnableType.NODE, + error=sample_run_error, + ) + + assert last_run.status == RunStatusEnum.ERROR + assert last_run.error is not None + assert last_run.error.error_class == "ValueError" + assert last_run.run_result is None + + def test_create_running_workflow(self, aware_datetime): + """Test creating LastRun for a running workflow.""" + last_run = LastRun( + status=RunStatusEnum.RUNNING, + started_at=aware_datetime, + name="test_workflow", + idx=-1, + runnable_type=RunnableType.GRAPH, + ) + + assert last_run.runnable_type == RunnableType.GRAPH + assert last_run.name == "test_workflow" + + def test_create_with_passed_parameters(self, aware_datetime): + """Test creating LastRun with passed parameters.""" + params = { + "amplitude": 0.5, + "frequency": 5.0e9, + "num_averages": 1000, + } + + last_run = LastRun( + status=RunStatusEnum.RUNNING, + started_at=aware_datetime, + name="test_node", + idx=-1, + runnable_type=RunnableType.NODE, + passed_parameters=params, + ) + + assert last_run.passed_parameters == params + assert last_run.passed_parameters["amplitude"] == 0.5 + + def test_create_with_state_updates(self, aware_datetime): + """Test creating LastRun with state updates.""" + state_updates = { + "qubit_0.frequency": StateUpdate( + key="qubit_0", + attr="frequency", + old=5.0e9, + new=5.1e9, + updated=True, + ) + } + + last_run = LastRun( + status=RunStatusEnum.FINISHED, + started_at=aware_datetime, + completed_at=aware_datetime, + name="test_node", + idx=42, + runnable_type=RunnableType.NODE, + state_updates=state_updates, + ) + + assert len(last_run.state_updates) == 1 + assert "qubit_0.frequency" in last_run.state_updates + + +class TestLastRunDuration: + """Tests for the run_duration computed field.""" + + def test_duration_for_completed_run( + self, aware_datetime, later_datetime + ): + """Test run_duration calculation for completed run.""" + # later_datetime is 5.5 seconds after aware_datetime + last_run = LastRun( + status=RunStatusEnum.FINISHED, + started_at=aware_datetime, + completed_at=later_datetime, + name="test_node", + idx=42, + runnable_type=RunnableType.NODE, + ) + + # Should be 5.5 seconds + assert last_run.run_duration == 5.5 + + def test_duration_for_running_execution(self): + """Test run_duration for still-running execution (live duration).""" + # Started a moment ago + started = datetime.now(timezone.utc) - timedelta(milliseconds=100) + + last_run = LastRun( + status=RunStatusEnum.RUNNING, + started_at=started, + name="test_node", + idx=-1, + runnable_type=RunnableType.NODE, + ) + + # Duration should be positive and small (less than 1 second) + assert 0 < last_run.run_duration < 1.0 + + def test_duration_for_very_short_run(self, aware_datetime): + """Test duration for very short execution (milliseconds).""" + completed = aware_datetime + timedelta(milliseconds=150) + + last_run = LastRun( + status=RunStatusEnum.FINISHED, + started_at=aware_datetime, + completed_at=completed, + name="test_node", + idx=42, + runnable_type=RunnableType.NODE, + ) + + assert last_run.run_duration == 0.15 + + def test_duration_for_long_run(self, aware_datetime): + """Test duration for long execution (minutes).""" + # 5 minutes and 30 seconds + completed = aware_datetime + timedelta(minutes=5, seconds=30) + + last_run = LastRun( + status=RunStatusEnum.FINISHED, + started_at=aware_datetime, + completed_at=completed, + name="test_node", + idx=42, + runnable_type=RunnableType.NODE, + ) + + assert last_run.run_duration == 330.0 + + def test_duration_recalculated_on_access(self): + """Test that duration is recalculated each time for running jobs.""" + import time + + # Start a moment ago + started = datetime.now(timezone.utc) - timedelta(milliseconds=50) + + last_run = LastRun( + status=RunStatusEnum.RUNNING, + started_at=started, + name="test_node", + idx=-1, + runnable_type=RunnableType.NODE, + ) + + # First access + duration1 = last_run.run_duration + assert duration1 > 0 + + # Wait a bit + time.sleep(0.1) + + # Second access should show increased duration + duration2 = last_run.run_duration + assert duration2 > duration1 + + +class TestLastRunSerialization: + """Tests for LastRun serialization and deserialization.""" + + def test_serialize_running_node(self, sample_last_run_running): + """Test serializing a running node to dict.""" + data = sample_last_run_running.model_dump() + + assert data["status"] == RunStatusEnum.RUNNING + assert data["name"] == "test_node" + assert data["idx"] == -1 + assert data["runnable_type"] == RunnableType.NODE + assert data["completed_at"] is None + assert data["run_result"] is None + assert data["error"] is None + + def test_serialize_finished_node(self, sample_last_run_finished): + """Test serializing a finished node to dict.""" + data = sample_last_run_finished.model_dump() + + assert data["status"] == RunStatusEnum.FINISHED + assert data["completed_at"] is not None + assert data["idx"] == 42 + assert data["run_result"] is not None + assert "amplitude" in data["passed_parameters"] + + def test_serialize_error_node(self, sample_last_run_error): + """Test serializing a failed node to dict.""" + data = sample_last_run_error.model_dump() + + assert data["status"] == RunStatusEnum.ERROR + assert data["error"] is not None + assert data["error"]["error_class"] == "ValueError" + assert data["error"]["message"] == "Invalid data" + + def test_deserialize_from_dict(self, aware_datetime): + """Test deserializing LastRun from dict.""" + data = { + "status": RunStatusEnum.RUNNING, + "started_at": aware_datetime.isoformat(), + "completed_at": None, + "name": "test_node", + "idx": -1, + "runnable_type": RunnableType.NODE, + "passed_parameters": {}, + "run_result": None, + "state_updates": {}, + "error": None, + } + + last_run = LastRun(**data) + + assert last_run.status == RunStatusEnum.RUNNING + assert last_run.name == "test_node" + + +class TestLastRunStatusTransitions: + """Tests for different status values and transitions.""" + + def test_all_status_values(self, aware_datetime): + """Test that all status enum values can be used.""" + for status in RunStatusEnum: + last_run = LastRun( + status=status, + started_at=aware_datetime, + completed_at=aware_datetime if status != RunStatusEnum.RUNNING else None, + name="test_node", + idx=-1, + runnable_type=RunnableType.NODE, + ) + assert last_run.status == status + + def test_running_to_finished_transition(self, aware_datetime): + """Test updating from RUNNING to FINISHED status.""" + # Initial RUNNING state + last_run = LastRun( + status=RunStatusEnum.RUNNING, + started_at=aware_datetime, + name="test_node", + idx=-1, + runnable_type=RunnableType.NODE, + ) + + # Simulate update to FINISHED (use model_construct for mock run_result) + completed = aware_datetime + timedelta(seconds=5) + updated_run = LastRun.model_construct( + status=RunStatusEnum.FINISHED, + started_at=last_run.started_at, + completed_at=completed, + name=last_run.name, + idx=42, + runnable_type=last_run.runnable_type, + run_result=Mock(success=True), + ) + + assert updated_run.status == RunStatusEnum.FINISHED + assert updated_run.completed_at is not None + assert updated_run.idx == 42 + + def test_running_to_error_transition( + self, aware_datetime, sample_run_error + ): + """Test updating from RUNNING to ERROR status.""" + # Initial RUNNING state + last_run = LastRun( + status=RunStatusEnum.RUNNING, + started_at=aware_datetime, + name="test_node", + idx=-1, + runnable_type=RunnableType.NODE, + ) + + # Simulate update to ERROR + completed = aware_datetime + timedelta(seconds=2) + updated_run = LastRun( + status=RunStatusEnum.ERROR, + started_at=last_run.started_at, + completed_at=completed, + name=last_run.name, + idx=-1, + runnable_type=last_run.runnable_type, + error=sample_run_error, + ) + + assert updated_run.status == RunStatusEnum.ERROR + assert updated_run.error is not None + assert updated_run.idx == -1 + + +class TestLastRunEdgeCases: + """Tests for edge cases and special scenarios.""" + + def test_idx_negative_one_for_no_snapshot(self, aware_datetime): + """Test that idx=-1 indicates no snapshot was created.""" + last_run = LastRun( + status=RunStatusEnum.RUNNING, + started_at=aware_datetime, + name="test_node", + idx=-1, + runnable_type=RunnableType.NODE, + ) + + assert last_run.idx == -1 + + def test_empty_passed_parameters(self, aware_datetime): + """Test with empty passed_parameters dict.""" + last_run = LastRun( + status=RunStatusEnum.RUNNING, + started_at=aware_datetime, + name="test_node", + idx=-1, + runnable_type=RunnableType.NODE, + passed_parameters={}, + ) + + assert last_run.passed_parameters == {} + assert len(last_run.passed_parameters) == 0 + + def test_empty_state_updates(self, aware_datetime): + """Test with empty state_updates dict.""" + last_run = LastRun( + status=RunStatusEnum.FINISHED, + started_at=aware_datetime, + completed_at=aware_datetime, + name="test_node", + idx=42, + runnable_type=RunnableType.NODE, + state_updates={}, + ) + + assert last_run.state_updates == {} + assert len(last_run.state_updates) == 0 + + def test_node_vs_graph_runnable_types(self, aware_datetime): + """Test both NODE and GRAPH runnable types.""" + node_run = LastRun( + status=RunStatusEnum.RUNNING, + started_at=aware_datetime, + name="test_node", + idx=-1, + runnable_type=RunnableType.NODE, + ) + + graph_run = LastRun( + status=RunStatusEnum.RUNNING, + started_at=aware_datetime, + name="test_workflow", + idx=-1, + runnable_type=RunnableType.GRAPH, + ) + + assert node_run.runnable_type == RunnableType.NODE + assert graph_run.runnable_type == RunnableType.GRAPH + + def test_very_long_node_name(self, aware_datetime): + """Test with very long node name.""" + long_name = "very_long_node_name_" * 20 + + last_run = LastRun( + status=RunStatusEnum.RUNNING, + started_at=aware_datetime, + name=long_name, + idx=-1, + runnable_type=RunnableType.NODE, + ) + + assert last_run.name == long_name + assert len(last_run.name) > 300 + + def test_many_state_updates(self, aware_datetime): + """Test with many state updates.""" + state_updates = { + f"qubit_{i}.frequency": StateUpdate( + key=f"qubit_{i}", + attr="frequency", + old=5.0e9 + i * 0.1e9, + new=5.1e9 + i * 0.1e9, + updated=True, + ) + for i in range(50) + } + + last_run = LastRun( + status=RunStatusEnum.FINISHED, + started_at=aware_datetime, + completed_at=aware_datetime, + name="test_node", + idx=42, + runnable_type=RunnableType.NODE, + state_updates=state_updates, + ) + + assert len(last_run.state_updates) == 50 + + def test_complex_passed_parameters(self, aware_datetime): + """Test with complex nested passed_parameters.""" + params = { + "basic": 42, + "nested": {"a": 1, "b": {"c": 2}}, + "list": [1, 2, 3], + "mixed": [{"x": 1}, {"y": 2}], + } + + last_run = LastRun( + status=RunStatusEnum.RUNNING, + started_at=aware_datetime, + name="test_node", + idx=-1, + runnable_type=RunnableType.NODE, + passed_parameters=params, + ) + + assert last_run.passed_parameters["nested"]["b"]["c"] == 2 + assert len(last_run.passed_parameters["list"]) == 3 From 8e17bceea271109e6c436577bf3a2573b4c85b30 Mon Sep 17 00:00:00 2001 From: Jonatan Kutchinsky Date: Thu, 6 Nov 2025 10:46:09 +0100 Subject: [PATCH 02/26] Unit tests for run_job.py + small fix for test_models_last_run.py --- tests/conftest.py | 53 +++- tests/unit/test_models_last_run.py | 2 +- tests/unit/test_run_job.py | 450 +++++++++++++++++++++++++++++ 3 files changed, 499 insertions(+), 6 deletions(-) create mode 100644 tests/unit/test_run_job.py diff --git a/tests/conftest.py b/tests/conftest.py index 606d5ce..fd2543e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -77,8 +77,7 @@ def sample_last_run_running(aware_datetime): @pytest.fixture def sample_last_run_finished(aware_datetime, later_datetime): """Provide a LastRun instance with FINISHED status.""" - # Use model_construct to bypass validation for mock run_result - return LastRun.model_construct( + return LastRun( status=RunStatusEnum.FINISHED, started_at=aware_datetime, completed_at=later_datetime, @@ -86,7 +85,7 @@ def sample_last_run_finished(aware_datetime, later_datetime): idx=42, runnable_type=RunnableType.NODE, passed_parameters={"amplitude": 0.5}, - run_result=Mock(success=True), + run_result=None, # Must be None or a real RunSummary for validation ) @@ -121,7 +120,7 @@ def mock_node(): node = Mock() node.name = "test_node" node.snapshot_idx = 42 - node.run_summary = Mock(success=True) + node.run_summary = None node.state_updates = {} return node @@ -136,7 +135,7 @@ def mock_workflow(): workflow = Mock() workflow.name = "test_workflow" workflow.snapshot_idx = 100 - workflow.run_summary = Mock(success=True) + workflow.run_summary = None return workflow @@ -150,3 +149,47 @@ def state_with_node(mock_node): def state_with_workflow(mock_workflow): """Provide a State with a mock workflow (bypassing validation).""" return State.model_construct(run_item=mock_workflow) + + +# Run job fixtures + + +@pytest.fixture +def mock_library(mock_workflow): + """Provide a mock QualibrationLibrary.""" + library = Mock() + library.graphs = {"test_workflow": mock_workflow} + return library + + +@pytest.fixture +def sample_parameters_class(): + """Provide a sample Pydantic model for parameter validation.""" + from pydantic import BaseModel, Field + + class TestParameters(BaseModel): + amplitude: float = Field(ge=0.0, le=1.0) + frequency: float = Field(gt=0.0) + num_averages: int = Field(ge=1, default=100) + + return TestParameters + + +@pytest.fixture +def sample_workflow_parameters_class(): + """Provide a sample workflow parameters class with nodes and parameters.""" + from pydantic import BaseModel, Field + + class NodeParams(BaseModel): + node1: dict = Field(default_factory=dict) + node2: dict = Field(default_factory=dict) + + class WorkflowParams(BaseModel): + frequency: float = Field(gt=0.0) + amplitude: float = Field(ge=0.0, le=1.0, default=0.5) + + class FullParams(BaseModel): + nodes: NodeParams = Field(default_factory=NodeParams) + parameters: WorkflowParams + + return FullParams diff --git a/tests/unit/test_models_last_run.py b/tests/unit/test_models_last_run.py index 013472b..54dc034 100644 --- a/tests/unit/test_models_last_run.py +++ b/tests/unit/test_models_last_run.py @@ -252,7 +252,7 @@ def test_serialize_finished_node(self, sample_last_run_finished): assert data["status"] == RunStatusEnum.FINISHED assert data["completed_at"] is not None assert data["idx"] == 42 - assert data["run_result"] is not None + assert "run_result" in data assert "amplitude" in data["passed_parameters"] def test_serialize_error_node(self, sample_last_run_error): diff --git a/tests/unit/test_run_job.py b/tests/unit/test_run_job.py new file mode 100644 index 0000000..7769391 --- /dev/null +++ b/tests/unit/test_run_job.py @@ -0,0 +1,450 @@ +""" +Tests for run_job.py orchestration layer (non-interactive mode only). +""" + +from unittest.mock import Mock, patch + +import pytest +from fastapi import HTTPException +from pydantic import BaseModel, Field, ValidationError + +from qualibrate_runner.config.models import State +from qualibrate_runner.core.models.enums import RunnableType, RunStatusEnum +from qualibrate_runner.core.run_job import ( + get_active_library_or_error, + run_node, + run_workflow, + validate_input_parameters, +) + + +class TestValidateInputParameters: + """Tests for validate_input_parameters function.""" + + def test_valid_parameters_pass_validation(self, sample_parameters_class): + """Test that valid parameters pass validation.""" + params = {"amplitude": 0.5, "frequency": 5.0e9, "num_averages": 100} + + result = validate_input_parameters(sample_parameters_class, params) + + assert result.amplitude == 0.5 + assert result.frequency == 5.0e9 + assert result.num_averages == 100 + + def test_valid_parameters_with_defaults(self, sample_parameters_class): + """Test that validation works with default values.""" + params = {"amplitude": 0.5, "frequency": 5.0e9} + + result = validate_input_parameters(sample_parameters_class, params) + + assert result.amplitude == 0.5 + assert result.frequency == 5.0e9 + assert result.num_averages == 100 # Default value + + def test_invalid_parameter_raises_http_exception(self, sample_parameters_class): + """Test that invalid parameters raise HTTPException with 422 status.""" + params = {"amplitude": 1.5, "frequency": 5.0e9} # amplitude > 1.0 + + with pytest.raises(HTTPException) as exc_info: + validate_input_parameters(sample_parameters_class, params) + + assert exc_info.value.status_code == 422 + assert exc_info.value.detail is not None + + def test_missing_required_field_raises_http_exception(self, sample_parameters_class): + """Test that missing required field raises HTTPException.""" + params = {"amplitude": 0.5} # Missing required 'frequency' + + with pytest.raises(HTTPException) as exc_info: + validate_input_parameters(sample_parameters_class, params) + + assert exc_info.value.status_code == 422 + # Check that error mentions the missing field + errors = exc_info.value.detail + assert any("frequency" in str(error).lower() for error in errors) + + def test_type_coercion_works(self, sample_parameters_class): + """Test that Pydantic type coercion works as expected.""" + params = { + "amplitude": "0.5", # String that can be coerced to float + "frequency": 5.0e9, + "num_averages": "50", # String that can be coerced to int + } + + result = validate_input_parameters(sample_parameters_class, params) + + assert result.amplitude == 0.5 + assert result.frequency == 5.0e9 + assert result.num_averages == 50 + + +class TestGetActiveLibraryOrError: + """Tests for get_active_library_or_error function.""" + + @patch("qualibrate_runner.core.run_job.QualibrationLibrary") + def test_returns_library_when_exists(self, mock_lib_class, mock_library): + """Test that function returns library when one exists.""" + mock_lib_class.get_active_library.return_value = mock_library + + result = get_active_library_or_error() + + mock_lib_class.get_active_library.assert_called_once_with(create=False) + assert result is mock_library + + @patch("qualibrate_runner.core.run_job.QualibrationLibrary") + def test_raises_exception_when_no_library(self, mock_lib_class): + """Test that function raises exception when no library exists.""" + mock_lib_class.get_active_library.side_effect = RuntimeError("No active library") + + with pytest.raises(RuntimeError, match="No active library"): + get_active_library_or_error() + + mock_lib_class.get_active_library.assert_called_once_with(create=False) + + +class TestRunNodeHappyPath: + """Tests for run_node function - happy path scenarios.""" + + def test_sets_run_item_to_node(self, mock_node, fresh_state): + """Test that state.run_item is set to the node.""" + mock_node.run = Mock(return_value=None) + + run_node(mock_node, {}, fresh_state) + + assert fresh_state.run_item is mock_node + + def test_creates_last_run_with_running_status(self, mock_node, fresh_state): + """Test that initial LastRun has RUNNING status.""" + # Track state during execution + last_run_during_execution = None + + def capture_state(*args, **kwargs): + nonlocal last_run_during_execution + last_run_during_execution = fresh_state.last_run + + mock_node.run = Mock(side_effect=capture_state) + + run_node(mock_node, {}, fresh_state) + + # During execution, status should have been RUNNING + assert last_run_during_execution.status == RunStatusEnum.RUNNING + assert last_run_during_execution.name == "test_node" + + def test_updates_state_with_finished_status(self, mock_node, fresh_state): + """Test that final state has FINISHED status on success.""" + mock_node.run = Mock(return_value=None) + + run_node(mock_node, {}, fresh_state) + + assert fresh_state.last_run.status == RunStatusEnum.FINISHED + assert fresh_state.last_run.error is None + + def test_captures_snapshot_idx(self, mock_node, fresh_state): + """Test that snapshot_idx is captured from node.""" + mock_node.run = Mock(return_value=None) + mock_node.snapshot_idx = 42 + + run_node(mock_node, {}, fresh_state) + + assert fresh_state.last_run.idx == 42 + + def test_captures_run_summary(self, mock_node, fresh_state): + """Test that run_summary is captured from node.""" + mock_node.run = Mock(return_value=None) + # run_summary is None by default in fixture + + run_node(mock_node, {}, fresh_state) + + # run_result should be None since run_summary is None + assert fresh_state.last_run.run_result is None + + def test_captures_state_updates(self, mock_node, fresh_state, sample_state_update): + """Test that state_updates are captured from node.""" + from qualibrate_runner.core.models.common import StateUpdate + + mock_node.run = Mock(return_value=None) + state_updates = {"qubit_0_frequency": sample_state_update} + mock_node.state_updates = state_updates + + run_node(mock_node, {}, fresh_state) + + assert fresh_state.last_run.state_updates == state_updates + + def test_sets_completed_at_timestamp(self, mock_node, fresh_state): + """Test that completed_at is set after execution.""" + mock_node.run = Mock(return_value=None) + + run_node(mock_node, {}, fresh_state) + + assert fresh_state.last_run.completed_at is not None + assert (fresh_state.last_run.completed_at > fresh_state.last_run.started_at) + + +class TestRunNodeErrorPath: + """Tests for run_node function - error scenarios.""" + + def test_captures_exception_in_state(self, mock_node, fresh_state): + """Test that exceptions are captured in state.last_run.error.""" + mock_node.run = Mock(side_effect=ValueError("Test error")) + + with pytest.raises(ValueError, match="Test error"): + run_node(mock_node, {}, fresh_state) + + assert fresh_state.last_run.status == RunStatusEnum.ERROR + assert fresh_state.last_run.error is not None + assert fresh_state.last_run.error.error_class == "ValueError" + assert fresh_state.last_run.error.message == "Test error" + + def test_captures_traceback(self, mock_node, fresh_state): + """Test that full traceback is captured.""" + mock_node.run = Mock(side_effect=RuntimeError("Node failed")) + + with pytest.raises(RuntimeError): + run_node(mock_node, {}, fresh_state) + + assert len(fresh_state.last_run.error.traceback) > 0 + # Traceback should be a list of strings + assert all(isinstance(line, str) for line in fresh_state.last_run.error.traceback) + + def test_re_raises_original_exception(self, mock_node, fresh_state): + """Test that the original exception is re-raised.""" + original_error = ValueError("Original error") + mock_node.run = Mock(side_effect=original_error) + + with pytest.raises(ValueError) as exc_info: + run_node(mock_node, {}, fresh_state) + + # Should be the same exception object + assert exc_info.value is original_error + + def test_state_updated_even_on_error(self, mock_node, fresh_state): + """Test that state is updated in finally block even on error.""" + mock_node.run = Mock(side_effect=RuntimeError("Error")) + + with pytest.raises(RuntimeError): + run_node(mock_node, {}, fresh_state) + + # State should still be updated with error info + assert fresh_state.last_run.status == RunStatusEnum.ERROR + assert fresh_state.last_run.completed_at is not None + assert fresh_state.last_run.name == "test_node" + + def test_snapshot_idx_negative_one_on_error(self, mock_node, fresh_state): + """Test that snapshot_idx remains -1 on error.""" + mock_node.run = Mock(side_effect=ValueError("Error")) + mock_node.snapshot_idx = 42 # Should not be used on error + + with pytest.raises(ValueError): + run_node(mock_node, {}, fresh_state) + + assert fresh_state.last_run.idx == -1 + + +class TestRunWorkflowHappyPath: + """Tests for run_workflow function - happy path scenarios.""" + + @patch("qualibrate_runner.core.run_job.get_active_library_or_error") + def test_retrieves_fresh_workflow_from_library(self, mock_get_library, mock_library, mock_workflow, fresh_state): + """Test that workflow is retrieved fresh from library.""" + mock_get_library.return_value = mock_library + + # Create a fresh copy mock + fresh_workflow = Mock() + fresh_workflow.name = "test_workflow" + fresh_workflow.snapshot_idx = 100 + fresh_workflow.run_summary = None # Must be None or real RunSummary + fresh_workflow.run = Mock(return_value=None) + + # Setup full_parameters_class + mock_params = Mock() + mock_params.nodes.model_dump.return_value = {} + mock_params.parameters.model_dump.return_value = {"frequency": 5.0e9} + fresh_workflow.full_parameters_class.return_value = mock_params + + mock_library.graphs["test_workflow"] = fresh_workflow + + run_workflow(mock_workflow, {"parameters": {"frequency": 5.0e9}}, fresh_state) + + # Should retrieve fresh workflow from library + mock_get_library.assert_called_once() + fresh_workflow.run.assert_called_once() + + @patch("qualibrate_runner.core.run_job.get_active_library_or_error") + def test_creates_last_run_with_graph_type(self, mock_get_library, mock_library, mock_workflow, fresh_state): + """Test that LastRun is created with GRAPH runnable_type.""" + mock_get_library.return_value = mock_library + + # Setup workflow + mock_workflow.run = Mock(return_value=None) + mock_params = Mock() + mock_params.nodes.model_dump.return_value = {} + mock_params.parameters.model_dump.return_value = {} + mock_workflow.full_parameters_class.return_value = mock_params + + # Track state during execution + last_run_during_execution = None + + def capture_state(*args, **kwargs): + nonlocal last_run_during_execution + last_run_during_execution = fresh_state.last_run + + mock_workflow.run.side_effect = capture_state + + run_workflow(mock_workflow, {}, fresh_state) + + assert (last_run_during_execution.runnable_type == RunnableType.GRAPH) + + @patch("qualibrate_runner.core.run_job.get_active_library_or_error") + def test_validates_parameters_with_full_parameters_class(self, mock_get_library, mock_library, mock_workflow, + fresh_state): + """Test that parameters are validated using full_parameters_class.""" + mock_get_library.return_value = mock_library + + # Setup workflow + mock_workflow.run = Mock(return_value=None) + mock_params = Mock() + mock_params.nodes.model_dump.return_value = {} + mock_params.parameters.model_dump.return_value = {"frequency": 5.0e9} + mock_workflow.full_parameters_class.return_value = mock_params + + input_params = {"parameters": {"frequency": 5.0e9}, "nodes": {}} + + run_workflow(mock_workflow, input_params, fresh_state) + + # Should call full_parameters_class with input parameters + mock_workflow.full_parameters_class.assert_called_once_with(**input_params) + + @patch("qualibrate_runner.core.run_job.get_active_library_or_error") + def test_splits_parameters_into_nodes_and_params(self, mock_get_library, mock_library, mock_workflow, fresh_state): + """Test that parameters are split into nodes and parameters.""" + mock_get_library.return_value = mock_library + + # Setup workflow + mock_workflow.run = Mock(return_value=None) + mock_params = Mock() + mock_params.nodes.model_dump.return_value = {"node1": {"amplitude": 0.5}} + mock_params.parameters.model_dump.return_value = {"frequency": 5.0e9} + mock_workflow.full_parameters_class.return_value = mock_params + + input_params = { + "parameters": {"frequency": 5.0e9}, + "nodes": {"node1": {"amplitude": 0.5}}, + } + + run_workflow(mock_workflow, input_params, fresh_state) + + # Should call workflow.run with separated params + mock_workflow.run.assert_called_once_with(nodes={"node1": {"amplitude": 0.5}}, frequency=5.0e9) + + @patch("qualibrate_runner.core.run_job.get_active_library_or_error") + def test_updates_state_with_finished_status(self, mock_get_library, mock_library, mock_workflow, fresh_state): + """Test that final state has FINISHED status on success.""" + mock_get_library.return_value = mock_library + + # Setup workflow + mock_workflow.run = Mock(return_value=None) + mock_params = Mock() + mock_params.nodes.model_dump.return_value = {} + mock_params.parameters.model_dump.return_value = {} + mock_workflow.full_parameters_class.return_value = mock_params + + run_workflow(mock_workflow, {}, fresh_state) + + assert fresh_state.last_run.status == RunStatusEnum.FINISHED + assert fresh_state.last_run.error is None + + @patch("qualibrate_runner.core.run_job.get_active_library_or_error") + def test_captures_workflow_snapshot_idx(self, mock_get_library, mock_library, mock_workflow, fresh_state): + """Test that snapshot_idx is captured from workflow.""" + mock_get_library.return_value = mock_library + + # Setup workflow + mock_workflow.run = Mock(return_value=None) + mock_workflow.snapshot_idx = 100 + mock_params = Mock() + mock_params.nodes.model_dump.return_value = {} + mock_params.parameters.model_dump.return_value = {} + mock_workflow.full_parameters_class.return_value = mock_params + + run_workflow(mock_workflow, {}, fresh_state) + + assert fresh_state.last_run.idx == 100 + + +class TestRunWorkflowErrorPath: + """Tests for run_workflow function - error scenarios.""" + + @patch("qualibrate_runner.core.run_job.get_active_library_or_error") + def test_library_not_found_raises_exception(self, mock_get_library, mock_workflow, fresh_state): + """Test that missing library raises exception.""" + mock_get_library.side_effect = RuntimeError("No active library") + + with pytest.raises(RuntimeError, match="No active library"): + run_workflow(mock_workflow, {}, fresh_state) + + assert fresh_state.last_run.status == RunStatusEnum.ERROR + + @patch("qualibrate_runner.core.run_job.get_active_library_or_error") + def test_workflow_not_in_library_raises_exception(self, mock_get_library, mock_library, mock_workflow, fresh_state): + """Test that missing workflow in library raises exception.""" + mock_get_library.return_value = mock_library + mock_library.graphs = {} # Empty, no workflows + + with pytest.raises(KeyError): + run_workflow(mock_workflow, {}, fresh_state) + + assert fresh_state.last_run.status == RunStatusEnum.ERROR + + @patch("qualibrate_runner.core.run_job.get_active_library_or_error") + def test_parameter_validation_errors_raise_exception(self, mock_get_library, mock_library, mock_workflow, + fresh_state): + """Test that parameter validation errors are raised.""" + mock_get_library.return_value = mock_library + + # Setup workflow to raise validation error + mock_workflow.full_parameters_class.side_effect = ValidationError.from_exception_data( + "test", + [{"type": "missing", "loc": ("frequency",), "msg": "Field required", "input": {}}], + ) + + with pytest.raises(ValidationError): + run_workflow(mock_workflow, {}, fresh_state) + + assert fresh_state.last_run.status == RunStatusEnum.ERROR + + @patch("qualibrate_runner.core.run_job.get_active_library_or_error") + def test_runtime_errors_captured_and_reraised(self, mock_get_library, mock_library, mock_workflow, fresh_state): + """Test that runtime errors during workflow.run are captured.""" + mock_get_library.return_value = mock_library + + # Setup workflow to raise error during run + mock_workflow.run = Mock(side_effect=RuntimeError("Workflow failed")) + mock_params = Mock() + mock_params.nodes.model_dump.return_value = {} + mock_params.parameters.model_dump.return_value = {} + mock_workflow.full_parameters_class.return_value = mock_params + + with pytest.raises(RuntimeError, match="Workflow failed"): + run_workflow(mock_workflow, {}, fresh_state) + + assert fresh_state.last_run.status == RunStatusEnum.ERROR + assert fresh_state.last_run.error.error_class == "RuntimeError" + + @patch("qualibrate_runner.core.run_job.get_active_library_or_error") + def test_state_updated_even_on_error(self, mock_get_library, mock_library, mock_workflow, fresh_state): + """Test that state is updated in finally block even on error.""" + mock_get_library.return_value = mock_library + + # Setup workflow to raise error + mock_workflow.run = Mock(side_effect=ValueError("Error")) + mock_params = Mock() + mock_params.nodes.model_dump.return_value = {} + mock_params.parameters.model_dump.return_value = {} + mock_workflow.full_parameters_class.return_value = mock_params + + with pytest.raises(ValueError): + run_workflow(mock_workflow, {}, fresh_state) + + # State should still be updated + assert fresh_state.last_run.status == RunStatusEnum.ERROR + assert fresh_state.last_run.completed_at is not None From f6f6a33fbc6e858e8a213b4b38b549b71da468b1 Mon Sep 17 00:00:00 2001 From: Jonatan Kutchinsky Date: Thu, 6 Nov 2025 11:04:32 +0100 Subject: [PATCH 03/26] Added clarifying comments to config/models.py, core/run_job.py, core/models/common.py and core/models/last_run.py --- qualibrate_runner/config/models.py | 43 ++++++ qualibrate_runner/core/models/common.py | 48 ++++++- qualibrate_runner/core/models/last_run.py | 28 +++- qualibrate_runner/core/run_job.py | 161 +++++++++++++++++++++- 4 files changed, 273 insertions(+), 7 deletions(-) diff --git a/qualibrate_runner/config/models.py b/qualibrate_runner/config/models.py index 661e134..fdd9983 100644 --- a/qualibrate_runner/config/models.py +++ b/qualibrate_runner/config/models.py @@ -1,3 +1,16 @@ +""" +Global state tracking for the qualibrate runner. + +This module defines the State model, which tracks the currently executing +calibration job and its status. The State is used by: +- The web UI to display real-time execution status +- The API to report job progress +- Monitoring systems to track calibration workflows + +The State is a singleton-like object (one per runner instance) that is +continuously updated as jobs execute. +""" + from pydantic import BaseModel, ConfigDict from qualibrate_runner.core.models.enums import RunStatusEnum @@ -6,6 +19,25 @@ class State(BaseModel): + """ + Tracks the current and last executed calibration job. + + This model serves as the single source of truth for the runner's execution + state. It is continuously updated by run_job.run_node() and + run_job.run_workflow() as jobs execute. + + The State is designed to be serializable (via Pydantic) so it can be + exposed through APIs for monitoring and UI purposes. + + Attributes: + last_run: Complete information about the most recent execution, + including status, timing, results, and errors. None if no job + has been executed yet. + run_item: Reference to the actual QualibrationNode or Graph being + executed. This allows access to the live object during execution. + None when no job is running. + """ + model_config = ConfigDict(arbitrary_types_allowed=True) last_run: LastRun | None = None @@ -19,6 +51,17 @@ def is_running(self) -> bool: ) def clear(self) -> None: + """ + Clear the state, removing all execution history. + + This resets both last_run and run_item to None, effectively clearing + all execution state. Used to start fresh or clean up after errors. + + Raises: + RuntimeError: If called while a job is currently running (status + is RUNNING). You must wait for the job to complete or error + before clearing state. + """ if ( self.last_run is not None and self.last_run.status == RunStatusEnum.RUNNING diff --git a/qualibrate_runner/core/models/common.py b/qualibrate_runner/core/models/common.py index c4c339e..5fec4ca 100644 --- a/qualibrate_runner/core/models/common.py +++ b/qualibrate_runner/core/models/common.py @@ -1,3 +1,13 @@ +""" +Common models for execution tracking. + +This module defines shared models used across the qualibrate runner: +- RunError: Captures exception information when executions fail +- StateUpdate: Records changes to quantum machine state (QuAM) + +These models are designed to be serializable for API responses and storage. +""" + from typing import Any from pydantic import BaseModel, Field @@ -6,14 +16,50 @@ class RunError(BaseModel): - """Model representing an error encountered during execution.""" + """ + Error information for failed executions. + + When a node or workflow execution fails, this model captures + error information for debugging and display purposes. + + The information is captured from Python exceptions in run_job.py and + stored in LastRun.error. + + Attributes: + error_class: The exception class name (e.g., "ValueError", "KeyError") + message: The exception's string representation (str(exception)) + traceback: Full Python traceback as a list of formatted strings, + allowing reconstruction of the error context + """ error_class: str = Field(..., description="The class of the error.") message: str = Field(..., description="The error message.") + + # details_headline: str = Field(..., description="The headline of the details.") + # details: str = Field(..., description="The details of the error.") + traceback: list[str] = Field(..., description="The traceback of the error.") class StateUpdate(BaseModel): + """ + Record of a single change to quantum machine state (QuAM). + + During calibration execution, nodes may update the quantum machine state with: + - Audit trails (what changed and when) + - Rollback capability (knowing old values) + - Display in UI (showing calibration effects) + + Each StateUpdate represents one attribute change on one quantum element. + + Attributes: + key: The QuAM path to the element being updated (e.g., "qubit_0") + attr: The specific attribute being changed (e.g., "resonance_frequency") + old: The value before the update + new: The value after the update + updated: Whether the update was successfully applied (typically True) + """ + key: str attr: str | int old: Any diff --git a/qualibrate_runner/core/models/last_run.py b/qualibrate_runner/core/models/last_run.py index e850ad0..b1c46e1 100644 --- a/qualibrate_runner/core/models/last_run.py +++ b/qualibrate_runner/core/models/last_run.py @@ -1,3 +1,17 @@ +""" +Execution tracking model for calibration jobs. + +This module defines the LastRun model, which captures information about +a single execution of a node or workflow. It includes: +- Execution status and timing +- Input parameters and results +- Error information if the execution failed +- State updates (changes to QuAM quantum machine state) + +The LastRun is updated by run_job.run_node() and run_job.run_workflow() +throughout the execution lifecycle (RUNNING -> FINISHED/ERROR). +""" + from collections.abc import Mapping from datetime import datetime from typing import Annotated, Any @@ -13,8 +27,18 @@ class LastRun(BaseModel): - """Model representing the last executed run.""" + """ + Execution record for a calibration node or workflow. + + This model captures info about a single execution, from start to + completion (or error). It's designed to be updated twice: + 1. Initially: when execution starts (RUNNING status, start time) + 2. Finally: when execution completes (FINISHED/ERROR status, results/error) + + The model supports serialization to JSON for API responses and storage. + """ + # Execution status can be RUNNING, FINISHED, or ERROR status: Annotated[ RunStatusEnum, Field( @@ -70,7 +94,7 @@ class LastRun(BaseModel): ] = None @computed_field(description="Duration of the run in seconds.") - def run_duration(self) -> float: + def run_duration(self) -> float: # Unit: seconds duration = ( self.completed_at - self.started_at if self.completed_at is not None diff --git a/qualibrate_runner/core/run_job.py b/qualibrate_runner/core/run_job.py index 7d956eb..083b66b 100644 --- a/qualibrate_runner/core/run_job.py +++ b/qualibrate_runner/core/run_job.py @@ -1,3 +1,19 @@ +""" +Job execution orchestration for QualibrationNodes and workflows. + +This module provides the top-level execution layer for running calibration +nodes and workflows (graphs). It handles: +- Parameter validation +- State tracking throughout execution +- Error capture and reporting +- Timestamp management (start/completion times) +- Integration with the active QualibrationLibrary + +The execution follows a try-except-finally pattern to ensure state is always +updated, even when errors occur. This state is used by the web UI and other +monitoring systems to track calibration progress. +""" + import traceback from collections.abc import Mapping from datetime import datetime @@ -20,6 +36,25 @@ def validate_input_parameters( parameters_class: type[BaseModel], passed_parameters: Mapping[str, Any], ) -> BaseModel: + """ + Validate input parameters against a Pydantic model schema. + + This function ensures that parameters passed to nodes or workflows conform + to their expected schema. If validation fails, it raises an HTTP exception + suitable for API responses. + + Args: + parameters_class: The Pydantic model class defining the expected + parameter schema + passed_parameters: Dictionary of parameter names and values to validate + + Returns: + A validated Pydantic model instance with the parameters + + Raises: + HTTPException: With status 422 (Unprocessable Entity) if validation + fails, containing detailed error information from Pydantic + """ try: return parameters_class.model_validate(passed_parameters) except ValidationError as ex: @@ -29,6 +64,19 @@ def validate_input_parameters( def get_active_library_or_error() -> QLibraryType: + """ + Retrieve the currently active QualibrationLibrary. + + This is a thin wrapper around QualibrationLibrary.get_active_library that + explicitly requests an existing library (create=False). It will raise an + error if no active library exists. + + Returns: + The active QualibrationLibrary instance + + Raises: + Exception: If no active library exists (raised by get_active_library) + """ return QualibrationLibrary.get_active_library(create=False) @@ -37,33 +85,77 @@ def run_node( passed_input_parameters: Mapping[str, Any], state: State, ) -> None: + """ + Execute a single QualibrationNode with comprehensive state tracking. + + This function orchestrates the execution of a calibration node, managing + the complete lifecycle: + 1. Initialize state with RUNNING status + 2. Execute the node in interactive mode + 3. Capture any errors with full traceback + 4. Update state with final results (success or error) + + The state is ALWAYS updated in the finally block, ensuring monitoring + systems have accurate information even when exceptions occur. + + Args: + node: The QualibrationNode instance to execute + passed_input_parameters: Runtime parameters to pass to the node's + run method + state: Global state object that tracks the current run status and + results for monitoring/UI purposes + + Raises: + Exception: Any exception raised by node.run() is re-raised after + being captured in state.last_run.error + + Side Effects: + - Updates state.run_item to reference the executing node + - Updates state.last_run with execution status, timing, and results + - Calls node.run() which may execute quantum programs and update + quantum machine state + """ + # Set the currently executing item for monitoring state.run_item = node run_status = RunStatusEnum.RUNNING + + # Initialize the run tracking with start time and RUNNING status state.last_run = LastRun( name=node.name, status=RunStatusEnum.RUNNING, - idx=-1, + idx=-1, # Snapshot index, updated after successful run passed_parameters=passed_input_parameters, started_at=datetime.now().astimezone(), runnable_type=RunnableType.NODE, ) + + # Track snapshot index and error state idx = -1 run_error = None + try: + # Execute the node in interactive mode with provided parameters + # interactive=True enables variable injection for Jupyter (not sure why this is needed here) node.run(interactive=True, **passed_input_parameters) except Exception as ex: + # Capture error details for state tracking run_status = RunStatusEnum.ERROR run_error = RunError( error_class=ex.__class__.__name__, message=str(ex), traceback=traceback.format_tb(ex.__traceback__), ) + # Re-raise to allow caller to handle the error raise else: + # If successful, capture the snapshot index for data storage + # The snapshot_idx links results to stored data files _idx = node.snapshot_idx if hasattr(node, "snapshot_idx") else -1 idx = _idx if _idx is not None else -1 run_status = RunStatusEnum.FINISHED finally: + # ALWAYS update state with final results, even if an error occurred + # This ensures the UI and monitoring systems have current information state.last_run = LastRun( name=state.last_run.name, status=run_status, @@ -74,7 +166,7 @@ def run_node( passed_parameters=passed_input_parameters, started_at=state.last_run.started_at, completed_at=datetime.now().astimezone(), - state_updates=node.state_updates, + state_updates=node.state_updates, # QuAM state changes error=run_error, ) @@ -84,41 +176,102 @@ def run_workflow( passed_input_parameters: Mapping[str, Any], state: State, ) -> None: + """ + Execute a calibration workflow (DAG of QualibrationNodes). + + This function orchestrates the execution of a workflow (also called a graph), + which is a directed acyclic graph (DAG) of interconnected QualibrationNodes. + The workflow execution follows these steps: + 1. Initialize state with RUNNING status + 2. Get a fresh copy of the workflow from the active library + 3. Validate and structure the input parameters + 4. Execute the workflow's DAG, running nodes in dependency order + 5. Capture any errors with full traceback + 6. Update state with final results + + The workflow is retrieved fresh from the library to ensure a clean instance + for each run, avoiding state pollution between runs. + + Args: + workflow: The workflow (graph) instance to execute - note that a fresh + copy is retrieved from the library, so this is mainly used for + the name + passed_input_parameters: Runtime parameters containing both workflow- + level parameters and node-specific parameters. These are validated + against workflow.full_parameters_class which includes both + 'parameters' (workflow-level) and 'nodes' (per-node overrides) + state: Global state object that tracks the current run status and + results for monitoring/UI purposes + + Raises: + Exception: Any exception raised during workflow execution is re-raised + after being captured in state.last_run.error + + Side Effects: + - Updates state.run_item to reference the executing workflow + - Updates state.last_run with execution status, timing, and results + - Executes all nodes in the workflow DAG in dependency order + - May execute quantum programs and update quantum machine state + """ run_status = RunStatusEnum.RUNNING + + # Initialize the run tracking with start time and RUNNING status state.last_run = LastRun( name=workflow.name, status=run_status, - idx=-1, + idx=-1, # Snapshot index, updated after successful run started_at=datetime.now().astimezone(), runnable_type=RunnableType.GRAPH, passed_parameters=passed_input_parameters, ) + + # Track snapshot index and error state idx = -1 run_error = None + try: + # Get the active library to retrieve a fresh workflow copy library = get_active_library_or_error() - workflow = library.graphs[workflow.name] # copied graph instance + + # Get a fresh copy of the workflow from the library + # This ensures each run starts with a clean state + workflow = library.graphs[workflow.name] + + # Set the currently executing item for monitoring state.run_item = workflow + + # Validate and structure input parameters + # full_parameters_class expects: {parameters: {...}, nodes: {...}} + # where 'parameters' are workflow-level and 'nodes' are per-node input_parameters = workflow.full_parameters_class( **passed_input_parameters ) + + # Execute the workflow DAG + # Nodes are executed in dependency order determined by the DAG workflow.run( nodes=input_parameters.nodes.model_dump(), **input_parameters.parameters.model_dump(), ) except Exception as ex: + # Capture error details for state tracking run_status = RunStatusEnum.ERROR run_error = RunError( error_class=ex.__class__.__name__, message=str(ex), traceback=traceback.format_tb(ex.__traceback__), ) + # Re-raise to allow caller to handle the error raise else: + # If successful, capture the snapshot index for data storage + # The snapshot_idx links results to stored data files idx = workflow.snapshot_idx if hasattr(workflow, "snapshot_idx") else -1 idx = idx if idx is not None else -1 run_status = RunStatusEnum.FINISHED finally: + # ALWAYS update state with final results, even if an error occurred + # This ensures the UI and monitoring systems have current information state.last_run = LastRun( name=state.last_run.name, status=run_status, From d3c1d202be8316a1d47678dd203c21b7e5fe8222 Mon Sep 17 00:00:00 2001 From: Jonatan Kutchinsky Date: Thu, 20 Nov 2025 09:01:08 +0100 Subject: [PATCH 04/26] Changed comments - better explanations --- qualibrate_runner/core/run_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qualibrate_runner/core/run_job.py b/qualibrate_runner/core/run_job.py index 083b66b..e0319f3 100644 --- a/qualibrate_runner/core/run_job.py +++ b/qualibrate_runner/core/run_job.py @@ -135,7 +135,7 @@ def run_node( try: # Execute the node in interactive mode with provided parameters - # interactive=True enables variable injection for Jupyter (not sure why this is needed here) + # interactive=True enables that the user approves changes to variables in the frontend after execution node.run(interactive=True, **passed_input_parameters) except Exception as ex: # Capture error details for state tracking From e0d995b061959f8ea00c3a36774d1e778997a7a5 Mon Sep 17 00:00:00 2001 From: Jonatan Kutchinsky Date: Thu, 20 Nov 2025 09:16:39 +0100 Subject: [PATCH 05/26] Replaced deprecated HTTP_422_UNPROCESSABLE_ENTITY with HTTP_422_UNPROCESSABLE_CONTENT --- qualibrate_runner/api/routes/others.py | 4 ++-- qualibrate_runner/api/routes/submit.py | 4 ++-- qualibrate_runner/core/run_job.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/qualibrate_runner/api/routes/others.py b/qualibrate_runner/api/routes/others.py index b1aa7de..2d875e7 100644 --- a/qualibrate_runner/api/routes/others.py +++ b/qualibrate_runner/api/routes/others.py @@ -98,13 +98,13 @@ def state_updated( or state.last_run.status != RunStatusEnum.FINISHED ): raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail="Node not executed or finished unsuccessful.", ) state_updates = state.last_run.state_updates if key not in state_updates: raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail="Unknown state update key.", ) state_updates[key].updated = True diff --git a/qualibrate_runner/api/routes/submit.py b/qualibrate_runner/api/routes/submit.py index f296178..b3e9e8b 100644 --- a/qualibrate_runner/api/routes/submit.py +++ b/qualibrate_runner/api/routes/submit.py @@ -36,7 +36,7 @@ def submit_node_run( input_parameters = input_parameters["parameters"] if state.is_running: raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail="Already running", ) validate_input_parameters( @@ -55,7 +55,7 @@ def submit_workflow_run( ) -> str: if state.is_running: raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail="Already running", ) input_parameters = { diff --git a/qualibrate_runner/core/run_job.py b/qualibrate_runner/core/run_job.py index e0319f3..b7163d4 100644 --- a/qualibrate_runner/core/run_job.py +++ b/qualibrate_runner/core/run_job.py @@ -59,7 +59,7 @@ def validate_input_parameters( return parameters_class.model_validate(passed_parameters) except ValidationError as ex: raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=ex.errors() + status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail=ex.errors() ) from ex From dff36ce47cdc1e09550b348cc0a97b240e50a548 Mon Sep 17 00:00:00 2001 From: Jonatan Kutchinsky Date: Thu, 20 Nov 2025 09:18:13 +0100 Subject: [PATCH 06/26] Adding integration tests to qualibrate-runner, that runs actual (test) nodes. --- poetry.lock | 630 ++++++++++++++---- pyproject.toml | 9 +- tests/conftest.py | 27 + tests/fixtures/__init__.py | 0 tests/fixtures/test_nodes/__init__.py | 0 .../fixtures/test_nodes/node_with_actions.py | 149 +++++ .../test_nodes/test_node_can_raise_in_body.py | 58 ++ tests/integration/__init__.py | 0 tests/integration/test_run_job_integration.py | 279 ++++++++ 9 files changed, 1004 insertions(+), 148 deletions(-) create mode 100644 tests/fixtures/__init__.py create mode 100644 tests/fixtures/test_nodes/__init__.py create mode 100644 tests/fixtures/test_nodes/node_with_actions.py create mode 100644 tests/fixtures/test_nodes/test_node_can_raise_in_body.py create mode 100644 tests/integration/__init__.py create mode 100644 tests/integration/test_run_job_integration.py diff --git a/poetry.lock b/poetry.lock index e99dfe1..5f4a6bc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,12 +1,27 @@ # This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. +[[package]] +name = "a2wsgi" +version = "1.10.10" +description = "Convert WSGI app to ASGI app or ASGI app to WSGI app." +optional = false +python-versions = ">=3.8.0" +groups = ["test"] +files = [ + {file = "a2wsgi-1.10.10-py3-none-any.whl", hash = "sha256:d2b21379479718539dc15fce53b876251a0efe7615352dfe49f6ad1bc507848d"}, + {file = "a2wsgi-1.10.10.tar.gz", hash = "sha256:a5bcffb52081ba39df0d5e9a884fc6f819d92e3a42389343ba77cbf809fe1f45"}, +] + +[package.dependencies] +typing_extensions = {version = "*", markers = "python_version < \"3.11\""} + [[package]] name = "annotated-types" version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "qm-dev", "test"] 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"}, @@ -18,7 +33,7 @@ version = "4.11.0" description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc"}, {file = "anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4"}, @@ -39,7 +54,7 @@ version = "3.6.2" description = "Bash tab completion for argparse" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "argcomplete-3.6.2-py3-none-any.whl", hash = "sha256:65b3133a29ad53fb42c48cf5114752c7ab66c1c38544fdf6460f450c09b42591"}, {file = "argcomplete-3.6.2.tar.gz", hash = "sha256:d0519b1bc867f5f4f4713c41ad0aba73a4a5f007449716b16f385f2166dc6adf"}, @@ -48,13 +63,31 @@ files = [ [package.extras] test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] +[[package]] +name = "asteval" +version = "1.0.7" +description = "Safe, minimalistic evaluator of python expression using ast module" +optional = false +python-versions = ">=3.10" +groups = ["test"] +files = [ + {file = "asteval-1.0.7-py3-none-any.whl", hash = "sha256:d78df08681dfff59031ca624ba7030f9dc576a7a16e2f7a5137c6e7ef3ee60c4"}, + {file = "asteval-1.0.7.tar.gz", hash = "sha256:729fa010511957afcbdbadb0304da09cef6904c145b6901e917afac2dd5668a1"}, +] + +[package.extras] +all = ["asteval[dev,doc,test]"] +dev = ["build", "twine"] +doc = ["Sphinx"] +test = ["coverage", "pytest", "pytest-cov"] + [[package]] name = "backports-tarfile" version = "1.2.0" description = "Backport of CPython tarfile module" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "qm-dev", "test"] markers = "python_version < \"3.12\"" files = [ {file = "backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34"}, @@ -71,7 +104,7 @@ version = "2.0.0b7" description = "A better Protobuf / gRPC generator & library" optional = false python-versions = "<4.0,>=3.7" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "betterproto-2.0.0b7-py3-none-any.whl", hash = "sha256:401ab8055e2f814e77b9c88a74d0e1ae3d1e8a969cced6aeb1b59f71ad63fbd2"}, {file = "betterproto-2.0.0b7.tar.gz", hash = "sha256:1b1458ca5278d519bcd62556a4c236f998a91d503f0f71c67b0b954747052af2"}, @@ -92,7 +125,7 @@ version = "25.9.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "black-25.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce41ed2614b706fd55fd0b4a6909d06b5bab344ffbfadc6ef34ae50adba3d4f7"}, {file = "black-25.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ab0ce111ef026790e9b13bd216fa7bc48edd934ffc4cbf78808b235793cbc92"}, @@ -140,7 +173,7 @@ version = "1.3.0" description = "A simple, correct Python build frontend" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "build-1.3.0-py3-none-any.whl", hash = "sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4"}, {file = "build-1.3.0.tar.gz", hash = "sha256:698edd0ea270bde950f53aed21f3a0135672206f3911e0176261a31e0e07b397"}, @@ -163,7 +196,7 @@ version = "0.14.3" description = "httplib2 caching for requests" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "cachecontrol-0.14.3-py3-none-any.whl", hash = "sha256:b35e44a3113f17d2a31c1e6b27b9de6d4405f84ae51baa8c1d3cc5b633010cae"}, {file = "cachecontrol-0.14.3.tar.gz", hash = "sha256:73e7efec4b06b20d9267b441c1f733664f989fb8688391b670ca812d70795d11"}, @@ -185,7 +218,7 @@ version = "2025.8.3" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, @@ -197,8 +230,7 @@ version = "2.0.0" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.9" -groups = ["main"] -markers = "platform_python_implementation != \"PyPy\" and sys_platform == \"linux\" or sys_platform == \"darwin\"" +groups = ["main", "qm-dev", "test"] files = [ {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, @@ -285,6 +317,7 @@ files = [ {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"}, {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"}, ] +markers = {main = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and (sys_platform == \"linux\" or sys_platform == \"darwin\")", qm-dev = "(sys_platform == \"linux\" or sys_platform == \"darwin\") and (platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\")", test = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and (sys_platform == \"linux\" or sys_platform == \"darwin\")"} [package.dependencies] pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} @@ -295,7 +328,7 @@ version = "3.4.3" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, @@ -384,7 +417,7 @@ version = "2.1.0" description = "Cleo allows you to create beautiful and testable command-line interfaces." optional = false python-versions = ">=3.7,<4.0" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "cleo-2.1.0-py3-none-any.whl", hash = "sha256:4a31bd4dd45695a64ee3c4758f583f134267c2bc518d8ae9a29cf237d009b07e"}, {file = "cleo-2.1.0.tar.gz", hash = "sha256:0b2c880b5d13660a7ea651001fb4acb527696c01f15c9ee650f377aa543fd523"}, @@ -400,7 +433,7 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -415,12 +448,12 @@ 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" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {main = "platform_system == \"Windows\" or os_name == \"nt\"", test = "sys_platform == \"win32\""} +markers = {main = "platform_system == \"Windows\" or os_name == \"nt\"", qm-dev = "os_name == \"nt\" or platform_system == \"Windows\""} [[package]] name = "contourpy" @@ -428,7 +461,7 @@ version = "1.3.2" description = "Python library for calculating contours of 2D quadrilateral grids" optional = false python-versions = ">=3.10" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934"}, {file = "contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989"}, @@ -505,7 +538,7 @@ version = "0.4.1" description = "Manage Python errors with ease" optional = false python-versions = ">=3.7,<4.0" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "crashtest-0.4.1-py3-none-any.whl", hash = "sha256:8d23eac5fa660409f57472e3851dab7ac18aba459a8d19cbbba86d3d5aecd2a5"}, {file = "crashtest-0.4.1.tar.gz", hash = "sha256:80d7b1f316ebfbd429f648076d6275c877ba30ba48979de4191714a75266f0ce"}, @@ -517,7 +550,7 @@ version = "46.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = "!=3.9.0,!=3.9.1,>=3.8" -groups = ["main"] +groups = ["main", "qm-dev", "test"] markers = "sys_platform == \"linux\"" files = [ {file = "cryptography-46.0.1-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:1cd6d50c1a8b79af1a6f703709d8973845f677c8e97b1268f5ff323d38ce8475"}, @@ -596,7 +629,7 @@ version = "0.12.1" description = "Composable style cycles" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, @@ -612,7 +645,7 @@ version = "2.43.0" description = "Collection of all Datadog Public endpoints" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "datadog_api_client-2.43.0-py3-none-any.whl", hash = "sha256:1d038b545263ad0b665f46d9d36dc0f0ed491770d9dcd68380be4685a9ae6cc3"}, {file = "datadog_api_client-2.43.0.tar.gz", hash = "sha256:f3aec5db9bcceb39f6315e505eea908da7133a19f8475ef996ef8be8e3c84855"}, @@ -636,7 +669,7 @@ version = "0.33.0" description = "Datamodel Code Generator" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "datamodel_code_generator-0.33.0-py3-none-any.whl", hash = "sha256:e229264aa612b2d5bb4901bcd6c520a799ae0d5c19262577a0f876eb48afaaa3"}, {file = "datamodel_code_generator-0.33.0.tar.gz", hash = "sha256:7635ef788201d69bd3e98ba88ce6afe479400dc2737fe9d5e21f87408f352c08"}, @@ -668,7 +701,7 @@ version = "4.48.2" description = "Dependency injection framework for Python" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "dependency_injector-4.48.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:54d0178be10f17b768afb5c0ed1c5c565abaa2d097b2bc5a529a31c580613df2"}, {file = "dependency_injector-4.48.2-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:12a15979fd534b728b3061c8aa52fd55adb77574758817daae9df8a1c2eb830b"}, @@ -712,13 +745,29 @@ pydantic = ["pydantic"] pydantic2 = ["pydantic-settings"] yaml = ["pyyaml"] +[[package]] +name = "dill" +version = "0.4.0" +description = "serialize all of Python" +optional = false +python-versions = ">=3.8" +groups = ["test"] +files = [ + {file = "dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049"}, + {file = "dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0"}, +] + +[package.extras] +graph = ["objgraph (>=1.7.2)"] +profile = ["gprof2dot (>=2022.7.29)"] + [[package]] name = "distlib" version = "0.4.0" description = "Distribution utilities" optional = false python-versions = "*" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, @@ -730,7 +779,7 @@ version = "0.22.8" description = "Python Git Library" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "dulwich-0.22.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:546176d18b8cc0a492b0f23f07411e38686024cffa7e9d097ae20512a2e57127"}, {file = "dulwich-0.22.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d2434dd72b2ae09b653c9cfe6764a03c25cfbd99fbbb7c426f0478f6fb1100f"}, @@ -797,7 +846,7 @@ version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] markers = "python_version == \"3.10\"" files = [ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, @@ -816,7 +865,7 @@ version = "0.117.1" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "test"] files = [ {file = "fastapi-0.117.1-py3-none-any.whl", hash = "sha256:33c51a0d21cab2b9722d4e56dbb9316f3687155be6b276191790d8da03507552"}, {file = "fastapi-0.117.1.tar.gz", hash = "sha256:fb2d42082d22b185f904ca0ecad2e195b851030bd6c5e4c032d1c981240c631a"}, @@ -838,7 +887,7 @@ version = "2.21.2" description = "Fastest Python implementation of JSON schema" optional = false python-versions = "*" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463"}, {file = "fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de"}, @@ -853,7 +902,7 @@ version = "3.19.1" description = "A platform independent file lock." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d"}, {file = "filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58"}, @@ -865,7 +914,7 @@ version = "0.6.3" description = "A utility to find python versions on your system" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "findpython-0.6.3-py3-none-any.whl", hash = "sha256:a85bb589b559cdf1b87227cc233736eb7cad894b9e68021ee498850611939ebc"}, {file = "findpython-0.6.3.tar.gz", hash = "sha256:5863ea55556d8aadc693481a14ac4f3624952719efc1c5591abb0b4a9e965c94"}, @@ -880,7 +929,7 @@ version = "4.60.1" description = "Tools to manipulate font files" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "fonttools-4.60.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9a52f254ce051e196b8fe2af4634c2d2f02c981756c6464dc192f1b6050b4e28"}, {file = "fonttools-4.60.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c7420a2696a44650120cdd269a5d2e56a477e2bfa9d95e86229059beb1c19e15"}, @@ -976,7 +1025,7 @@ version = "1.3.0" description = "GenSON is a powerful, user-friendly JSON Schema generator." optional = false python-versions = "*" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "genson-1.3.0-py3-none-any.whl", hash = "sha256:468feccd00274cc7e4c09e84b08704270ba8d95232aa280f65b986139cec67f7"}, {file = "genson-1.3.0.tar.gz", hash = "sha256:e02db9ac2e3fd29e65b5286f7135762e2cd8a986537c075b06fc5f1517308e37"}, @@ -988,7 +1037,7 @@ version = "1.75.1" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "grpcio-1.75.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:1712b5890b22547dd29f3215c5788d8fc759ce6dd0b85a6ba6e2731f2d04c088"}, {file = "grpcio-1.75.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8d04e101bba4b55cea9954e4aa71c24153ba6182481b487ff376da28d4ba46cf"}, @@ -1065,7 +1114,7 @@ version = "0.4.5" description = "Pure-Python gRPC implementation for asyncio" optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "grpclib-0.4.5.tar.gz", hash = "sha256:bf83ed55aca59497e168761d9555056efc54a8f865316c3b39becd007e9f9a73"}, ] @@ -1083,7 +1132,7 @@ version = "0.16.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, @@ -1095,7 +1144,7 @@ version = "4.3.0" description = "Pure-Python HTTP/2 protocol implementation" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd"}, {file = "h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1"}, @@ -1111,7 +1160,7 @@ version = "4.1.0" description = "Pure-Python HPACK header encoding" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496"}, {file = "hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca"}, @@ -1123,7 +1172,7 @@ version = "1.0.9" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, @@ -1145,7 +1194,7 @@ version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -1171,7 +1220,7 @@ version = "6.1.0" description = "Pure-Python HTTP/2 framing" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5"}, {file = "hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08"}, @@ -1183,7 +1232,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -1198,7 +1247,7 @@ version = "8.7.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] markers = "python_version < \"3.12\"" files = [ {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, @@ -1223,7 +1272,7 @@ version = "7.5.0" description = "Correctly generate plurals, singular nouns, ordinals, indefinite articles" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "inflect-7.5.0-py3-none-any.whl", hash = "sha256:2aea70e5e70c35d8350b8097396ec155ffd68def678c7ff97f51aa69c1d92344"}, {file = "inflect-7.5.0.tar.gz", hash = "sha256:faf19801c3742ed5a05a8ce388e0d8fe1a07f8d095c82201eb904f5d27ad571f"}, @@ -1259,7 +1308,7 @@ version = "0.7.0" description = "A library for installing Python wheels." optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "installer-0.7.0-py3-none-any.whl", hash = "sha256:05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53"}, {file = "installer-0.7.0.tar.gz", hash = "sha256:a26d3e3116289bb08216e0d0f7d925fcef0b0194eedfa0c944bcaaa106c4b631"}, @@ -1271,7 +1320,7 @@ version = "6.0.1" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.9.0" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615"}, {file = "isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450"}, @@ -1287,7 +1336,7 @@ version = "3.4.0" description = "Utility functions for Python class constructs" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790"}, {file = "jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd"}, @@ -1306,7 +1355,7 @@ version = "6.0.1" description = "Useful decorators and context managers" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "jaraco.context-6.0.1-py3-none-any.whl", hash = "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4"}, {file = "jaraco_context-6.0.1.tar.gz", hash = "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3"}, @@ -1325,7 +1374,7 @@ version = "4.3.0" description = "Functools like those found in stdlib" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "jaraco_functools-4.3.0-py3-none-any.whl", hash = "sha256:227ff8ed6f7b8f62c56deff101545fa7543cf2c8e7b82a7c2116e672f29c26e8"}, {file = "jaraco_functools-4.3.0.tar.gz", hash = "sha256:cfd13ad0dd2c47a3600b439ef72d8615d482cedcff1632930d6f28924d92f294"}, @@ -1348,7 +1397,7 @@ version = "0.9.0" description = "Low-level, pure Python DBus protocol wrapper." optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "qm-dev", "test"] markers = "sys_platform == \"linux\"" files = [ {file = "jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683"}, @@ -1365,7 +1414,7 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -1383,7 +1432,7 @@ version = "1.5.2" description = "Lightweight pipelining with Python functions" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241"}, {file = "joblib-1.5.2.tar.gz", hash = "sha256:3faa5c39054b2f03ca547da9b2f52fde67c06240c31853f306aea97f13647b55"}, @@ -1395,7 +1444,7 @@ version = "1.33" description = "Apply JSON-Patches (RFC 6902)" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, @@ -1410,7 +1459,7 @@ version = "3.0.0" description = "Identify specific nodes in a JSON document (RFC 6901)" optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, @@ -1422,7 +1471,7 @@ version = "25.6.0" description = "Store and access your passwords safely." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd"}, {file = "keyring-25.6.0.tar.gz", hash = "sha256:0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66"}, @@ -1452,7 +1501,7 @@ version = "1.4.9" description = "A fast implementation of the Cassowary constraint solver" optional = false python-versions = ">=3.10" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b4b4d74bda2b8ebf4da5bd42af11d02d04428b2c32846e4c2c93219df8a7987b"}, {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fb3b8132019ea572f4611d770991000d7f58127560c4889729248eb5852a102f"}, @@ -1557,13 +1606,38 @@ files = [ {file = "kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d"}, ] +[[package]] +name = "lmfit" +version = "1.3.4" +description = "Least-Squares Minimization with Bounds and Constraints" +optional = false +python-versions = ">=3.9" +groups = ["test"] +files = [ + {file = "lmfit-1.3.4-py3-none-any.whl", hash = "sha256:afce1593b42324d37ae2908249b0c55445e2f4c1a0474ff706a8e2f7b5d949fa"}, + {file = "lmfit-1.3.4.tar.gz", hash = "sha256:3c22c28c43f717f6c5b4a3bd81e893a2149739c26a592c046f2e33c23cfbe497"}, +] + +[package.dependencies] +asteval = ">=1.0" +dill = ">=0.3.4" +numpy = ">=1.24" +scipy = ">=1.10.0" +uncertainties = ">=3.2.2" + +[package.extras] +all = ["lmfit[dev,doc,test]"] +dev = ["build", "check-wheel-contents", "flake8-pyproject", "pre-commit", "twine"] +doc = ["Pillow", "Sphinx", "cairosvg", "corner", "emcee (>=3.0.0)", "ipykernel", "jupyter_sphinx (>=0.2.4)", "matplotlib", "numdifftools", "pandas", "pycairo ; platform_system == \"Windows\"", "sphinx-gallery (>=0.10)", "sphinxcontrib-svg2pdfconverter", "sympy"] +test = ["coverage", "flaky", "pytest", "pytest-cov"] + [[package]] name = "markupsafe" version = "3.0.3" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, @@ -1662,7 +1736,7 @@ version = "3.23.3" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "marshmallow-3.23.3-py3-none-any.whl", hash = "sha256:20c0f8c613f68bcb45b2a0d3282e2f172575560170bf220d67aafb42717910e4"}, {file = "marshmallow-3.23.3.tar.gz", hash = "sha256:d586c8685ebdb80bf754e1f96e3f305aaf30951f1fc69175b977453633467e76"}, @@ -1682,7 +1756,7 @@ version = "5.11" description = "An unofficial extension to Marshmallow to allow for polymorphic fields" optional = false python-versions = ">=3.5" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "marshmallow-polyfield-5.11.tar.gz", hash = "sha256:8075a9cc490da4af58b902b4a40a99882dd031adb7aaa96abd147a4fcd53415f"}, ] @@ -1696,7 +1770,7 @@ version = "3.10.6" description = "Python plotting package" optional = false python-versions = ">=3.10" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "matplotlib-3.10.6-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:bc7316c306d97463a9866b89d5cc217824e799fa0de346c8f68f4f3d27c8693d"}, {file = "matplotlib-3.10.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d00932b0d160ef03f59f9c0e16d1e3ac89646f7785165ce6ad40c842db16cc2e"}, @@ -1775,7 +1849,7 @@ version = "10.8.0" description = "More routines for operating on iterables, beyond itertools" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b"}, {file = "more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd"}, @@ -1787,7 +1861,7 @@ version = "1.1.1" description = "MessagePack serializer" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "msgpack-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:353b6fc0c36fde68b661a12949d7d49f8f51ff5fa019c1e47c87c4ff34b080ed"}, {file = "msgpack-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:79c408fcf76a958491b4e3b103d1c417044544b68e96d06432a189b43d1215c8"}, @@ -1856,7 +1930,7 @@ version = "6.6.4" description = "multidict implementation" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "multidict-6.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b8aa6f0bd8125ddd04a6593437bad6a7e70f300ff4180a531654aa2ab3f6d58f"}, {file = "multidict-6.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9e5853bbd7264baca42ffc53391b490d65fe62849bf2c690fa3f6273dbcd0cb"}, @@ -2040,7 +2114,7 @@ version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "qm-dev", "test"] files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, @@ -2052,7 +2126,7 @@ version = "3.4.2" description = "Python package for creating and manipulating graphs and networks" optional = false python-versions = ">=3.10" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f"}, {file = "networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1"}, @@ -2072,7 +2146,7 @@ version = "1.26.4" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, @@ -2118,12 +2192,112 @@ version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, ] +[[package]] +name = "pandas" +version = "2.3.3" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.9" +groups = ["test"] +files = [ + {file = "pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c"}, + {file = "pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a"}, + {file = "pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1"}, + {file = "pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838"}, + {file = "pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250"}, + {file = "pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4"}, + {file = "pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826"}, + {file = "pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523"}, + {file = "pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45"}, + {file = "pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66"}, + {file = "pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b"}, + {file = "pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791"}, + {file = "pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151"}, + {file = "pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c"}, + {file = "pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53"}, + {file = "pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35"}, + {file = "pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908"}, + {file = "pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89"}, + {file = "pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98"}, + {file = "pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084"}, + {file = "pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b"}, + {file = "pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713"}, + {file = "pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8"}, + {file = "pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d"}, + {file = "pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac"}, + {file = "pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c"}, + {file = "pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493"}, + {file = "pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee"}, + {file = "pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5"}, + {file = "pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21"}, + {file = "pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78"}, + {file = "pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110"}, + {file = "pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86"}, + {file = "pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc"}, + {file = "pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0"}, + {file = "pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593"}, + {file = "pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c"}, + {file = "pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b"}, + {file = "pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6"}, + {file = "pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3"}, + {file = "pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5"}, + {file = "pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec"}, + {file = "pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7"}, + {file = "pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450"}, + {file = "pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5"}, + {file = "pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788"}, + {file = "pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87"}, + {file = "pandas-2.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c503ba5216814e295f40711470446bc3fd00f0faea8a086cbc688808e26f92a2"}, + {file = "pandas-2.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a637c5cdfa04b6d6e2ecedcb81fc52ffb0fd78ce2ebccc9ea964df9f658de8c8"}, + {file = "pandas-2.3.3-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:854d00d556406bffe66a4c0802f334c9ad5a96b4f1f868adf036a21b11ef13ff"}, + {file = "pandas-2.3.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf1f8a81d04ca90e32a0aceb819d34dbd378a98bf923b6398b9a3ec0bf44de29"}, + {file = "pandas-2.3.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:23ebd657a4d38268c7dfbdf089fbc31ea709d82e4923c5ffd4fbd5747133ce73"}, + {file = "pandas-2.3.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5554c929ccc317d41a5e3d1234f3be588248e61f08a74dd17c9eabb535777dc9"}, + {file = "pandas-2.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:d3e28b3e83862ccf4d85ff19cf8c20b2ae7e503881711ff2d534dc8f761131aa"}, + {file = "pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b"}, +] + +[package.dependencies] +numpy = [ + {version = ">=1.22.4", markers = "python_version < \"3.11\""}, + {version = ">=1.23.2", markers = "python_version == \"3.11\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, +] +python-dateutil = ">=2.8.2" +pytz = ">=2020.1" +tzdata = ">=2022.7" + +[package.extras] +all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] +aws = ["s3fs (>=2022.11.0)"] +clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] +compression = ["zstandard (>=0.19.0)"] +computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] +consortium-standard = ["dataframe-api-compat (>=0.1.7)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] +feather = ["pyarrow (>=10.0.1)"] +fss = ["fsspec (>=2022.11.0)"] +gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] +hdf5 = ["tables (>=3.8.0)"] +html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] +mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] +parquet = ["pyarrow (>=10.0.1)"] +performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] +plot = ["matplotlib (>=3.6.3)"] +postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] +pyarrow = ["pyarrow (>=10.0.1)"] +spss = ["pyreadstat (>=1.2.0)"] +sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.9.2)"] + [[package]] name = "pastel" version = "0.2.1" @@ -2142,7 +2316,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "qm-dev", "test"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -2154,7 +2328,7 @@ version = "2025.9.18" description = "Installer for Python Build Standalone" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "pbs_installer-2025.9.18-py3-none-any.whl", hash = "sha256:8ef55d7675698747505c237015d14c81759bd66a0d4c8b20cec9a2dc96e8434c"}, {file = "pbs_installer-2025.9.18.tar.gz", hash = "sha256:c0a51a7c1e015723bd8396f02e15b5876e439f74b0f45bbac436b189f903219f"}, @@ -2175,7 +2349,7 @@ version = "11.3.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860"}, {file = "pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"}, @@ -2300,7 +2474,7 @@ version = "1.12.1.2" description = "Query metadata from sdists / bdists / installed packages." optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "pkginfo-1.12.1.2-py3-none-any.whl", hash = "sha256:c783ac885519cab2c34927ccfa6bf64b5a704d7c69afaea583dd9b7afe969343"}, {file = "pkginfo-1.12.1.2.tar.gz", hash = "sha256:5cd957824ac36f140260964eba3c6be6442a8359b8c48f4adf90210f33a04b7b"}, @@ -2315,7 +2489,7 @@ version = "4.4.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85"}, {file = "platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"}, @@ -2332,7 +2506,7 @@ version = "5.24.1" description = "An open-source, interactive data visualization library for Python" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "plotly-5.24.1-py3-none-any.whl", hash = "sha256:f67073a1e637eb0dc3e46324d9d51e2fe76e9727c892dde64ddf1e1b51f29089"}, {file = "plotly-5.24.1.tar.gz", hash = "sha256:dbc8ac8339d248a4bcc36e08a5659bacfe1b079390b8953533f4eb22169b4bae"}, @@ -2384,7 +2558,7 @@ version = "2.1.3" description = "Python dependency management and packaging made easy." optional = false python-versions = "<4.0,>=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "poetry-2.1.3-py3-none-any.whl", hash = "sha256:7054d3f97ccce7f31961ead16250407c4577bfe57e2037a190ae2913fc40a20c"}, {file = "poetry-2.1.3.tar.gz", hash = "sha256:f2c9bd6790b19475976d88ea4553bcc3533c0dc73f740edc4fffe9e2add50594"}, @@ -2420,7 +2594,7 @@ version = "2.1.3" description = "Poetry PEP 517 Build Backend" optional = false python-versions = "<4.0,>=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "poetry_core-2.1.3-py3-none-any.whl", hash = "sha256:2c704f05016698a54ca1d327f46ce2426d72eaca6ff614132c8477c292266771"}, {file = "poetry_core-2.1.3.tar.gz", hash = "sha256:0522a015477ed622c89aad56a477a57813cace0c8e7ff2a2906b7ef4a2e296a4"}, @@ -2432,7 +2606,7 @@ version = "6.32.1" description = "" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "protobuf-6.32.1-cp310-abi3-win32.whl", hash = "sha256:a8a32a84bc9f2aad712041b8b366190f71dde248926da517bde9e832e4412085"}, {file = "protobuf-6.32.1-cp310-abi3-win_amd64.whl", hash = "sha256:b00a7d8c25fa471f16bc8153d0e53d6c9e827f0953f3c09aaa4331c718cae5e1"}, @@ -2451,12 +2625,12 @@ version = "2.23" description = "C parser in Python" optional = false python-versions = ">=3.8" -groups = ["main"] -markers = "(platform_python_implementation != \"PyPy\" and sys_platform == \"linux\" or sys_platform == \"darwin\") and implementation_name != \"PyPy\"" +groups = ["main", "qm-dev", "test"] files = [ {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"}, {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}, ] +markers = {main = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and implementation_name != \"PyPy\" and (sys_platform == \"linux\" or sys_platform == \"darwin\")", qm-dev = "(sys_platform == \"linux\" or sys_platform == \"darwin\") and (platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and implementation_name != \"PyPy\"", test = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and implementation_name != \"PyPy\" and (sys_platform == \"linux\" or sys_platform == \"darwin\")"} [[package]] name = "pydantic" @@ -2464,7 +2638,7 @@ version = "2.11.9" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "pydantic-2.11.9-py3-none-any.whl", hash = "sha256:c42dd626f5cfc1c6950ce6205ea58c93efa406da65f479dcb4029d5934857da2"}, {file = "pydantic-2.11.9.tar.gz", hash = "sha256:6b8ffda597a14812a7975c90b82a8a2e777d9257aba3453f973acd3c032a18e2"}, @@ -2486,7 +2660,7 @@ version = "2.33.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, @@ -2598,7 +2772,7 @@ version = "2.11.0" description = "Settings management using Pydantic" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c"}, {file = "pydantic_settings-2.11.0.tar.gz", hash = "sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180"}, @@ -2637,7 +2811,7 @@ version = "3.2.5" description = "pyparsing - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e"}, {file = "pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6"}, @@ -2652,7 +2826,7 @@ version = "1.2.0" description = "Wrappers to call pyproject.toml-based build backend hooks." optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913"}, {file = "pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8"}, @@ -2706,7 +2880,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -2721,7 +2895,7 @@ version = "1.1.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc"}, {file = "python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab"}, @@ -2736,7 +2910,7 @@ version = "3.3.0" description = "JSON Log Formatter for the Python Logging Package" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "python_json_logger-3.3.0-py3-none-any.whl", hash = "sha256:dd980fae8cffb24c13caf6e158d3d61c0d6d22342f932cb6e9deedab3d35eec7"}, {file = "python_json_logger-3.3.0.tar.gz", hash = "sha256:12b7e74b17775e7d565129296105bbe3910842d9d0eb083fc83a6a617aa8df84"}, @@ -2751,7 +2925,7 @@ version = "0.1.10" description = "A Fast, spec compliant Python 3.12+ tokenizer that runs on older Pythons." optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "pytokens-0.1.10-py3-none-any.whl", hash = "sha256:db7b72284e480e69fb085d9f251f66b3d2df8b7166059261258ff35f50fb711b"}, {file = "pytokens-0.1.10.tar.gz", hash = "sha256:c9a4bfa0be1d26aebce03e6884ba454e842f186a59ea43a6d3b25af58223c044"}, @@ -2760,13 +2934,25 @@ files = [ [package.extras] dev = ["black", "build", "mypy", "pytest", "pytest-cov", "setuptools", "tox", "twine", "wheel"] +[[package]] +name = "pytz" +version = "2025.2" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +groups = ["test"] +files = [ + {file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"}, + {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, +] + [[package]] name = "pywin32-ctypes" version = "0.2.3" description = "A (partial) reimplementation of pywin32 using ctypes/cffi" optional = false python-versions = ">=3.6" -groups = ["main"] +groups = ["main", "qm-dev", "test"] markers = "sys_platform == \"win32\"" files = [ {file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"}, @@ -2779,7 +2965,7 @@ version = "6.0.3" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "qm-dev", "test"] files = [ {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, @@ -2862,7 +3048,7 @@ version = "2.1.6" description = "SDK to control an Octave with QUA" optional = false python-versions = "<3.13,>=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "qm_octave-2.1.6-py3-none-any.whl", hash = "sha256:2e3df8085330ecf0eb1cc3dd661ebec7b3505959d0fabb3a2dc6175ec7c7cba3"}, {file = "qm_octave-2.1.6.tar.gz", hash = "sha256:8a16e6a8c76b060bc2856020a3f54f2e6d936ffc24e2b7963376ce990a8110be"}, @@ -2880,7 +3066,7 @@ version = "1.2.3" description = "QUA language SDK to control a Quantum Computer" optional = false python-versions = "<3.13,>=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "qm_qua-1.2.3-py3-none-any.whl", hash = "sha256:d719aa0f6ca8dc02e3f9941103942d2f260390ba1527857e08e164cd5e1e1df1"}, {file = "qm_qua-1.2.3.tar.gz", hash = "sha256:529cbb443c04f895cd9b2b663ac4617cb221dbdd490220d1de955a2a9bf1c9c5"}, @@ -2912,7 +3098,7 @@ version = "0.20.0" description = "The qualang_tools package includes various tools related to QUA programs in Python" optional = false python-versions = "<3.13,>=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "qualang_tools-0.20.0-py3-none-any.whl", hash = "sha256:0dc71d2c00eaa98da86b70e082cf0c59d9d61b1494d74bc5aee6b07b8f1bff9c"}, {file = "qualang_tools-0.20.0.tar.gz", hash = "sha256:23de4e0dc81387e45803afdf58c51f1a9c6f8b6ea650becda7d3b31904b0d342"}, @@ -2933,48 +3119,125 @@ interplot = ["dill (>=0.3.4,<0.4.0)", "ipython (>=8.10.0,<9.0.0)", "pypiwin32 (> two-qubit-rb = ["cirq (==1.2.0) ; python_version >= \"3.10\"", "tqdm (>=4.67.1,<5.0.0)", "xarray (>=2023.0.0,<2024.0.0)"] [[package]] -name = "qualibrate-config" -version = "0.1.7" -description = "Configuration for Qualibrate" +name = "qualibrate" +version = "0.3.1" +description = "Qubit calibration software" optional = false -python-versions = "<4.0,>=3.10" -groups = ["main"] +python-versions = "<3.13,>=3.10" +groups = ["test"] files = [ - {file = "qualibrate_config-0.1.7-py3-none-any.whl", hash = "sha256:788d86c8c93680c3188b03c74b817c3e8798be5e0454daabb18d7428cfeb4fb6"}, - {file = "qualibrate_config-0.1.7.tar.gz", hash = "sha256:82f8f46ad3526ee4e9b39f9dc57a0430461f6a17cfdedef59b4bdf431da59faf"}, + {file = "qualibrate-0.3.1-py3-none-any.whl", hash = "sha256:aa2adde1a62beb27d99681096a46cb71c5a45abf90e6928f1c307490f2801a67"}, + {file = "qualibrate-0.3.1.tar.gz", hash = "sha256:d95fb8557bb5c410cb8431e944c91c9abe763960f6b6ac6cf6041d0eb8668f65"}, ] [package.dependencies] +a2wsgi = ">=1.10.8,<2.0.0" click = ">=8.1.7,<8.2.0" -jsonpatch = ">=1.33,<2.0" -pydantic = ">=2.9.2,<3.0.0" -pydantic-settings = ">=2.6.1,<3.0.0" +fastapi = ">=0.117.1,<0.118.0" +pydantic = ">=2.11.9,<3.0.0" +pydantic-settings = ">=2.11.0,<3.0.0" +qualibrate-app = "0.4.1" +qualibrate-config = "0.1.8" +qualibrate-core = "0.4.1" +qualibrate-runner = "0.4.0" tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""} tomli-w = ">=1.0.0,<2.0.0" +uvicorn = ">=0.37.0,<0.38.0" [[package]] -name = "qualibrate-core" -version = "0.4.0" +name = "qualibrate-app" +version = "0.4.1" description = "" optional = false python-versions = "<3.13,>=3.10" -groups = ["main"] +groups = ["test"] files = [ - {file = "qualibrate_core-0.4.0-py3-none-any.whl", hash = "sha256:82a54fe773ddbad91741e326e60d240a9ae6a8578cf57783dac3bc18e6b49008"}, - {file = "qualibrate_core-0.4.0.tar.gz", hash = "sha256:17d34b38c58bf09729de84b2646c1f54098bbd179ae313699a7c32f3c2832600"}, + {file = "qualibrate_app-0.4.1-py3-none-any.whl", hash = "sha256:7157d3996ddfa38fad551666f14d368beac6b544c76fbc3cf5c2d1f84e26d49f"}, + {file = "qualibrate_app-0.4.1.tar.gz", hash = "sha256:f948bfd764042d158ad59e59889a8560c9674813121800f4594e4d466cbeb829"}, ] [package.dependencies] -datamodel-code-generator = ">=0.33.0,<0.34.0" +click = ">=8.1.7,<8.2.0" +fastapi = ">=0.117.1,<0.118.0" jsonpatch = ">=1.33,<2.0" -jsonpointer = ">=3.0.0,<4.0.0" -matplotlib = ">=3.10.6,<4.0.0" -networkx = ">=3.4.2,<3.5.0" -pydantic = ">=2.9.2,<3.0.0" -python-json-logger = ">=3.2.1,<4.0.0" -qualang-tools = {version = ">=0.20.0", markers = "python_version >= \"3.10\" and python_version < \"3.13\""} qualibrate-config = ">=0.1.7,<0.2.0" -types-networkx = ">=3.4.2.20250527,<3.5.0.0" +requests = ">=2.32.4,<3.0.0" +tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""} +tomli-w = ">=1.0.0,<2.0.0" +uvicorn = ">=0.37.0,<0.38.0" + +[[package]] +name = "qualibrate-config" +version = "0.1.8" +description = "Configuration for Qualibrate" +optional = false +python-versions = "^3.10" +groups = ["main", "qm-dev", "test"] +files = [] +develop = true + +[package.dependencies] +click = "^8.1.7,<8.2.0" +jsonpatch = "^1.33" +pydantic = "^2.9.2" +pydantic-settings = "^2.6.1" +tomli = {version = "^2.0.1", markers = "python_version < \"3.11\""} +tomli-w = "^1.0.0" + +[package.source] +type = "directory" +url = "../qualibrate-config" + +[[package]] +name = "qualibrate-core" +version = "0.4.1" +description = "" +optional = false +python-versions = "^3.10,<3.13" +groups = ["main", "qm-dev", "test"] +files = [] +develop = true + +[package.dependencies] +datamodel-code-generator = "^0.33.0" +jsonpatch = "^1.33" +jsonpointer = "^3.0.0" +matplotlib = "^3.10.6" +networkx = "~3.4.2" +pydantic = "^2.9.2" +python-json-logger = "^3.2.1" +qualang-tools = {version = ">=0.20.0", markers = "python_version >= \"3.10\" and python_version < \"3.13\""} +qualibrate-config = "^0.1.7,<0.2.0" +types-networkx = "~3.4.2.20250527" + +[package.source] +type = "directory" +url = "../qualibrate-core" + +[[package]] +name = "qualibration-libs" +version = "0.2.1" +description = "Utility library supporting calibration nodes and graphs for the QUAlibration graphs platform." +optional = false +python-versions = ">=3.9, <3.13" +groups = ["test"] +files = [] +develop = true + +[package.dependencies] +colorama = ">=0.4.6" +lmfit = ">=1.3.3" +matplotlib = ">=3.10.0" +numpy = ">=1.20.0" +qm-qua = ">=1.2.2" +qualibrate = ">=0.2.1" +quam-builder = ">=0.1.2" +scipy = ">=1.13.1" +xarray = ">=2024.7.0" + +[package.source] +type = "directory" +url = "../qualibration-libs" [[package]] name = "quam" @@ -2982,7 +3245,7 @@ version = "0.4.2" description = "Quantum Abstract Machine (QUAM) facilitates development of abstraction layers in experiments." optional = false python-versions = "<3.13,>=3.9" -groups = ["main"] +groups = ["main", "test"] files = [ {file = "quam-0.4.2-py3-none-any.whl", hash = "sha256:4651c97f3a0f601f02e7d4d980973bc9eeab47af8954a1a776f4ba6a6ca601f8"}, {file = "quam-0.4.2.tar.gz", hash = "sha256:0804833adc1813b41d78b14183ddec5135bc97d013fce2744041712e69e668a9"}, @@ -3000,13 +3263,35 @@ build = ["build (>=1.2.1)", "setuptools (>=71)", "setuptools-scm (>=8.1.0)"] dev = ["black (>=23.7.0)", "flake8 (>=5.0.1)", "ipykernel (>=6.24.0)", "pyproject-flake8 (>=5.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=4.1.0)", "pytest-mock (>=3.6.1)"] docs = ["mkdocs-gen-files", "mkdocs-jupyter", "mkdocstrings[python] (>=0.18)"] +[[package]] +name = "quam-builder" +version = "0.2.0" +description = "A Python tool designed to programmatically construct QUAM (Quantum Abstract Machine) configurations for the Quantum Orchestration Platform (QOP)." +optional = false +python-versions = ">=3.9,<3.13" +groups = ["test"] +files = [] +develop = false + +[package.dependencies] +qm-qua = ">=1.2.2" +qualang-tools = ">=0.19.0" +quam = ">=0.4.0" +xarray = ">=2024.7.0" + +[package.source] +type = "git" +url = "https://github.com/qua-platform/quam-builder.git" +reference = "HEAD" +resolved_reference = "9c924713e0255dd293ac1bc18d2dbf38f1c128bd" + [[package]] name = "rapidfuzz" version = "3.14.1" description = "rapid fuzzy string matching" optional = false python-versions = ">=3.10" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "rapidfuzz-3.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:489440e4b5eea0d150a31076eb183bed0ec84f934df206c72ae4fc3424501758"}, {file = "rapidfuzz-3.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eff22cc938c3f74d194df03790a6c3325d213b28cf65cdefd6fdeae759b745d5"}, @@ -3111,7 +3396,7 @@ version = "2.32.5" description = "Python HTTP for Humans." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, @@ -3133,7 +3418,7 @@ version = "1.0.0" description = "A utility belt for advanced users of python-requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, @@ -3177,7 +3462,7 @@ version = "1.7.2" description = "A set of python modules for machine learning and data mining" optional = false python-versions = ">=3.10" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "scikit_learn-1.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b33579c10a3081d076ab403df4a4190da4f4432d443521674637677dc91e61f"}, {file = "scikit_learn-1.7.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:36749fb62b3d961b1ce4fedf08fa57a1986cd409eff2d783bca5d4b9b5fce51c"}, @@ -3233,7 +3518,7 @@ version = "1.15.3" description = "Fundamental algorithms for scientific computing in Python" optional = false python-versions = ">=3.10" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c"}, {file = "scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253"}, @@ -3297,7 +3582,7 @@ version = "3.4.0" description = "Python bindings to FreeDesktop.org Secret Service API" optional = false python-versions = ">=3.10" -groups = ["main"] +groups = ["main", "qm-dev", "test"] markers = "sys_platform == \"linux\"" files = [ {file = "secretstorage-3.4.0-py3-none-any.whl", hash = "sha256:0e3b6265c2c63509fb7415717607e4b2c9ab767b7f344a57473b779ca13bd02e"}, @@ -3314,7 +3599,7 @@ version = "1.5.4" description = "Tool to Detect Surrounding Shell" optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, @@ -3326,7 +3611,7 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -3338,7 +3623,7 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -3350,7 +3635,7 @@ version = "0.48.0" description = "The little ASGI library that shines." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "test"] files = [ {file = "starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659"}, {file = "starlette-0.48.0.tar.gz", hash = "sha256:7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46"}, @@ -3369,7 +3654,7 @@ version = "9.1.2" description = "Retry code until it succeeds" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138"}, {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"}, @@ -3385,7 +3670,7 @@ version = "3.6.0" description = "threadpoolctl" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb"}, {file = "threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e"}, @@ -3397,7 +3682,7 @@ version = "4.8.2" description = "TinyDB is a tiny, document oriented database optimized for your happiness :)" optional = false python-versions = "<4.0,>=3.8" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "tinydb-4.8.2-py3-none-any.whl", hash = "sha256:f97030ee5cbc91eeadd1d7af07ab0e48ceb04aa63d4a983adbaca4cba16e86c3"}, {file = "tinydb-4.8.2.tar.gz", hash = "sha256:f7dfc39b8d7fda7a1ca62a8dbb449ffd340a117c1206b68c50b1a481fb95181d"}, @@ -3409,7 +3694,7 @@ version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" -groups = ["main", "dev", "test"] +groups = ["main", "dev", "qm-dev", "test"] files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -3444,7 +3729,7 @@ files = [ {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] -markers = {main = "python_version <= \"3.11\"", dev = "python_version == \"3.10\"", test = "python_version == \"3.10\""} +markers = {main = "python_version < \"3.12\"", dev = "python_version == \"3.10\"", qm-dev = "python_version < \"3.12\"", test = "python_version < \"3.12\""} [[package]] name = "tomli-w" @@ -3452,7 +3737,7 @@ version = "1.2.0" description = "A lil' TOML writer" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90"}, {file = "tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021"}, @@ -3464,7 +3749,7 @@ version = "0.13.3" description = "Style preserving TOML library" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0"}, {file = "tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1"}, @@ -3476,7 +3761,7 @@ version = "2025.9.11.17" description = "Canonical source for classifiers on PyPI (pypi.org)." optional = false python-versions = "*" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "trove_classifiers-2025.9.11.17-py3-none-any.whl", hash = "sha256:5d392f2d244deb1866556457d6f3516792124a23d1c3a463a2e8668a5d1c15dd"}, {file = "trove_classifiers-2025.9.11.17.tar.gz", hash = "sha256:931ca9841a5e9c9408bc2ae67b50d28acf85bef56219b56860876dd1f2d024dd"}, @@ -3488,7 +3773,7 @@ version = "4.4.4" description = "Run-time type checker for Python" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "typeguard-4.4.4-py3-none-any.whl", hash = "sha256:b5f562281b6bfa1f5492470464730ef001646128b180769880468bd84b68b09e"}, {file = "typeguard-4.4.4.tar.gz", hash = "sha256:3a7fd2dffb705d4d0efaed4306a704c89b9dee850b688f060a8b1615a79e5f74"}, @@ -3503,7 +3788,7 @@ version = "3.4.2.20250527" description = "Typing stubs for networkx" optional = false python-versions = ">=3.10" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "types_networkx-3.4.2.20250527-py3-none-any.whl", hash = "sha256:3716f750e61bf46ceba2a43474d7f9b61f306274a243cee6cc2aa9f4fb37ab41"}, {file = "types_networkx-3.4.2.20250527.tar.gz", hash = "sha256:484914f0dee6ce8d87bda494f641a9227e954d716d2f330bfcbf69c2431b646d"}, @@ -3518,12 +3803,11 @@ version = "4.15.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" -groups = ["main", "dev", "test"] +groups = ["main", "dev", "qm-dev", "test"] files = [ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, ] -markers = {test = "python_version == \"3.10\""} [[package]] name = "typing-inspection" @@ -3531,7 +3815,7 @@ version = "0.4.1" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"}, {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"}, @@ -3540,13 +3824,43 @@ files = [ [package.dependencies] typing-extensions = ">=4.12.0" +[[package]] +name = "tzdata" +version = "2025.2" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +groups = ["test"] +files = [ + {file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"}, + {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}, +] + +[[package]] +name = "uncertainties" +version = "3.2.3" +description = "calculations with values with uncertainties, error propagation" +optional = false +python-versions = ">=3.8" +groups = ["test"] +files = [ + {file = "uncertainties-3.2.3-py3-none-any.whl", hash = "sha256:313353900d8f88b283c9bad81e7d2b2d3d4bcc330cbace35403faaed7e78890a"}, + {file = "uncertainties-3.2.3.tar.gz", hash = "sha256:76a5653e686f617a42922d546a239e9efce72e6b35411b7750a1d12dcba03031"}, +] + +[package.extras] +all = ["uncertainties[arrays,doc,test]"] +arrays = ["numpy"] +doc = ["python-docs-theme", "sphinx", "sphinx-copybutton"] +test = ["pytest", "pytest_codspeed", "pytest_cov", "scipy"] + [[package]] name = "urllib3" version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, @@ -3564,7 +3878,7 @@ version = "0.37.0" description = "The lightning-fast ASGI server." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "test"] files = [ {file = "uvicorn-0.37.0-py3-none-any.whl", hash = "sha256:913b2b88672343739927ce381ff9e2ad62541f9f8289664fa1d1d3803fa2ce6c"}, {file = "uvicorn-0.37.0.tar.gz", hash = "sha256:4115c8add6d3fd536c8ee77f0e14a7fd2ebba939fed9b02583a97f80648f9e13"}, @@ -3584,7 +3898,7 @@ version = "20.34.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "virtualenv-20.34.0-py3-none-any.whl", hash = "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026"}, {file = "virtualenv-20.34.0.tar.gz", hash = "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a"}, @@ -3679,13 +3993,39 @@ files = [ {file = "websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee"}, ] +[[package]] +name = "xarray" +version = "2024.11.0" +description = "N-D labeled arrays and datasets in Python" +optional = false +python-versions = ">=3.10" +groups = ["test"] +files = [ + {file = "xarray-2024.11.0-py3-none-any.whl", hash = "sha256:6ee94f63ddcbdd0cf3909d1177f78cdac756640279c0e32ae36819a89cdaba37"}, + {file = "xarray-2024.11.0.tar.gz", hash = "sha256:1ccace44573ddb862e210ad3ec204210654d2c750bec11bbe7d842dfc298591f"}, +] + +[package.dependencies] +numpy = ">=1.24" +packaging = ">=23.2" +pandas = ">=2.1" + +[package.extras] +accel = ["bottleneck", "flox", "numba (>=0.54)", "numbagg", "opt_einsum", "scipy"] +complete = ["xarray[accel,etc,io,parallel,viz]"] +dev = ["hypothesis", "jinja2", "mypy", "pre-commit", "pytest", "pytest-cov", "pytest-env", "pytest-timeout", "pytest-xdist", "ruff", "sphinx", "sphinx_autosummary_accessors", "xarray[complete]"] +etc = ["sparse"] +io = ["cftime", "fsspec", "h5netcdf", "netCDF4", "pooch", "pydap ; python_version < \"3.10\"", "scipy", "zarr"] +parallel = ["dask[complete]"] +viz = ["cartopy", "matplotlib", "nc-time-axis", "seaborn"] + [[package]] name = "xattr" version = "1.2.0" description = "Python wrapper for extended filesystem attributes" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "qm-dev", "test"] markers = "sys_platform == \"darwin\"" files = [ {file = "xattr-1.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3df4d8d91e2996c3c72a390ec82e8544acdcb6c7df67b954f1736ff37ea4293e"}, @@ -3772,7 +4112,7 @@ version = "3.23.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] markers = "python_version < \"3.12\"" files = [ {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, @@ -3793,7 +4133,7 @@ version = "0.25.0" description = "Zstandard bindings for Python" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "zstandard-0.25.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e59fdc271772f6686e01e1b3b74537259800f57e24280be3f29c8a0deb1904dd"}, {file = "zstandard-0.25.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4d441506e9b372386a5271c64125f72d5df6d2a8e8a2a45a0ae09b03cb781ef7"}, @@ -3902,4 +4242,4 @@ cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and pyt [metadata] lock-version = "2.1" python-versions = "^3.10,<3.13" -content-hash = "74cb247423a030ecd6aeedc032fb871730ed8e0d1c6e9a77e953a06005f33147" +content-hash = "4657da2105785294b056c411f4051f2519da097de910c60faa3289f85236df02" diff --git a/pyproject.toml b/pyproject.toml index ad5c0e7..ec042fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,10 +28,13 @@ poethepoet = "^0.37.0" pytest = "^8.4.2" pytest-mock = "^3.15.1" freezegun = "^1.2.0" +xarray = "^2024.1.0" +quam-builder = {git = "https://github.com/qua-platform/quam-builder.git"} +qualibration-libs = {path = "../qualibration-libs", develop = true} -#[tool.poetry.group.qm-dev.dependencies] -#qualibrate-config = {path = "../qualibrate-config", develop = true} -#qualibrate-core = {path = "../qualibrate-core", develop = true} +[tool.poetry.group.qm-dev.dependencies] +qualibrate-config = {path = "../qualibrate-config", develop = true} +qualibrate-core = {path = "../qualibrate-core", develop = true} [tool.poetry.scripts] qualibrate-runner = "qualibrate_runner.__main__:main" diff --git a/tests/conftest.py b/tests/conftest.py index fd2543e..e73887d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,6 +6,7 @@ """ from datetime import datetime, timezone +from pathlib import Path from unittest.mock import Mock import pytest @@ -193,3 +194,29 @@ class FullParams(BaseModel): parameters: WorkflowParams return FullParams + + +@pytest.fixture(scope="function") +def test_library(): + """ + Provide a QualibrationLibrary instance loaded with test nodes. + + This fixture creates a library pointing to tests/fixtures/test_nodes, + which properly sets filepath and other metadata on the test nodes + during the scan process. + + Scope is explicitly set to "function" to ensure each test gets fresh + node instances, maintaining test independence. + """ + from qualibrate import QualibrationLibrary + + # Get path to test_nodes directory + test_nodes_path = Path(__file__).parent / "fixtures" / "test_nodes" + + # Create library with set_active=False to avoid interfering with + # other tests + library = QualibrationLibrary( + library_folder=test_nodes_path, set_active=False + ) + + return library diff --git a/tests/fixtures/__init__.py b/tests/fixtures/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/test_nodes/__init__.py b/tests/fixtures/test_nodes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/test_nodes/node_with_actions.py b/tests/fixtures/test_nodes/node_with_actions.py new file mode 100644 index 0000000..029ea7b --- /dev/null +++ b/tests/fixtures/test_nodes/node_with_actions.py @@ -0,0 +1,149 @@ +""" +Test node with multiple actions demonstrating action chaining and skipping. + +This node tests: +- Multiple actions that build on each other +- Action skipping via skip_actions parameter +- Namespace accumulation across actions +- Conditional action execution via skip_if +""" + +from unittest.mock import Mock +import numpy as np +import xarray as xr +from pydantic import Field + +from qualibrate import NodeParameters, QualibrationNode +from qualang_tools.units import unit +from qualang_tools.results import progress_counter +from qualibration_libs.data import XarrayDataFetcher + + +class Parameters(NodeParameters): + """Parameters for action test node.""" + + amplitude: float = Field(default=0.5, ge=0.0, le=1.0) + frequency: float = Field(default=5.0e9, gt=0.0) + num_points: int = Field(default=10, ge=1) + num_shots: int = Field(default=1000, ge=1) + frequency_span_in_mhz: float = Field(default=100, gt=0.0) + frequency_step_in_mhz: float = Field(default=0.25, gt=0.0) + update_state: bool = Field( + default=True, + description="Whether to run the state update action", + ) + trigger_error: bool = Field( + default=False, + description="Whether to run the action that raises an error", + ) + error_message: str = Field( + default="Test error from action", + description="Error message to use", + ) + trigger_deep_error: bool = Field( + default=False, + description="Whether to run the action that raises an error inside XarrayDataFetcher", + ) + + +# Create the node +node = QualibrationNode( + name="node_with_actions", + parameters=Parameters(), +) + + +@node.run_action +def prepare_data(node): + """Generate measurement data based on parameters.""" + data = [ + node.parameters.amplitude * i + for i in range(node.parameters.num_points) + ] + return { + "data": data, + "data_length": len(data), + "amplitude": node.parameters.amplitude, + } + + +@node.run_action +def process_data(node): + """Process the prepared data.""" + # Access data from previous action via namespace + raw_data = node.namespace["data"] + processed = [x * 2 for x in raw_data] + mean_value = sum(processed) / len(processed) + + return { + "processed_data": processed, + "mean": mean_value, + "max": max(processed), + "min": min(processed), + } + + +@node.run_action(skip_if=not node.parameters.trigger_deep_error) +def execute_qua_program(node): + mock_job = Mock() + mock_job.result_handles.keys.return_value = [] + + # Code supposed to display the progress bar (won't work) + u = unit(coerce_to_integer=True) + span = node.parameters.frequency_span_in_mhz * u.MHz + step = node.parameters.frequency_step_in_mhz * u.MHz + dfs = np.arange(-span // 2, +span // 2, step) + sweep_axes = { + "qubit": xr.DataArray(["q1", "q2"]), + "detuning": xr.DataArray(dfs, attrs={"long_name": "readout frequency", "units": "Hz"}), + } + data_fetcher = XarrayDataFetcher(mock_job, sweep_axes) + for dataset in data_fetcher: + progress_counter( + data_fetcher["nonexistent_key"], # This will raise KeyError from inside XarrayDataFetcher + node.parameters.num_shots, + start_time=data_fetcher.t_start, + ) + node.log(mock_job.execution_report()) + + return {"ds_raw": dataset} + + +@node.run_action(skip_if=not node.parameters.update_state) +def update_machine_state(node): + """Update quantum machine state (if enabled).""" + # This action only runs if update_state is True + state_updated = False + + if node.machine: + # Would update machine parameters here in real scenario + # For testing, just track that we would update + state_updated = True + + return { + "state_updated": state_updated, + "frequency_used": node.parameters.frequency, + } + + +@node.run_action(skip_if=not node.parameters.trigger_error) +def process_data_with_error(node): + """Process data - this action raises an error when executed.""" + # This action always fails when it runs + raise ValueError(node.parameters.error_message) + + +@node.run_action +def finalize_results(node): + """Collect all results into final summary.""" + # Gather all data from namespace + summary = { + "total_actions_run": 4, # This action is always last + "data_points": node.namespace.get("data_length", 0), + "mean_value": node.namespace.get("mean", 0), + } + + return {"summary": summary} + + +node.results = node.namespace diff --git a/tests/fixtures/test_nodes/test_node_can_raise_in_body.py b/tests/fixtures/test_nodes/test_node_can_raise_in_body.py new file mode 100644 index 0000000..f0e45f9 --- /dev/null +++ b/tests/fixtures/test_nodes/test_node_can_raise_in_body.py @@ -0,0 +1,58 @@ +""" +Simple test node without any actions. + +This node provides basic functionality for testing node execution without the complexity of the action system. + +It can raise an exception in the main body, and can test error handling in the orchestration layer when the error +occurs in the node's main body, before any actions are executed. +""" + +from pydantic import Field + +from qualibrate import NodeParameters, QualibrationNode + + +class Parameters(NodeParameters): + """Parameters for simple test""" + amplitude: float = Field(default=0.5, ge=0.0, le=1.0) + frequency: float = Field(default=5.0e9, gt=0.0) + num_points: int = Field(default=10, ge=1) + + """Parameters for error test""" + should_fail: bool = Field( + default=False, + description="Whether the node should raise an error in body", + ) + error_message: str = Field( + default="Test error from node body", + description="Error message to use", + ) + error_type: str = Field( + default="ValueError", + description="Type of error to raise (ValueError or RuntimeError)", + ) + + +# Create the node +node = QualibrationNode( + name="node_raises_in_body", + parameters=Parameters(), +) + +# Raise error in main body if configured +if node.parameters.should_fail: + error_msg = node.parameters.error_message + if node.parameters.error_type == "RuntimeError": + raise RuntimeError(error_msg) + else: + raise ValueError(error_msg) + +# This code only runs if we didn't fail above +data = [node.parameters.amplitude * i for i in range(node.parameters.num_points)] +node.results = { + "data": data, + "length": len(data), + "amplitude": node.parameters.amplitude, + "frequency": node.parameters.frequency, + "error_raised": False +} diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/integration/test_run_job_integration.py b/tests/integration/test_run_job_integration.py new file mode 100644 index 0000000..c750efb --- /dev/null +++ b/tests/integration/test_run_job_integration.py @@ -0,0 +1,279 @@ +""" +Integration tests for run_job.py using real node definition files. + +These tests verify the full execution chain: +run_job → node.run() → ActionManager → Action + +They use real parametric node files from tests/fixtures/test_nodes/ +to ensure the complete system works end-to-end in non-interactive mode. + +Test nodes are loaded through QualibrationLibrary to ensure proper +initialization including filepath setting. +""" + +import pytest + +from qualibrate_runner.config.models import RunStatusEnum +from qualibrate_runner.core.run_job import run_node + + +class TestSimpleNodeExecution: + """Integration tests for simple nodes without action system.""" + + def test_simple_node_success(self, test_library, fresh_state): + """Test successful execution of simple node.""" + node = test_library.nodes["node_raises_in_body"] + + # Execute through run_node (full orchestration) + run_node(node, {}, fresh_state) + + # Verify state tracking + assert fresh_state.last_run.status == RunStatusEnum.FINISHED + assert fresh_state.last_run.completed_at is not None + assert fresh_state.last_run.error is None + + # Verify node results + assert node.results is not None + assert "data" in node.results + assert node.results["error_raised"] is False + + def test_simple_node_with_different_parameters( + self, test_library, fresh_state + ): + """Test simple node with runtime parameter override.""" + node = test_library.nodes["node_raises_in_body"] + + # Execute with custom parameters + params = {"amplitude": 0.8, "num_points": 5} + run_node(node, params, fresh_state) + + # Verify execution succeeded + assert fresh_state.last_run.status == RunStatusEnum.FINISHED + assert fresh_state.last_run.passed_parameters == params + + def test_simple_node_error_in_body(self, test_library, fresh_state): + """Test error capture when node body raises exception.""" + node = test_library.nodes["node_raises_in_body"] + + # Execute with should_fail=True + params = { + "should_fail": True, + "error_message": "Test error from body", + "error_type": "ValueError", + } + + with pytest.raises(ValueError, match="Test error from body"): + run_node(node, params, fresh_state) + + # Verify error was captured in state + assert fresh_state.last_run.status == RunStatusEnum.ERROR + assert fresh_state.last_run.error is not None + assert "ValueError" in fresh_state.last_run.error.error_class + assert "Test error from body" in fresh_state.last_run.error.message + + +class TestNodeWithActionsExecution: + """Integration tests for nodes with action system.""" + + def test_all_actions_execute(self, test_library, fresh_state): + """Test full execution with all default actions.""" + node = test_library.nodes["node_with_actions"] + + # Execute with defaults + run_node(node, {}, fresh_state) + + # Verify state tracking + assert fresh_state.last_run.status == RunStatusEnum.FINISHED + assert fresh_state.last_run.error is None + + # Verify all default actions executed + assert "data" in node.namespace + assert "processed_data" in node.namespace + assert "state_updated" in node.namespace # update_state defaults True + assert "summary" in node.namespace + + def test_conditional_action_skip(self, test_library, fresh_state): + """Test conditional action skipping via parameters.""" + node = test_library.nodes["node_with_actions"] + + # Execute with update_state=False to skip that action + params = {"update_state": False} + run_node(node, params, fresh_state) + + # Verify execution succeeded + assert fresh_state.last_run.status == RunStatusEnum.FINISHED + + # Verify normal actions ran + assert "data" in node.namespace + assert "processed_data" in node.namespace + + # Verify conditional action was skipped + assert "state_updated" not in node.namespace + + def test_action_raises_error(self, test_library, fresh_state): + """Test error capture when action raises exception.""" + node = test_library.nodes["node_with_actions"] + + # Execute with trigger_error=True + params = { + "trigger_error": True, + "error_message": "Test error from action", + } + + with pytest.raises(ValueError, match="Test error from action"): + run_node(node, params, fresh_state) + + # Verify error was captured + assert fresh_state.last_run.status == RunStatusEnum.ERROR + assert fresh_state.last_run.error is not None + assert "ValueError" in fresh_state.last_run.error.error_class + + def test_error_from_external_library(self, test_library, fresh_state): + """Test error propagation from external library code.""" + node = test_library.nodes["node_with_actions"] + + # Execute with trigger_deep_error=True + params = {"trigger_deep_error": True} + + # This should raise an error from inside XarrayDataFetcher + with pytest.raises(KeyError): + run_node(node, params, fresh_state) + + # Verify error was captured + assert fresh_state.last_run.status == RunStatusEnum.ERROR + assert fresh_state.last_run.error is not None + + +class TestNamespaceAccumulation: + """Integration tests for namespace data flow across actions.""" + + def test_namespace_accumulates_across_actions( + self, test_library, fresh_state + ): + """Test that namespace accumulates data from all actions.""" + node = test_library.nodes["node_with_actions"] + + run_node(node, {}, fresh_state) + + # Verify data from each action is in namespace + # From prepare_data + assert "data" in node.namespace + assert "data_length" in node.namespace + + # From process_data + assert "processed_data" in node.namespace + assert "mean" in node.namespace + + # From finalize_results + assert "summary" in node.namespace + + def test_later_actions_use_earlier_data(self, test_library, fresh_state): + """Test that actions can access data from previous actions.""" + node = test_library.nodes["node_with_actions"] + + run_node(node, {}, fresh_state) + + # process_data uses data from prepare_data + raw_data = node.namespace["data"] + processed_data = node.namespace["processed_data"] + + # Verify processing is correct (doubles each value) + assert len(processed_data) == len(raw_data) + for i in range(len(raw_data)): + assert processed_data[i] == raw_data[i] * 2 + + +class TestStateTracking: + """Integration tests for state lifecycle management.""" + + def test_state_lifecycle_success(self, test_library, fresh_state): + """Test state transitions during successful execution.""" + node = test_library.nodes["node_with_actions"] + + # Initial state + assert fresh_state.last_run is None + + # Execute + run_node(node, {}, fresh_state) + + # Final state + assert fresh_state.last_run is not None + assert fresh_state.last_run.status == RunStatusEnum.FINISHED + assert fresh_state.last_run.started_at is not None + assert fresh_state.last_run.completed_at is not None + assert ( + fresh_state.last_run.completed_at + >= fresh_state.last_run.started_at + ) + + def test_state_lifecycle_with_error(self, test_library, fresh_state): + """Test state updates when execution fails.""" + node = test_library.nodes["node_with_actions"] + + # Execute with error + params = {"trigger_error": True} + + with pytest.raises(ValueError): + run_node(node, params, fresh_state) + + # State should be updated even on error + assert fresh_state.last_run is not None + assert fresh_state.last_run.status == RunStatusEnum.ERROR + assert fresh_state.last_run.error is not None + assert fresh_state.last_run.completed_at is not None + + def test_passed_parameters_captured(self, test_library, fresh_state): + """Test that passed parameters are captured in state.""" + node = test_library.nodes["node_with_actions"] + + params = { + "amplitude": 0.7, + "num_points": 8, + "update_state": False, + } + + run_node(node, params, fresh_state) + + # Verify parameters were captured + assert fresh_state.last_run.passed_parameters == params + + +class TestParametricBehavior: + """Integration tests for parametric node behavior.""" + + def test_namespace_persists_across_runs(self, test_library, fresh_state): + """Test that namespace accumulates across multiple runs""" + node = test_library.nodes["node_with_actions"] + + # First execution: normal (with update_state=True by default) + run_node(node, {}, fresh_state) + assert fresh_state.last_run.status == RunStatusEnum.FINISHED + assert "state_updated" in node.namespace + first_run_keys = set(node.namespace.keys()) + + # Second execution: namespace should accumulate, not reset + run_node(node, {"update_state": False}, fresh_state) + assert fresh_state.last_run.status == RunStatusEnum.FINISHED + # Verify namespace accumulated - all keys from first run still present + assert first_run_keys.issubset(set(node.namespace.keys())) + # "state_updated" still present from first run (correct behavior) + assert "state_updated" in node.namespace + + def test_error_execution_preserves_namespace( + self, test_library, fresh_state + ): + """Test that namespace is preserved even when execution fails.""" + node = test_library.nodes["node_with_actions"] + + # First execution: normal + run_node(node, {}, fresh_state) + assert fresh_state.last_run.status == RunStatusEnum.FINISHED + successful_run_keys = set(node.namespace.keys()) + + # Second execution: trigger error + with pytest.raises(ValueError): + run_node(node, {"trigger_error": True}, fresh_state) + assert fresh_state.last_run.status == RunStatusEnum.ERROR + + # Namespace from successful run should still be present + assert successful_run_keys.issubset(set(node.namespace.keys())) From e5ec79b70933ca813cbfa041e0e5dba07f2be38c Mon Sep 17 00:00:00 2001 From: Jonatan Kutchinsky Date: Thu, 20 Nov 2025 09:53:01 +0100 Subject: [PATCH 07/26] Adding optional fields 'details_headline' and 'details' to RunError class + a few unit tests --- qualibrate_runner/core/models/common.py | 6 +-- tests/unit/test_models_common.py | 70 +++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/qualibrate_runner/core/models/common.py b/qualibrate_runner/core/models/common.py index 5fec4ca..bf98d00 100644 --- a/qualibrate_runner/core/models/common.py +++ b/qualibrate_runner/core/models/common.py @@ -34,11 +34,9 @@ class RunError(BaseModel): error_class: str = Field(..., description="The class of the error.") message: str = Field(..., description="The error message.") - - # details_headline: str = Field(..., description="The headline of the details.") - # details: str = Field(..., description="The details of the error.") - traceback: list[str] = Field(..., description="The traceback of the error.") + details_headline: str | None = Field(None, description="The headline of the details.") + details: str | None = Field(None, description="The details of the error.") class StateUpdate(BaseModel): diff --git a/tests/unit/test_models_common.py b/tests/unit/test_models_common.py index 4ddbea5..3224076 100644 --- a/tests/unit/test_models_common.py +++ b/tests/unit/test_models_common.py @@ -116,6 +116,76 @@ def test_long_traceback(self): assert len(error.traceback) == 20 + def test_create_with_optional_detail_fields(self, sample_traceback): + """Test creating RunError with optional detail fields.""" + error = RunError( + error_class="ValueError", + message="Invalid configuration", + traceback=sample_traceback, + details_headline="Configuration Error", + details="The 'frequency' parameter must be greater than zero.", + ) + + assert error.error_class == "ValueError" + assert error.details_headline == "Configuration Error" + assert ( + error.details + == "The 'frequency' parameter must be greater than zero." + ) + + def test_create_without_optional_detail_fields(self, sample_traceback): + """Test that optional detail fields default to None.""" + error = RunError( + error_class="ValueError", + message="Test error", + traceback=sample_traceback, + ) + + assert error.details_headline is None + assert error.details is None + + def test_serialization_with_optional_fields(self, sample_traceback): + """Test serialization includes optional fields when present.""" + error = RunError( + error_class="RuntimeError", + message="Something failed", + traceback=sample_traceback, + details_headline="Execution Error", + details="Check the logs for more information", + ) + + serialized = error.model_dump() + assert "details_headline" in serialized + assert serialized["details_headline"] == "Execution Error" + assert "details" in serialized + assert serialized["details"] == "Check the logs for more information" + + def test_deserialization_with_optional_fields(self, sample_traceback): + """Test deserialization with optional fields.""" + data = { + "error_class": "ValueError", + "message": "Test error", + "traceback": sample_traceback, + "details_headline": "Test Headline", + "details": "Test Details", + } + + error = RunError(**data) + assert error.details_headline == "Test Headline" + assert error.details == "Test Details" + + def test_deserialization_without_optional_fields(self, sample_traceback): + """Test deserialization without optional fields.""" + data = { + "error_class": "ValueError", + "message": "Test error", + "traceback": sample_traceback, + } + + error = RunError(**data) + assert error.details_headline is None + assert error.details is None + class TestStateUpdate: """Tests for the StateUpdate model.""" From cb2aa53e6835c9e85ff0cd3bdac61d8651ddcfeb Mon Sep 17 00:00:00 2001 From: Jonatan Kutchinsky Date: Wed, 26 Nov 2025 14:21:19 +0100 Subject: [PATCH 08/26] Copy details for a RunError from the error that occured in a Node into the RunError generated in run_node in run_job.py --- qualibrate_runner/core/run_job.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qualibrate_runner/core/run_job.py b/qualibrate_runner/core/run_job.py index b7163d4..48d0eeb 100644 --- a/qualibrate_runner/core/run_job.py +++ b/qualibrate_runner/core/run_job.py @@ -140,9 +140,13 @@ def run_node( except Exception as ex: # Capture error details for state tracking run_status = RunStatusEnum.ERROR + run_summary = getattr(node, "run_summary", None) + run_summary_error = getattr(run_summary, "error", None) if run_summary else None # Very safe way to access error run_error = RunError( error_class=ex.__class__.__name__, message=str(ex), + details_headline=getattr(run_summary_error, 'details_headline', None) if run_summary_error else None, + details=getattr(run_summary_error, 'details', None) if run_summary_error else None, traceback=traceback.format_tb(ex.__traceback__), ) # Re-raise to allow caller to handle the error From eb5db43297f468e28b91043ec8bc30f1bfea1c7e Mon Sep 17 00:00:00 2001 From: Jonatan Kutchinsky Date: Wed, 26 Nov 2025 15:17:06 +0100 Subject: [PATCH 09/26] More integration tests of failing nodes --- .../test_nodes/node_with_subroutine.py | 91 +++++++++ tests/integration/test_run_job_integration.py | 191 ++++++++++++++++++ 2 files changed, 282 insertions(+) create mode 100644 tests/fixtures/test_nodes/node_with_subroutine.py diff --git a/tests/fixtures/test_nodes/node_with_subroutine.py b/tests/fixtures/test_nodes/node_with_subroutine.py new file mode 100644 index 0000000..2cf89e5 --- /dev/null +++ b/tests/fixtures/test_nodes/node_with_subroutine.py @@ -0,0 +1,91 @@ +""" +Test node with action that calls a subroutine. + +This node tests error handling when the error occurs in a subroutine +that is called from an action, ensuring the full call chain is captured. +""" + +from pydantic import Field + +from qualibrate import NodeParameters, QualibrationNode + + +class Parameters(NodeParameters): + """Parameters for subroutine test.""" + + amplitude: float = Field(default=0.5, ge=0.0, le=1.0) + num_points: int = Field(default=10, ge=1) + trigger_subroutine_error: bool = Field( + default=False, + description="Whether to trigger error in subroutine", + ) + error_message: str = Field( + default="Error from subroutine", + description="Error message to raise", + ) + + +# Create the node +node = QualibrationNode( + name="node_with_subroutine", + parameters=Parameters(), +) + + +def helper_function(amplitude: float, num_points: int) -> list[float]: + """Helper function that processes data. + + This subroutine is called by the action. If parameters trigger an error, + it will raise from within this function. + """ + # Do some processing + data = [amplitude * i for i in range(num_points)] + + # This line will raise if we access out of bounds + # We use this to trigger an error in the subroutine + result = data[num_points] # IndexError: list index out of range + + return data + + +def another_helper(value: float, message: str) -> float: + """Another helper that can raise a ValueError. + + This demonstrates a deeper call stack within the node file. + """ + if value < 0: + raise ValueError(message) + return value * 2 + + +@node.run_action +def prepare_data(node): + """Generate measurement data based on parameters.""" + # Simple data generation without errors + data = [ + node.parameters.amplitude * i for i in range(node.parameters.num_points) + ] + return {"data": data, "data_length": len(data)} + + +@node.run_action(skip_if=not node.parameters.trigger_subroutine_error) +def process_with_subroutine(node): + """Process data using a helper function. + + This action calls helper_function, which will raise an IndexError + when trigger_subroutine_error is True. + """ + # Call the helper function - error will occur inside it + result = helper_function( + node.parameters.amplitude, node.parameters.num_points + ) + return {"processed": result} + + +@node.run_action +def finalize(node): + """Finalize results.""" + return {"status": "completed"} + + +node.results = node.namespace diff --git a/tests/integration/test_run_job_integration.py b/tests/integration/test_run_job_integration.py index c750efb..7f0dc43 100644 --- a/tests/integration/test_run_job_integration.py +++ b/tests/integration/test_run_job_integration.py @@ -277,3 +277,194 @@ def test_error_execution_preserves_namespace( # Namespace from successful run should still be present assert successful_run_keys.issubset(set(node.namespace.keys())) + + +class TestErrorMessaging: + """Integration tests for improved error messaging""" + + def test_error_details_for_action_failure(self, test_library, fresh_state): + """Test error details when error occurs in action (inside library error).""" + node = test_library.nodes["node_with_actions"] + + # Trigger error from inside XarrayDataFetcher + params = {"trigger_deep_error": True} + + with pytest.raises(KeyError): + run_node(node, params, fresh_state) + + # Verify error was captured + assert fresh_state.last_run.status == RunStatusEnum.ERROR + assert fresh_state.last_run.error is not None + error = fresh_state.last_run.error + + # Test CURRENT state + assert error.details_headline is not None + assert error.details is not None + assert len(error.traceback) > 0 + assert "execute_qua_program" in error.details_headline + + assert "execute_qua_program" in error.details + assert "Completed actions" in error.details + assert "prepare_data" in error.details + assert "process_data" in error.details + + # 4. Details should contain simplified traceback (simple format, no code blocks) + assert "Traceback:" in error.details + # Should NOT contain framework files in simplified version + assert "action_manager.py" not in error.details + assert "action.py" not in error.details + + # 5. Details should contain source snippet (simple format, no code blocks) + assert "Source Code:" in error.details + + # 6. Full traceback should be preserved + assert len(error.traceback) > 5 # Should have many frames + # Full traceback SHOULD contain framework files + traceback_str = "".join(error.traceback) + assert ( + "action_manager.py" in traceback_str + or "action.py" in traceback_str + ) + + def test_error_details_for_body_failure(self, test_library, fresh_state): + """Test error details when error occurs in node body (not in action).""" + node = test_library.nodes["node_raises_in_body"] + + # Trigger error in node body + params = { + "should_fail": True, + "error_message": "Test error from body", + "error_type": "ValueError", + } + + with pytest.raises(ValueError, match="Test error from body"): + run_node(node, params, fresh_state) + + # Verify error was captured + assert fresh_state.last_run.status == RunStatusEnum.ERROR + assert fresh_state.last_run.error is not None + error = fresh_state.last_run.error + + # 1. Headline should indicate body error (no action) + assert "body" in error.details_headline or "initialization" in error.details_headline + + # 2. Details should NOT contain action execution summary + # (since no actions were used) + assert "Completed actions" not in error.details or "prepare_data" not in error.details + + # 3. Details should contain source snippet from node file (simple format) + assert "Source Code:" in error.details + + # 4. Details should contain simplified traceback (simple format) + assert "Traceback:" in error.details + # Body errors should not have action framework in simplified version + assert "action_manager.py" not in error.details + + def test_error_details_with_action_history( + self, test_library, fresh_state + ): + """Test error details include completed and skipped actions.""" + node = test_library.nodes["node_with_actions"] + + # Trigger error with some actions completed and some skipped + params = { + "update_state": False, # This action will be skipped + "trigger_error": True, # This will cause the error + } + + with pytest.raises(ValueError): + run_node(node, params, fresh_state) + + # Verify error was captured + assert fresh_state.last_run.status == RunStatusEnum.ERROR + error = fresh_state.last_run.error + + # 1. Should show completed actions before the error + assert "Completed actions" in error.details + assert "prepare_data" in error.details + assert "process_data" in error.details + + # 2. Should show skipped actions + assert "Skipped actions" in error.details + assert "update_machine_state" in error.details + + # 3. Should show which action failed (simple format) + assert "process_data_with_error" in error.details + + def test_error_details_for_action_with_subroutine( + self, test_library, fresh_state + ): + """Test error in subroutine called by action shows full call chain.""" + node = test_library.nodes["node_with_subroutine"] + + # Trigger error in helper_function (subroutine) + params = {"trigger_subroutine_error": True} + + with pytest.raises(IndexError): + run_node(node, params, fresh_state) + + # Verify error was captured + assert fresh_state.last_run.status == RunStatusEnum.ERROR + error = fresh_state.last_run.error + + # 1. Headline should mention the action (not the subroutine) + assert "process_with_subroutine" in error.details_headline + + # 2. Simplified traceback should show BOTH the action AND the subroutine (simple format) + assert "Traceback:" in error.details + # Should include the action function + assert "process_with_subroutine" in error.details + # Should include the subroutine + assert "helper_function" in error.details + + # 3. Source snippet should show the subroutine (where error occurred, simple format) + assert "Source Code:" in error.details + # The error occurs at: result = data[num_points] + # So the snippet should show this line + + # 4. Simplified traceback should NOT include framework code + assert "action_manager.py" not in error.details + + # 5. Full traceback should be preserved with all frames + assert len(error.traceback) > 5 + + def test_error_headline_for_action(self, test_library, fresh_state): + """Test headline format for action errors.""" + node = test_library.nodes["node_with_actions"] + + params = { + "trigger_error": True, + "error_message": "Custom error message for testing", + } + + with pytest.raises(ValueError): + run_node(node, params, fresh_state) + + error = fresh_state.last_run.error + headline = error.details_headline + assert "process_data_with_error" in headline + + # Should indicate it's in an action + assert "action" in headline + + def test_error_headline_for_body(self, test_library, fresh_state): + """Test headline format for body/initialization errors.""" + node = test_library.nodes["node_raises_in_body"] + + params = { + "should_fail": True, + "error_message": "Body error message", + "error_type": "RuntimeError", + } + + with pytest.raises(RuntimeError): + run_node(node, params, fresh_state) + + error = fresh_state.last_run.error + headline = error.details_headline + + # Should indicate location (body or initialization) + assert "body" in headline or "initialization" in headline + + # Should NOT mention an action name (since it's not in an action) + assert "action" not in headline or "initialization" in headline From 8de77366abc32f8061286078e8a72cfbc641f5fc Mon Sep 17 00:00:00 2001 From: Jonatan Kutchinsky Date: Wed, 26 Nov 2025 16:24:07 +0100 Subject: [PATCH 10/26] Formatting fixes --- qualibrate_runner/core/models/common.py | 4 +- qualibrate_runner/core/run_job.py | 17 ++- .../fixtures/test_nodes/node_with_actions.py | 11 +- .../test_nodes/test_node_can_raise_in_body.py | 7 +- tests/integration/test_run_job_integration.py | 20 ++-- tests/unit/test_config_state.py | 17 ++- tests/unit/test_models_common.py | 6 +- tests/unit/test_models_last_run.py | 8 +- tests/unit/test_run_job.py | 104 +++++++++++++----- 9 files changed, 131 insertions(+), 63 deletions(-) diff --git a/qualibrate_runner/core/models/common.py b/qualibrate_runner/core/models/common.py index bf98d00..1569e81 100644 --- a/qualibrate_runner/core/models/common.py +++ b/qualibrate_runner/core/models/common.py @@ -35,7 +35,9 @@ class RunError(BaseModel): error_class: str = Field(..., description="The class of the error.") message: str = Field(..., description="The error message.") traceback: list[str] = Field(..., description="The traceback of the error.") - details_headline: str | None = Field(None, description="The headline of the details.") + details_headline: str | None = Field( + None, description="The headline of the details." + ) details: str | None = Field(None, description="The details of the error.") diff --git a/qualibrate_runner/core/run_job.py b/qualibrate_runner/core/run_job.py index 48d0eeb..edb589a 100644 --- a/qualibrate_runner/core/run_job.py +++ b/qualibrate_runner/core/run_job.py @@ -59,7 +59,8 @@ def validate_input_parameters( return parameters_class.model_validate(passed_parameters) except ValidationError as ex: raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail=ex.errors() + status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, + detail=ex.errors(), ) from ex @@ -141,12 +142,20 @@ def run_node( # Capture error details for state tracking run_status = RunStatusEnum.ERROR run_summary = getattr(node, "run_summary", None) - run_summary_error = getattr(run_summary, "error", None) if run_summary else None # Very safe way to access error + run_summary_error = ( + getattr(run_summary, "error", None) if run_summary else None + ) # Very safe way to access error run_error = RunError( error_class=ex.__class__.__name__, message=str(ex), - details_headline=getattr(run_summary_error, 'details_headline', None) if run_summary_error else None, - details=getattr(run_summary_error, 'details', None) if run_summary_error else None, + details_headline=getattr( + run_summary_error, "details_headline", None + ) + if run_summary_error + else None, + details=getattr(run_summary_error, "details", None) + if run_summary_error + else None, traceback=traceback.format_tb(ex.__traceback__), ) # Re-raise to allow caller to handle the error diff --git a/tests/fixtures/test_nodes/node_with_actions.py b/tests/fixtures/test_nodes/node_with_actions.py index 029ea7b..2053206 100644 --- a/tests/fixtures/test_nodes/node_with_actions.py +++ b/tests/fixtures/test_nodes/node_with_actions.py @@ -57,8 +57,7 @@ class Parameters(NodeParameters): def prepare_data(node): """Generate measurement data based on parameters.""" data = [ - node.parameters.amplitude * i - for i in range(node.parameters.num_points) + node.parameters.amplitude * i for i in range(node.parameters.num_points) ] return { "data": data, @@ -95,12 +94,16 @@ def execute_qua_program(node): dfs = np.arange(-span // 2, +span // 2, step) sweep_axes = { "qubit": xr.DataArray(["q1", "q2"]), - "detuning": xr.DataArray(dfs, attrs={"long_name": "readout frequency", "units": "Hz"}), + "detuning": xr.DataArray( + dfs, attrs={"long_name": "readout frequency", "units": "Hz"} + ), } data_fetcher = XarrayDataFetcher(mock_job, sweep_axes) for dataset in data_fetcher: progress_counter( - data_fetcher["nonexistent_key"], # This will raise KeyError from inside XarrayDataFetcher + data_fetcher[ + "nonexistent_key" + ], # This will raise KeyError from inside XarrayDataFetcher node.parameters.num_shots, start_time=data_fetcher.t_start, ) diff --git a/tests/fixtures/test_nodes/test_node_can_raise_in_body.py b/tests/fixtures/test_nodes/test_node_can_raise_in_body.py index f0e45f9..69b899c 100644 --- a/tests/fixtures/test_nodes/test_node_can_raise_in_body.py +++ b/tests/fixtures/test_nodes/test_node_can_raise_in_body.py @@ -14,6 +14,7 @@ class Parameters(NodeParameters): """Parameters for simple test""" + amplitude: float = Field(default=0.5, ge=0.0, le=1.0) frequency: float = Field(default=5.0e9, gt=0.0) num_points: int = Field(default=10, ge=1) @@ -48,11 +49,13 @@ class Parameters(NodeParameters): raise ValueError(error_msg) # This code only runs if we didn't fail above -data = [node.parameters.amplitude * i for i in range(node.parameters.num_points)] +data = [ + node.parameters.amplitude * i for i in range(node.parameters.num_points) +] node.results = { "data": data, "length": len(data), "amplitude": node.parameters.amplitude, "frequency": node.parameters.frequency, - "error_raised": False + "error_raised": False, } diff --git a/tests/integration/test_run_job_integration.py b/tests/integration/test_run_job_integration.py index 7f0dc43..8c5c426 100644 --- a/tests/integration/test_run_job_integration.py +++ b/tests/integration/test_run_job_integration.py @@ -202,8 +202,7 @@ def test_state_lifecycle_success(self, test_library, fresh_state): assert fresh_state.last_run.started_at is not None assert fresh_state.last_run.completed_at is not None assert ( - fresh_state.last_run.completed_at - >= fresh_state.last_run.started_at + fresh_state.last_run.completed_at >= fresh_state.last_run.started_at ) def test_state_lifecycle_with_error(self, test_library, fresh_state): @@ -322,8 +321,7 @@ def test_error_details_for_action_failure(self, test_library, fresh_state): # Full traceback SHOULD contain framework files traceback_str = "".join(error.traceback) assert ( - "action_manager.py" in traceback_str - or "action.py" in traceback_str + "action_manager.py" in traceback_str or "action.py" in traceback_str ) def test_error_details_for_body_failure(self, test_library, fresh_state): @@ -346,11 +344,17 @@ def test_error_details_for_body_failure(self, test_library, fresh_state): error = fresh_state.last_run.error # 1. Headline should indicate body error (no action) - assert "body" in error.details_headline or "initialization" in error.details_headline + assert ( + "body" in error.details_headline + or "initialization" in error.details_headline + ) # 2. Details should NOT contain action execution summary # (since no actions were used) - assert "Completed actions" not in error.details or "prepare_data" not in error.details + assert ( + "Completed actions" not in error.details + or "prepare_data" not in error.details + ) # 3. Details should contain source snippet from node file (simple format) assert "Source Code:" in error.details @@ -360,9 +364,7 @@ def test_error_details_for_body_failure(self, test_library, fresh_state): # Body errors should not have action framework in simplified version assert "action_manager.py" not in error.details - def test_error_details_with_action_history( - self, test_library, fresh_state - ): + def test_error_details_with_action_history(self, test_library, fresh_state): """Test error details include completed and skipped actions.""" node = test_library.nodes["node_with_actions"] diff --git a/tests/unit/test_config_state.py b/tests/unit/test_config_state.py index 874b9f8..6094b84 100644 --- a/tests/unit/test_config_state.py +++ b/tests/unit/test_config_state.py @@ -43,9 +43,7 @@ def test_not_running_when_error(self, sample_last_run_error): state = State(last_run=sample_last_run_error) assert state.is_running is False - def test_is_running_changes_with_status( - self, aware_datetime, mock_node - ): + def test_is_running_changes_with_status(self, aware_datetime, mock_node): """Test is_running property changes as status changes.""" state = State() @@ -113,7 +111,9 @@ def test_clear_while_running_raises_error( ) # Should raise RuntimeError - with pytest.raises(RuntimeError, match="Can't clear while item is running"): + with pytest.raises( + RuntimeError, match="Can't clear while item is running" + ): state.clear() # State should be unchanged @@ -124,9 +124,7 @@ def test_clear_while_running_raises_error( class TestStateLifecycle: """Tests for the typical State lifecycle during execution.""" - def test_full_execution_lifecycle_success( - self, aware_datetime, mock_node - ): + def test_full_execution_lifecycle_success(self, aware_datetime, mock_node): """Test state through full successful execution lifecycle.""" state = State() @@ -204,9 +202,7 @@ def test_full_execution_lifecycle_error( assert state.last_run is None assert state.run_item is None - def test_multiple_sequential_executions( - self, aware_datetime, mock_node - ): + def test_multiple_sequential_executions(self, aware_datetime, mock_node): """Test state through multiple sequential executions.""" state = State() @@ -262,6 +258,7 @@ def test_run_item_can_be_any_object(self): Note: When using model_construct, Pydantic bypasses validation which allows storing any object type. """ + # Create a simple custom object class CustomRunnable: def __init__(self): diff --git a/tests/unit/test_models_common.py b/tests/unit/test_models_common.py index 3224076..1b50a8e 100644 --- a/tests/unit/test_models_common.py +++ b/tests/unit/test_models_common.py @@ -104,7 +104,7 @@ def test_multiline_error_message(self): def test_long_traceback(self): """Test RunError with a long traceback (many frames).""" long_traceback = [ - f' File "/path/frame_{i}.py", line {i*10}, in func_{i}\n' + f' File "/path/frame_{i}.py", line {i * 10}, in func_{i}\n' for i in range(20) ] @@ -386,7 +386,9 @@ def test_complex_number_values(self): def test_very_long_key(self): """Test StateUpdate with very long nested key path.""" - long_key = "system.rack_1.chassis_2.module_3.channel_4.qubit_5.subsystem_6" + long_key = ( + "system.rack_1.chassis_2.module_3.channel_4.qubit_5.subsystem_6" + ) update = StateUpdate( key=long_key, attr="parameter", old=0, new=1, updated=True diff --git a/tests/unit/test_models_last_run.py b/tests/unit/test_models_last_run.py index 54dc034..6f8156e 100644 --- a/tests/unit/test_models_last_run.py +++ b/tests/unit/test_models_last_run.py @@ -139,9 +139,7 @@ def test_create_with_state_updates(self, aware_datetime): class TestLastRunDuration: """Tests for the run_duration computed field.""" - def test_duration_for_completed_run( - self, aware_datetime, later_datetime - ): + def test_duration_for_completed_run(self, aware_datetime, later_datetime): """Test run_duration calculation for completed run.""" # later_datetime is 5.5 seconds after aware_datetime last_run = LastRun( @@ -294,7 +292,9 @@ def test_all_status_values(self, aware_datetime): last_run = LastRun( status=status, started_at=aware_datetime, - completed_at=aware_datetime if status != RunStatusEnum.RUNNING else None, + completed_at=aware_datetime + if status != RunStatusEnum.RUNNING + else None, name="test_node", idx=-1, runnable_type=RunnableType.NODE, diff --git a/tests/unit/test_run_job.py b/tests/unit/test_run_job.py index 7769391..0d58781 100644 --- a/tests/unit/test_run_job.py +++ b/tests/unit/test_run_job.py @@ -41,7 +41,9 @@ def test_valid_parameters_with_defaults(self, sample_parameters_class): assert result.frequency == 5.0e9 assert result.num_averages == 100 # Default value - def test_invalid_parameter_raises_http_exception(self, sample_parameters_class): + def test_invalid_parameter_raises_http_exception( + self, sample_parameters_class + ): """Test that invalid parameters raise HTTPException with 422 status.""" params = {"amplitude": 1.5, "frequency": 5.0e9} # amplitude > 1.0 @@ -51,7 +53,9 @@ def test_invalid_parameter_raises_http_exception(self, sample_parameters_class): assert exc_info.value.status_code == 422 assert exc_info.value.detail is not None - def test_missing_required_field_raises_http_exception(self, sample_parameters_class): + def test_missing_required_field_raises_http_exception( + self, sample_parameters_class + ): """Test that missing required field raises HTTPException.""" params = {"amplitude": 0.5} # Missing required 'frequency' @@ -94,7 +98,9 @@ def test_returns_library_when_exists(self, mock_lib_class, mock_library): @patch("qualibrate_runner.core.run_job.QualibrationLibrary") def test_raises_exception_when_no_library(self, mock_lib_class): """Test that function raises exception when no library exists.""" - mock_lib_class.get_active_library.side_effect = RuntimeError("No active library") + mock_lib_class.get_active_library.side_effect = RuntimeError( + "No active library" + ) with pytest.raises(RuntimeError, match="No active library"): get_active_library_or_error() @@ -158,7 +164,9 @@ def test_captures_run_summary(self, mock_node, fresh_state): # run_result should be None since run_summary is None assert fresh_state.last_run.run_result is None - def test_captures_state_updates(self, mock_node, fresh_state, sample_state_update): + def test_captures_state_updates( + self, mock_node, fresh_state, sample_state_update + ): """Test that state_updates are captured from node.""" from qualibrate_runner.core.models.common import StateUpdate @@ -177,7 +185,9 @@ def test_sets_completed_at_timestamp(self, mock_node, fresh_state): run_node(mock_node, {}, fresh_state) assert fresh_state.last_run.completed_at is not None - assert (fresh_state.last_run.completed_at > fresh_state.last_run.started_at) + assert ( + fresh_state.last_run.completed_at > fresh_state.last_run.started_at + ) class TestRunNodeErrorPath: @@ -204,7 +214,10 @@ def test_captures_traceback(self, mock_node, fresh_state): assert len(fresh_state.last_run.error.traceback) > 0 # Traceback should be a list of strings - assert all(isinstance(line, str) for line in fresh_state.last_run.error.traceback) + assert all( + isinstance(line, str) + for line in fresh_state.last_run.error.traceback + ) def test_re_raises_original_exception(self, mock_node, fresh_state): """Test that the original exception is re-raised.""" @@ -244,7 +257,9 @@ class TestRunWorkflowHappyPath: """Tests for run_workflow function - happy path scenarios.""" @patch("qualibrate_runner.core.run_job.get_active_library_or_error") - def test_retrieves_fresh_workflow_from_library(self, mock_get_library, mock_library, mock_workflow, fresh_state): + def test_retrieves_fresh_workflow_from_library( + self, mock_get_library, mock_library, mock_workflow, fresh_state + ): """Test that workflow is retrieved fresh from library.""" mock_get_library.return_value = mock_library @@ -263,14 +278,18 @@ def test_retrieves_fresh_workflow_from_library(self, mock_get_library, mock_libr mock_library.graphs["test_workflow"] = fresh_workflow - run_workflow(mock_workflow, {"parameters": {"frequency": 5.0e9}}, fresh_state) + run_workflow( + mock_workflow, {"parameters": {"frequency": 5.0e9}}, fresh_state + ) # Should retrieve fresh workflow from library mock_get_library.assert_called_once() fresh_workflow.run.assert_called_once() @patch("qualibrate_runner.core.run_job.get_active_library_or_error") - def test_creates_last_run_with_graph_type(self, mock_get_library, mock_library, mock_workflow, fresh_state): + def test_creates_last_run_with_graph_type( + self, mock_get_library, mock_library, mock_workflow, fresh_state + ): """Test that LastRun is created with GRAPH runnable_type.""" mock_get_library.return_value = mock_library @@ -292,11 +311,12 @@ def capture_state(*args, **kwargs): run_workflow(mock_workflow, {}, fresh_state) - assert (last_run_during_execution.runnable_type == RunnableType.GRAPH) + assert last_run_during_execution.runnable_type == RunnableType.GRAPH @patch("qualibrate_runner.core.run_job.get_active_library_or_error") - def test_validates_parameters_with_full_parameters_class(self, mock_get_library, mock_library, mock_workflow, - fresh_state): + def test_validates_parameters_with_full_parameters_class( + self, mock_get_library, mock_library, mock_workflow, fresh_state + ): """Test that parameters are validated using full_parameters_class.""" mock_get_library.return_value = mock_library @@ -312,17 +332,23 @@ def test_validates_parameters_with_full_parameters_class(self, mock_get_library, run_workflow(mock_workflow, input_params, fresh_state) # Should call full_parameters_class with input parameters - mock_workflow.full_parameters_class.assert_called_once_with(**input_params) + mock_workflow.full_parameters_class.assert_called_once_with( + **input_params + ) @patch("qualibrate_runner.core.run_job.get_active_library_or_error") - def test_splits_parameters_into_nodes_and_params(self, mock_get_library, mock_library, mock_workflow, fresh_state): + def test_splits_parameters_into_nodes_and_params( + self, mock_get_library, mock_library, mock_workflow, fresh_state + ): """Test that parameters are split into nodes and parameters.""" mock_get_library.return_value = mock_library # Setup workflow mock_workflow.run = Mock(return_value=None) mock_params = Mock() - mock_params.nodes.model_dump.return_value = {"node1": {"amplitude": 0.5}} + mock_params.nodes.model_dump.return_value = { + "node1": {"amplitude": 0.5} + } mock_params.parameters.model_dump.return_value = {"frequency": 5.0e9} mock_workflow.full_parameters_class.return_value = mock_params @@ -334,10 +360,14 @@ def test_splits_parameters_into_nodes_and_params(self, mock_get_library, mock_li run_workflow(mock_workflow, input_params, fresh_state) # Should call workflow.run with separated params - mock_workflow.run.assert_called_once_with(nodes={"node1": {"amplitude": 0.5}}, frequency=5.0e9) + mock_workflow.run.assert_called_once_with( + nodes={"node1": {"amplitude": 0.5}}, frequency=5.0e9 + ) @patch("qualibrate_runner.core.run_job.get_active_library_or_error") - def test_updates_state_with_finished_status(self, mock_get_library, mock_library, mock_workflow, fresh_state): + def test_updates_state_with_finished_status( + self, mock_get_library, mock_library, mock_workflow, fresh_state + ): """Test that final state has FINISHED status on success.""" mock_get_library.return_value = mock_library @@ -354,7 +384,9 @@ def test_updates_state_with_finished_status(self, mock_get_library, mock_library assert fresh_state.last_run.error is None @patch("qualibrate_runner.core.run_job.get_active_library_or_error") - def test_captures_workflow_snapshot_idx(self, mock_get_library, mock_library, mock_workflow, fresh_state): + def test_captures_workflow_snapshot_idx( + self, mock_get_library, mock_library, mock_workflow, fresh_state + ): """Test that snapshot_idx is captured from workflow.""" mock_get_library.return_value = mock_library @@ -375,7 +407,9 @@ class TestRunWorkflowErrorPath: """Tests for run_workflow function - error scenarios.""" @patch("qualibrate_runner.core.run_job.get_active_library_or_error") - def test_library_not_found_raises_exception(self, mock_get_library, mock_workflow, fresh_state): + def test_library_not_found_raises_exception( + self, mock_get_library, mock_workflow, fresh_state + ): """Test that missing library raises exception.""" mock_get_library.side_effect = RuntimeError("No active library") @@ -385,7 +419,9 @@ def test_library_not_found_raises_exception(self, mock_get_library, mock_workflo assert fresh_state.last_run.status == RunStatusEnum.ERROR @patch("qualibrate_runner.core.run_job.get_active_library_or_error") - def test_workflow_not_in_library_raises_exception(self, mock_get_library, mock_library, mock_workflow, fresh_state): + def test_workflow_not_in_library_raises_exception( + self, mock_get_library, mock_library, mock_workflow, fresh_state + ): """Test that missing workflow in library raises exception.""" mock_get_library.return_value = mock_library mock_library.graphs = {} # Empty, no workflows @@ -396,15 +432,25 @@ def test_workflow_not_in_library_raises_exception(self, mock_get_library, mock_l assert fresh_state.last_run.status == RunStatusEnum.ERROR @patch("qualibrate_runner.core.run_job.get_active_library_or_error") - def test_parameter_validation_errors_raise_exception(self, mock_get_library, mock_library, mock_workflow, - fresh_state): + def test_parameter_validation_errors_raise_exception( + self, mock_get_library, mock_library, mock_workflow, fresh_state + ): """Test that parameter validation errors are raised.""" mock_get_library.return_value = mock_library # Setup workflow to raise validation error - mock_workflow.full_parameters_class.side_effect = ValidationError.from_exception_data( - "test", - [{"type": "missing", "loc": ("frequency",), "msg": "Field required", "input": {}}], + mock_workflow.full_parameters_class.side_effect = ( + ValidationError.from_exception_data( + "test", + [ + { + "type": "missing", + "loc": ("frequency",), + "msg": "Field required", + "input": {}, + } + ], + ) ) with pytest.raises(ValidationError): @@ -413,7 +459,9 @@ def test_parameter_validation_errors_raise_exception(self, mock_get_library, moc assert fresh_state.last_run.status == RunStatusEnum.ERROR @patch("qualibrate_runner.core.run_job.get_active_library_or_error") - def test_runtime_errors_captured_and_reraised(self, mock_get_library, mock_library, mock_workflow, fresh_state): + def test_runtime_errors_captured_and_reraised( + self, mock_get_library, mock_library, mock_workflow, fresh_state + ): """Test that runtime errors during workflow.run are captured.""" mock_get_library.return_value = mock_library @@ -431,7 +479,9 @@ def test_runtime_errors_captured_and_reraised(self, mock_get_library, mock_libra assert fresh_state.last_run.error.error_class == "RuntimeError" @patch("qualibrate_runner.core.run_job.get_active_library_or_error") - def test_state_updated_even_on_error(self, mock_get_library, mock_library, mock_workflow, fresh_state): + def test_state_updated_even_on_error( + self, mock_get_library, mock_library, mock_workflow, fresh_state + ): """Test that state is updated in finally block even on error.""" mock_get_library.return_value = mock_library From 7e4ab20cc3fb4c5a7a627344a08837f63d23b006 Mon Sep 17 00:00:00 2001 From: Jonatan Kutchinsky Date: Wed, 26 Nov 2025 16:33:54 +0100 Subject: [PATCH 11/26] Trying to fix github action --- .github/workflows/check.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index 146aa7f..5caf2f7 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -22,7 +22,7 @@ jobs: cache-dependency-path: poetry.lock - name: Install python deps - run: poetry install --with dev --all-extras + run: poetry install --with dev --with test - name: Check python package typing run: poetry run poe type From 606c367b83536dbca0e711431214a1b1a3eb3c36 Mon Sep 17 00:00:00 2001 From: Jonatan Kutchinsky Date: Wed, 26 Nov 2025 16:38:03 +0100 Subject: [PATCH 12/26] Again, trying to fix github action --- .github/workflows/check.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index 5caf2f7..e22b73e 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -22,7 +22,7 @@ jobs: cache-dependency-path: poetry.lock - name: Install python deps - run: poetry install --with dev --with test + run: poetry install --with dev - name: Check python package typing run: poetry run poe type From 7a046c293a8c5ebb4f33e2c99c32a7ca79843c81 Mon Sep 17 00:00:00 2001 From: Jonatan Kutchinsky Date: Wed, 26 Nov 2025 16:44:51 +0100 Subject: [PATCH 13/26] Fixing github action - probably caused by the poetry.lock file --- poetry.lock | 453 ++++++++++++++++------------------------------------ 1 file changed, 142 insertions(+), 311 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5f4a6bc..23acc32 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,27 +1,12 @@ # This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. -[[package]] -name = "a2wsgi" -version = "1.10.10" -description = "Convert WSGI app to ASGI app or ASGI app to WSGI app." -optional = false -python-versions = ">=3.8.0" -groups = ["test"] -files = [ - {file = "a2wsgi-1.10.10-py3-none-any.whl", hash = "sha256:d2b21379479718539dc15fce53b876251a0efe7615352dfe49f6ad1bc507848d"}, - {file = "a2wsgi-1.10.10.tar.gz", hash = "sha256:a5bcffb52081ba39df0d5e9a884fc6f819d92e3a42389343ba77cbf809fe1f45"}, -] - -[package.dependencies] -typing_extensions = {version = "*", markers = "python_version < \"3.11\""} - [[package]] name = "annotated-types" version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] 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"}, @@ -33,7 +18,7 @@ version = "4.11.0" description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc"}, {file = "anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4"}, @@ -54,7 +39,7 @@ version = "3.6.2" description = "Bash tab completion for argparse" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main"] files = [ {file = "argcomplete-3.6.2-py3-none-any.whl", hash = "sha256:65b3133a29ad53fb42c48cf5114752c7ab66c1c38544fdf6460f450c09b42591"}, {file = "argcomplete-3.6.2.tar.gz", hash = "sha256:d0519b1bc867f5f4f4713c41ad0aba73a4a5f007449716b16f385f2166dc6adf"}, @@ -63,31 +48,13 @@ files = [ [package.extras] test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] -[[package]] -name = "asteval" -version = "1.0.7" -description = "Safe, minimalistic evaluator of python expression using ast module" -optional = false -python-versions = ">=3.10" -groups = ["test"] -files = [ - {file = "asteval-1.0.7-py3-none-any.whl", hash = "sha256:d78df08681dfff59031ca624ba7030f9dc576a7a16e2f7a5137c6e7ef3ee60c4"}, - {file = "asteval-1.0.7.tar.gz", hash = "sha256:729fa010511957afcbdbadb0304da09cef6904c145b6901e917afac2dd5668a1"}, -] - -[package.extras] -all = ["asteval[dev,doc,test]"] -dev = ["build", "twine"] -doc = ["Sphinx"] -test = ["coverage", "pytest", "pytest-cov"] - [[package]] name = "backports-tarfile" version = "1.2.0" description = "Backport of CPython tarfile module" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] markers = "python_version < \"3.12\"" files = [ {file = "backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34"}, @@ -104,7 +71,7 @@ version = "2.0.0b7" description = "A better Protobuf / gRPC generator & library" optional = false python-versions = "<4.0,>=3.7" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "betterproto-2.0.0b7-py3-none-any.whl", hash = "sha256:401ab8055e2f814e77b9c88a74d0e1ae3d1e8a969cced6aeb1b59f71ad63fbd2"}, {file = "betterproto-2.0.0b7.tar.gz", hash = "sha256:1b1458ca5278d519bcd62556a4c236f998a91d503f0f71c67b0b954747052af2"}, @@ -125,7 +92,7 @@ version = "25.9.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main"] files = [ {file = "black-25.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce41ed2614b706fd55fd0b4a6909d06b5bab344ffbfadc6ef34ae50adba3d4f7"}, {file = "black-25.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ab0ce111ef026790e9b13bd216fa7bc48edd934ffc4cbf78808b235793cbc92"}, @@ -173,7 +140,7 @@ version = "1.3.0" description = "A simple, correct Python build frontend" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "build-1.3.0-py3-none-any.whl", hash = "sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4"}, {file = "build-1.3.0.tar.gz", hash = "sha256:698edd0ea270bde950f53aed21f3a0135672206f3911e0176261a31e0e07b397"}, @@ -196,7 +163,7 @@ version = "0.14.3" description = "httplib2 caching for requests" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "cachecontrol-0.14.3-py3-none-any.whl", hash = "sha256:b35e44a3113f17d2a31c1e6b27b9de6d4405f84ae51baa8c1d3cc5b633010cae"}, {file = "cachecontrol-0.14.3.tar.gz", hash = "sha256:73e7efec4b06b20d9267b441c1f733664f989fb8688391b670ca812d70795d11"}, @@ -218,7 +185,7 @@ version = "2025.8.3" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, @@ -230,7 +197,8 @@ version = "2.0.0" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] +markers = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and (sys_platform == \"linux\" or sys_platform == \"darwin\")" files = [ {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, @@ -317,7 +285,6 @@ files = [ {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"}, {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"}, ] -markers = {main = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and (sys_platform == \"linux\" or sys_platform == \"darwin\")", qm-dev = "(sys_platform == \"linux\" or sys_platform == \"darwin\") and (platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\")", test = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and (sys_platform == \"linux\" or sys_platform == \"darwin\")"} [package.dependencies] pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} @@ -328,7 +295,7 @@ version = "3.4.3" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, @@ -417,7 +384,7 @@ version = "2.1.0" description = "Cleo allows you to create beautiful and testable command-line interfaces." optional = false python-versions = ">=3.7,<4.0" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "cleo-2.1.0-py3-none-any.whl", hash = "sha256:4a31bd4dd45695a64ee3c4758f583f134267c2bc518d8ae9a29cf237d009b07e"}, {file = "cleo-2.1.0.tar.gz", hash = "sha256:0b2c880b5d13660a7ea651001fb4acb527696c01f15c9ee650f377aa543fd523"}, @@ -433,7 +400,7 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -448,12 +415,12 @@ 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" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {main = "platform_system == \"Windows\" or os_name == \"nt\"", qm-dev = "os_name == \"nt\" or platform_system == \"Windows\""} +markers = {main = "platform_system == \"Windows\" or os_name == \"nt\"", test = "platform_system == \"Windows\" or os_name == \"nt\" or sys_platform == \"win32\""} [[package]] name = "contourpy" @@ -461,7 +428,7 @@ version = "1.3.2" description = "Python library for calculating contours of 2D quadrilateral grids" optional = false python-versions = ">=3.10" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934"}, {file = "contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989"}, @@ -538,7 +505,7 @@ version = "0.4.1" description = "Manage Python errors with ease" optional = false python-versions = ">=3.7,<4.0" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "crashtest-0.4.1-py3-none-any.whl", hash = "sha256:8d23eac5fa660409f57472e3851dab7ac18aba459a8d19cbbba86d3d5aecd2a5"}, {file = "crashtest-0.4.1.tar.gz", hash = "sha256:80d7b1f316ebfbd429f648076d6275c877ba30ba48979de4191714a75266f0ce"}, @@ -550,7 +517,7 @@ version = "46.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = "!=3.9.0,!=3.9.1,>=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] markers = "sys_platform == \"linux\"" files = [ {file = "cryptography-46.0.1-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:1cd6d50c1a8b79af1a6f703709d8973845f677c8e97b1268f5ff323d38ce8475"}, @@ -629,7 +596,7 @@ version = "0.12.1" description = "Composable style cycles" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, @@ -645,7 +612,7 @@ version = "2.43.0" description = "Collection of all Datadog Public endpoints" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "datadog_api_client-2.43.0-py3-none-any.whl", hash = "sha256:1d038b545263ad0b665f46d9d36dc0f0ed491770d9dcd68380be4685a9ae6cc3"}, {file = "datadog_api_client-2.43.0.tar.gz", hash = "sha256:f3aec5db9bcceb39f6315e505eea908da7133a19f8475ef996ef8be8e3c84855"}, @@ -669,7 +636,7 @@ version = "0.33.0" description = "Datamodel Code Generator" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main"] files = [ {file = "datamodel_code_generator-0.33.0-py3-none-any.whl", hash = "sha256:e229264aa612b2d5bb4901bcd6c520a799ae0d5c19262577a0f876eb48afaaa3"}, {file = "datamodel_code_generator-0.33.0.tar.gz", hash = "sha256:7635ef788201d69bd3e98ba88ce6afe479400dc2737fe9d5e21f87408f352c08"}, @@ -701,7 +668,7 @@ version = "4.48.2" description = "Dependency injection framework for Python" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "dependency_injector-4.48.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:54d0178be10f17b768afb5c0ed1c5c565abaa2d097b2bc5a529a31c580613df2"}, {file = "dependency_injector-4.48.2-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:12a15979fd534b728b3061c8aa52fd55adb77574758817daae9df8a1c2eb830b"}, @@ -745,29 +712,13 @@ pydantic = ["pydantic"] pydantic2 = ["pydantic-settings"] yaml = ["pyyaml"] -[[package]] -name = "dill" -version = "0.4.0" -description = "serialize all of Python" -optional = false -python-versions = ">=3.8" -groups = ["test"] -files = [ - {file = "dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049"}, - {file = "dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0"}, -] - -[package.extras] -graph = ["objgraph (>=1.7.2)"] -profile = ["gprof2dot (>=2022.7.29)"] - [[package]] name = "distlib" version = "0.4.0" description = "Distribution utilities" optional = false python-versions = "*" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, @@ -779,7 +730,7 @@ version = "0.22.8" description = "Python Git Library" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "dulwich-0.22.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:546176d18b8cc0a492b0f23f07411e38686024cffa7e9d097ae20512a2e57127"}, {file = "dulwich-0.22.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d2434dd72b2ae09b653c9cfe6764a03c25cfbd99fbbb7c426f0478f6fb1100f"}, @@ -846,7 +797,7 @@ version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] markers = "python_version == \"3.10\"" files = [ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, @@ -865,7 +816,7 @@ version = "0.117.1" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main"] files = [ {file = "fastapi-0.117.1-py3-none-any.whl", hash = "sha256:33c51a0d21cab2b9722d4e56dbb9316f3687155be6b276191790d8da03507552"}, {file = "fastapi-0.117.1.tar.gz", hash = "sha256:fb2d42082d22b185f904ca0ecad2e195b851030bd6c5e4c032d1c981240c631a"}, @@ -887,7 +838,7 @@ version = "2.21.2" description = "Fastest Python implementation of JSON schema" optional = false python-versions = "*" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463"}, {file = "fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de"}, @@ -902,7 +853,7 @@ version = "3.19.1" description = "A platform independent file lock." optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d"}, {file = "filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58"}, @@ -914,7 +865,7 @@ version = "0.6.3" description = "A utility to find python versions on your system" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "findpython-0.6.3-py3-none-any.whl", hash = "sha256:a85bb589b559cdf1b87227cc233736eb7cad894b9e68021ee498850611939ebc"}, {file = "findpython-0.6.3.tar.gz", hash = "sha256:5863ea55556d8aadc693481a14ac4f3624952719efc1c5591abb0b4a9e965c94"}, @@ -929,7 +880,7 @@ version = "4.60.1" description = "Tools to manipulate font files" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "fonttools-4.60.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9a52f254ce051e196b8fe2af4634c2d2f02c981756c6464dc192f1b6050b4e28"}, {file = "fonttools-4.60.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c7420a2696a44650120cdd269a5d2e56a477e2bfa9d95e86229059beb1c19e15"}, @@ -1025,7 +976,7 @@ version = "1.3.0" description = "GenSON is a powerful, user-friendly JSON Schema generator." optional = false python-versions = "*" -groups = ["main", "qm-dev", "test"] +groups = ["main"] files = [ {file = "genson-1.3.0-py3-none-any.whl", hash = "sha256:468feccd00274cc7e4c09e84b08704270ba8d95232aa280f65b986139cec67f7"}, {file = "genson-1.3.0.tar.gz", hash = "sha256:e02db9ac2e3fd29e65b5286f7135762e2cd8a986537c075b06fc5f1517308e37"}, @@ -1037,7 +988,7 @@ version = "1.75.1" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "grpcio-1.75.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:1712b5890b22547dd29f3215c5788d8fc759ce6dd0b85a6ba6e2731f2d04c088"}, {file = "grpcio-1.75.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8d04e101bba4b55cea9954e4aa71c24153ba6182481b487ff376da28d4ba46cf"}, @@ -1114,7 +1065,7 @@ version = "0.4.5" description = "Pure-Python gRPC implementation for asyncio" optional = false python-versions = ">=3.7" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "grpclib-0.4.5.tar.gz", hash = "sha256:bf83ed55aca59497e168761d9555056efc54a8f865316c3b39becd007e9f9a73"}, ] @@ -1132,7 +1083,7 @@ version = "0.16.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, @@ -1144,7 +1095,7 @@ version = "4.3.0" description = "Pure-Python HTTP/2 protocol implementation" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd"}, {file = "h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1"}, @@ -1160,7 +1111,7 @@ version = "4.1.0" description = "Pure-Python HPACK header encoding" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496"}, {file = "hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca"}, @@ -1172,7 +1123,7 @@ version = "1.0.9" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, @@ -1194,7 +1145,7 @@ version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -1220,7 +1171,7 @@ version = "6.1.0" description = "Pure-Python HTTP/2 framing" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5"}, {file = "hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08"}, @@ -1232,7 +1183,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -1247,7 +1198,7 @@ version = "8.7.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] markers = "python_version < \"3.12\"" files = [ {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, @@ -1272,7 +1223,7 @@ version = "7.5.0" description = "Correctly generate plurals, singular nouns, ordinals, indefinite articles" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main"] files = [ {file = "inflect-7.5.0-py3-none-any.whl", hash = "sha256:2aea70e5e70c35d8350b8097396ec155ffd68def678c7ff97f51aa69c1d92344"}, {file = "inflect-7.5.0.tar.gz", hash = "sha256:faf19801c3742ed5a05a8ce388e0d8fe1a07f8d095c82201eb904f5d27ad571f"}, @@ -1308,7 +1259,7 @@ version = "0.7.0" description = "A library for installing Python wheels." optional = false python-versions = ">=3.7" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "installer-0.7.0-py3-none-any.whl", hash = "sha256:05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53"}, {file = "installer-0.7.0.tar.gz", hash = "sha256:a26d3e3116289bb08216e0d0f7d925fcef0b0194eedfa0c944bcaaa106c4b631"}, @@ -1320,7 +1271,7 @@ version = "6.0.1" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.9.0" -groups = ["main", "qm-dev", "test"] +groups = ["main"] files = [ {file = "isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615"}, {file = "isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450"}, @@ -1336,7 +1287,7 @@ version = "3.4.0" description = "Utility functions for Python class constructs" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790"}, {file = "jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd"}, @@ -1355,7 +1306,7 @@ version = "6.0.1" description = "Useful decorators and context managers" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "jaraco.context-6.0.1-py3-none-any.whl", hash = "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4"}, {file = "jaraco_context-6.0.1.tar.gz", hash = "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3"}, @@ -1374,7 +1325,7 @@ version = "4.3.0" description = "Functools like those found in stdlib" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "jaraco_functools-4.3.0-py3-none-any.whl", hash = "sha256:227ff8ed6f7b8f62c56deff101545fa7543cf2c8e7b82a7c2116e672f29c26e8"}, {file = "jaraco_functools-4.3.0.tar.gz", hash = "sha256:cfd13ad0dd2c47a3600b439ef72d8615d482cedcff1632930d6f28924d92f294"}, @@ -1397,7 +1348,7 @@ version = "0.9.0" description = "Low-level, pure Python DBus protocol wrapper." optional = false python-versions = ">=3.7" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] markers = "sys_platform == \"linux\"" files = [ {file = "jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683"}, @@ -1414,7 +1365,7 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" -groups = ["main", "qm-dev", "test"] +groups = ["main"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -1432,7 +1383,7 @@ version = "1.5.2" description = "Lightweight pipelining with Python functions" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241"}, {file = "joblib-1.5.2.tar.gz", hash = "sha256:3faa5c39054b2f03ca547da9b2f52fde67c06240c31853f306aea97f13647b55"}, @@ -1444,7 +1395,7 @@ version = "1.33" description = "Apply JSON-Patches (RFC 6902)" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, @@ -1459,7 +1410,7 @@ version = "3.0.0" description = "Identify specific nodes in a JSON document (RFC 6901)" optional = false python-versions = ">=3.7" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, @@ -1471,7 +1422,7 @@ version = "25.6.0" description = "Store and access your passwords safely." optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd"}, {file = "keyring-25.6.0.tar.gz", hash = "sha256:0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66"}, @@ -1501,7 +1452,7 @@ version = "1.4.9" description = "A fast implementation of the Cassowary constraint solver" optional = false python-versions = ">=3.10" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b4b4d74bda2b8ebf4da5bd42af11d02d04428b2c32846e4c2c93219df8a7987b"}, {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fb3b8132019ea572f4611d770991000d7f58127560c4889729248eb5852a102f"}, @@ -1606,38 +1557,13 @@ files = [ {file = "kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d"}, ] -[[package]] -name = "lmfit" -version = "1.3.4" -description = "Least-Squares Minimization with Bounds and Constraints" -optional = false -python-versions = ">=3.9" -groups = ["test"] -files = [ - {file = "lmfit-1.3.4-py3-none-any.whl", hash = "sha256:afce1593b42324d37ae2908249b0c55445e2f4c1a0474ff706a8e2f7b5d949fa"}, - {file = "lmfit-1.3.4.tar.gz", hash = "sha256:3c22c28c43f717f6c5b4a3bd81e893a2149739c26a592c046f2e33c23cfbe497"}, -] - -[package.dependencies] -asteval = ">=1.0" -dill = ">=0.3.4" -numpy = ">=1.24" -scipy = ">=1.10.0" -uncertainties = ">=3.2.2" - -[package.extras] -all = ["lmfit[dev,doc,test]"] -dev = ["build", "check-wheel-contents", "flake8-pyproject", "pre-commit", "twine"] -doc = ["Pillow", "Sphinx", "cairosvg", "corner", "emcee (>=3.0.0)", "ipykernel", "jupyter_sphinx (>=0.2.4)", "matplotlib", "numdifftools", "pandas", "pycairo ; platform_system == \"Windows\"", "sphinx-gallery (>=0.10)", "sphinxcontrib-svg2pdfconverter", "sympy"] -test = ["coverage", "flaky", "pytest", "pytest-cov"] - [[package]] name = "markupsafe" version = "3.0.3" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main"] files = [ {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, @@ -1736,7 +1662,7 @@ version = "3.23.3" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "marshmallow-3.23.3-py3-none-any.whl", hash = "sha256:20c0f8c613f68bcb45b2a0d3282e2f172575560170bf220d67aafb42717910e4"}, {file = "marshmallow-3.23.3.tar.gz", hash = "sha256:d586c8685ebdb80bf754e1f96e3f305aaf30951f1fc69175b977453633467e76"}, @@ -1756,7 +1682,7 @@ version = "5.11" description = "An unofficial extension to Marshmallow to allow for polymorphic fields" optional = false python-versions = ">=3.5" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "marshmallow-polyfield-5.11.tar.gz", hash = "sha256:8075a9cc490da4af58b902b4a40a99882dd031adb7aaa96abd147a4fcd53415f"}, ] @@ -1770,7 +1696,7 @@ version = "3.10.6" description = "Python plotting package" optional = false python-versions = ">=3.10" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "matplotlib-3.10.6-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:bc7316c306d97463a9866b89d5cc217824e799fa0de346c8f68f4f3d27c8693d"}, {file = "matplotlib-3.10.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d00932b0d160ef03f59f9c0e16d1e3ac89646f7785165ce6ad40c842db16cc2e"}, @@ -1849,7 +1775,7 @@ version = "10.8.0" description = "More routines for operating on iterables, beyond itertools" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b"}, {file = "more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd"}, @@ -1861,7 +1787,7 @@ version = "1.1.1" description = "MessagePack serializer" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "msgpack-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:353b6fc0c36fde68b661a12949d7d49f8f51ff5fa019c1e47c87c4ff34b080ed"}, {file = "msgpack-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:79c408fcf76a958491b4e3b103d1c417044544b68e96d06432a189b43d1215c8"}, @@ -1930,7 +1856,7 @@ version = "6.6.4" description = "multidict implementation" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "multidict-6.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b8aa6f0bd8125ddd04a6593437bad6a7e70f300ff4180a531654aa2ab3f6d58f"}, {file = "multidict-6.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9e5853bbd7264baca42ffc53391b490d65fe62849bf2c690fa3f6273dbcd0cb"}, @@ -2114,7 +2040,7 @@ version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.8" -groups = ["main", "dev", "qm-dev", "test"] +groups = ["main", "dev"] files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, @@ -2126,7 +2052,7 @@ version = "3.4.2" description = "Python package for creating and manipulating graphs and networks" optional = false python-versions = ">=3.10" -groups = ["main", "qm-dev", "test"] +groups = ["main"] files = [ {file = "networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f"}, {file = "networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1"}, @@ -2146,7 +2072,7 @@ version = "1.26.4" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, @@ -2192,7 +2118,7 @@ version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, @@ -2316,7 +2242,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" -groups = ["main", "dev", "qm-dev", "test"] +groups = ["main", "dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -2328,7 +2254,7 @@ version = "2025.9.18" description = "Installer for Python Build Standalone" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "pbs_installer-2025.9.18-py3-none-any.whl", hash = "sha256:8ef55d7675698747505c237015d14c81759bd66a0d4c8b20cec9a2dc96e8434c"}, {file = "pbs_installer-2025.9.18.tar.gz", hash = "sha256:c0a51a7c1e015723bd8396f02e15b5876e439f74b0f45bbac436b189f903219f"}, @@ -2349,7 +2275,7 @@ version = "11.3.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860"}, {file = "pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"}, @@ -2474,7 +2400,7 @@ version = "1.12.1.2" description = "Query metadata from sdists / bdists / installed packages." optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "pkginfo-1.12.1.2-py3-none-any.whl", hash = "sha256:c783ac885519cab2c34927ccfa6bf64b5a704d7c69afaea583dd9b7afe969343"}, {file = "pkginfo-1.12.1.2.tar.gz", hash = "sha256:5cd957824ac36f140260964eba3c6be6442a8359b8c48f4adf90210f33a04b7b"}, @@ -2489,7 +2415,7 @@ version = "4.4.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85"}, {file = "platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"}, @@ -2506,7 +2432,7 @@ version = "5.24.1" description = "An open-source, interactive data visualization library for Python" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "plotly-5.24.1-py3-none-any.whl", hash = "sha256:f67073a1e637eb0dc3e46324d9d51e2fe76e9727c892dde64ddf1e1b51f29089"}, {file = "plotly-5.24.1.tar.gz", hash = "sha256:dbc8ac8339d248a4bcc36e08a5659bacfe1b079390b8953533f4eb22169b4bae"}, @@ -2558,7 +2484,7 @@ version = "2.1.3" description = "Python dependency management and packaging made easy." optional = false python-versions = "<4.0,>=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "poetry-2.1.3-py3-none-any.whl", hash = "sha256:7054d3f97ccce7f31961ead16250407c4577bfe57e2037a190ae2913fc40a20c"}, {file = "poetry-2.1.3.tar.gz", hash = "sha256:f2c9bd6790b19475976d88ea4553bcc3533c0dc73f740edc4fffe9e2add50594"}, @@ -2594,7 +2520,7 @@ version = "2.1.3" description = "Poetry PEP 517 Build Backend" optional = false python-versions = "<4.0,>=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "poetry_core-2.1.3-py3-none-any.whl", hash = "sha256:2c704f05016698a54ca1d327f46ce2426d72eaca6ff614132c8477c292266771"}, {file = "poetry_core-2.1.3.tar.gz", hash = "sha256:0522a015477ed622c89aad56a477a57813cace0c8e7ff2a2906b7ef4a2e296a4"}, @@ -2606,7 +2532,7 @@ version = "6.32.1" description = "" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "protobuf-6.32.1-cp310-abi3-win32.whl", hash = "sha256:a8a32a84bc9f2aad712041b8b366190f71dde248926da517bde9e832e4412085"}, {file = "protobuf-6.32.1-cp310-abi3-win_amd64.whl", hash = "sha256:b00a7d8c25fa471f16bc8153d0e53d6c9e827f0953f3c09aaa4331c718cae5e1"}, @@ -2625,12 +2551,12 @@ version = "2.23" description = "C parser in Python" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] +markers = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and implementation_name != \"PyPy\" and (sys_platform == \"linux\" or sys_platform == \"darwin\")" files = [ {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"}, {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}, ] -markers = {main = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and implementation_name != \"PyPy\" and (sys_platform == \"linux\" or sys_platform == \"darwin\")", qm-dev = "(sys_platform == \"linux\" or sys_platform == \"darwin\") and (platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and implementation_name != \"PyPy\"", test = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and implementation_name != \"PyPy\" and (sys_platform == \"linux\" or sys_platform == \"darwin\")"} [[package]] name = "pydantic" @@ -2638,7 +2564,7 @@ version = "2.11.9" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "pydantic-2.11.9-py3-none-any.whl", hash = "sha256:c42dd626f5cfc1c6950ce6205ea58c93efa406da65f479dcb4029d5934857da2"}, {file = "pydantic-2.11.9.tar.gz", hash = "sha256:6b8ffda597a14812a7975c90b82a8a2e777d9257aba3453f973acd3c032a18e2"}, @@ -2660,7 +2586,7 @@ version = "2.33.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, @@ -2772,7 +2698,7 @@ version = "2.11.0" description = "Settings management using Pydantic" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c"}, {file = "pydantic_settings-2.11.0.tar.gz", hash = "sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180"}, @@ -2811,7 +2737,7 @@ version = "3.2.5" description = "pyparsing - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e"}, {file = "pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6"}, @@ -2826,7 +2752,7 @@ version = "1.2.0" description = "Wrappers to call pyproject.toml-based build backend hooks." optional = false python-versions = ">=3.7" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913"}, {file = "pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8"}, @@ -2880,7 +2806,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -2895,7 +2821,7 @@ version = "1.1.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc"}, {file = "python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab"}, @@ -2910,7 +2836,7 @@ version = "3.3.0" description = "JSON Log Formatter for the Python Logging Package" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main"] files = [ {file = "python_json_logger-3.3.0-py3-none-any.whl", hash = "sha256:dd980fae8cffb24c13caf6e158d3d61c0d6d22342f932cb6e9deedab3d35eec7"}, {file = "python_json_logger-3.3.0.tar.gz", hash = "sha256:12b7e74b17775e7d565129296105bbe3910842d9d0eb083fc83a6a617aa8df84"}, @@ -2925,7 +2851,7 @@ version = "0.1.10" description = "A Fast, spec compliant Python 3.12+ tokenizer that runs on older Pythons." optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main"] files = [ {file = "pytokens-0.1.10-py3-none-any.whl", hash = "sha256:db7b72284e480e69fb085d9f251f66b3d2df8b7166059261258ff35f50fb711b"}, {file = "pytokens-0.1.10.tar.gz", hash = "sha256:c9a4bfa0be1d26aebce03e6884ba454e842f186a59ea43a6d3b25af58223c044"}, @@ -2952,7 +2878,7 @@ version = "0.2.3" description = "A (partial) reimplementation of pywin32 using ctypes/cffi" optional = false python-versions = ">=3.6" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] markers = "sys_platform == \"win32\"" files = [ {file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"}, @@ -2965,7 +2891,7 @@ version = "6.0.3" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" -groups = ["main", "dev", "qm-dev", "test"] +groups = ["main", "dev"] files = [ {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, @@ -3048,7 +2974,7 @@ version = "2.1.6" description = "SDK to control an Octave with QUA" optional = false python-versions = "<3.13,>=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "qm_octave-2.1.6-py3-none-any.whl", hash = "sha256:2e3df8085330ecf0eb1cc3dd661ebec7b3505959d0fabb3a2dc6175ec7c7cba3"}, {file = "qm_octave-2.1.6.tar.gz", hash = "sha256:8a16e6a8c76b060bc2856020a3f54f2e6d936ffc24e2b7963376ce990a8110be"}, @@ -3066,7 +2992,7 @@ version = "1.2.3" description = "QUA language SDK to control a Quantum Computer" optional = false python-versions = "<3.13,>=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "qm_qua-1.2.3-py3-none-any.whl", hash = "sha256:d719aa0f6ca8dc02e3f9941103942d2f260390ba1527857e08e164cd5e1e1df1"}, {file = "qm_qua-1.2.3.tar.gz", hash = "sha256:529cbb443c04f895cd9b2b663ac4617cb221dbdd490220d1de955a2a9bf1c9c5"}, @@ -3098,7 +3024,7 @@ version = "0.20.0" description = "The qualang_tools package includes various tools related to QUA programs in Python" optional = false python-versions = "<3.13,>=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "qualang_tools-0.20.0-py3-none-any.whl", hash = "sha256:0dc71d2c00eaa98da86b70e082cf0c59d9d61b1494d74bc5aee6b07b8f1bff9c"}, {file = "qualang_tools-0.20.0.tar.gz", hash = "sha256:23de4e0dc81387e45803afdf58c51f1a9c6f8b6ea650becda7d3b31904b0d342"}, @@ -3119,125 +3045,48 @@ interplot = ["dill (>=0.3.4,<0.4.0)", "ipython (>=8.10.0,<9.0.0)", "pypiwin32 (> two-qubit-rb = ["cirq (==1.2.0) ; python_version >= \"3.10\"", "tqdm (>=4.67.1,<5.0.0)", "xarray (>=2023.0.0,<2024.0.0)"] [[package]] -name = "qualibrate" -version = "0.3.1" -description = "Qubit calibration software" +name = "qualibrate-config" +version = "0.1.8" +description = "Configuration for Qualibrate" optional = false -python-versions = "<3.13,>=3.10" -groups = ["test"] +python-versions = "<4.0,>=3.10" +groups = ["main", "test"] files = [ - {file = "qualibrate-0.3.1-py3-none-any.whl", hash = "sha256:aa2adde1a62beb27d99681096a46cb71c5a45abf90e6928f1c307490f2801a67"}, - {file = "qualibrate-0.3.1.tar.gz", hash = "sha256:d95fb8557bb5c410cb8431e944c91c9abe763960f6b6ac6cf6041d0eb8668f65"}, + {file = "qualibrate_config-0.1.8-py3-none-any.whl", hash = "sha256:58969295ada9253540e18208387acad9097a4883051dd8ed72887e89b4f6d37f"}, + {file = "qualibrate_config-0.1.8.tar.gz", hash = "sha256:7deccb0d160a0f1863ca43c87c5712d7a7e40a4c89fa03a8ed06d2ff552a1176"}, ] [package.dependencies] -a2wsgi = ">=1.10.8,<2.0.0" click = ">=8.1.7,<8.2.0" -fastapi = ">=0.117.1,<0.118.0" -pydantic = ">=2.11.9,<3.0.0" -pydantic-settings = ">=2.11.0,<3.0.0" -qualibrate-app = "0.4.1" -qualibrate-config = "0.1.8" -qualibrate-core = "0.4.1" -qualibrate-runner = "0.4.0" +jsonpatch = ">=1.33,<2.0" +pydantic = ">=2.9.2,<3.0.0" +pydantic-settings = ">=2.6.1,<3.0.0" tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""} tomli-w = ">=1.0.0,<2.0.0" -uvicorn = ">=0.37.0,<0.38.0" [[package]] -name = "qualibrate-app" +name = "qualibrate-core" version = "0.4.1" description = "" optional = false python-versions = "<3.13,>=3.10" -groups = ["test"] +groups = ["main"] files = [ - {file = "qualibrate_app-0.4.1-py3-none-any.whl", hash = "sha256:7157d3996ddfa38fad551666f14d368beac6b544c76fbc3cf5c2d1f84e26d49f"}, - {file = "qualibrate_app-0.4.1.tar.gz", hash = "sha256:f948bfd764042d158ad59e59889a8560c9674813121800f4594e4d466cbeb829"}, + {file = "qualibrate_core-0.4.1-py3-none-any.whl", hash = "sha256:c1124aee019e5774079705d5bd04b91e8e2e4911de26a9e16830256a1c13fd83"}, + {file = "qualibrate_core-0.4.1.tar.gz", hash = "sha256:969047b8ca0156d2f27f4a838cf1198b32732c51610abf39d426a8c8c2e5949f"}, ] [package.dependencies] -click = ">=8.1.7,<8.2.0" -fastapi = ">=0.117.1,<0.118.0" +datamodel-code-generator = ">=0.33.0,<0.34.0" jsonpatch = ">=1.33,<2.0" -qualibrate-config = ">=0.1.7,<0.2.0" -requests = ">=2.32.4,<3.0.0" -tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""} -tomli-w = ">=1.0.0,<2.0.0" -uvicorn = ">=0.37.0,<0.38.0" - -[[package]] -name = "qualibrate-config" -version = "0.1.8" -description = "Configuration for Qualibrate" -optional = false -python-versions = "^3.10" -groups = ["main", "qm-dev", "test"] -files = [] -develop = true - -[package.dependencies] -click = "^8.1.7,<8.2.0" -jsonpatch = "^1.33" -pydantic = "^2.9.2" -pydantic-settings = "^2.6.1" -tomli = {version = "^2.0.1", markers = "python_version < \"3.11\""} -tomli-w = "^1.0.0" - -[package.source] -type = "directory" -url = "../qualibrate-config" - -[[package]] -name = "qualibrate-core" -version = "0.4.1" -description = "" -optional = false -python-versions = "^3.10,<3.13" -groups = ["main", "qm-dev", "test"] -files = [] -develop = true - -[package.dependencies] -datamodel-code-generator = "^0.33.0" -jsonpatch = "^1.33" -jsonpointer = "^3.0.0" -matplotlib = "^3.10.6" -networkx = "~3.4.2" -pydantic = "^2.9.2" -python-json-logger = "^3.2.1" +jsonpointer = ">=3.0.0,<4.0.0" +matplotlib = ">=3.10.6,<4.0.0" +networkx = ">=3.4.2,<3.5.0" +pydantic = ">=2.9.2,<3.0.0" +python-json-logger = ">=3.2.1,<4.0.0" qualang-tools = {version = ">=0.20.0", markers = "python_version >= \"3.10\" and python_version < \"3.13\""} -qualibrate-config = "^0.1.7,<0.2.0" -types-networkx = "~3.4.2.20250527" - -[package.source] -type = "directory" -url = "../qualibrate-core" - -[[package]] -name = "qualibration-libs" -version = "0.2.1" -description = "Utility library supporting calibration nodes and graphs for the QUAlibration graphs platform." -optional = false -python-versions = ">=3.9, <3.13" -groups = ["test"] -files = [] -develop = true - -[package.dependencies] -colorama = ">=0.4.6" -lmfit = ">=1.3.3" -matplotlib = ">=3.10.0" -numpy = ">=1.20.0" -qm-qua = ">=1.2.2" -qualibrate = ">=0.2.1" -quam-builder = ">=0.1.2" -scipy = ">=1.13.1" -xarray = ">=2024.7.0" - -[package.source] -type = "directory" -url = "../qualibration-libs" +qualibrate-config = ">=0.1.7,<0.2.0" +types-networkx = ">=3.4.2.20250527,<3.5.0.0" [[package]] name = "quam" @@ -3291,7 +3140,7 @@ version = "3.14.1" description = "rapid fuzzy string matching" optional = false python-versions = ">=3.10" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "rapidfuzz-3.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:489440e4b5eea0d150a31076eb183bed0ec84f934df206c72ae4fc3424501758"}, {file = "rapidfuzz-3.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eff22cc938c3f74d194df03790a6c3325d213b28cf65cdefd6fdeae759b745d5"}, @@ -3396,7 +3245,7 @@ version = "2.32.5" description = "Python HTTP for Humans." optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, @@ -3418,7 +3267,7 @@ version = "1.0.0" description = "A utility belt for advanced users of python-requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, @@ -3462,7 +3311,7 @@ version = "1.7.2" description = "A set of python modules for machine learning and data mining" optional = false python-versions = ">=3.10" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "scikit_learn-1.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b33579c10a3081d076ab403df4a4190da4f4432d443521674637677dc91e61f"}, {file = "scikit_learn-1.7.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:36749fb62b3d961b1ce4fedf08fa57a1986cd409eff2d783bca5d4b9b5fce51c"}, @@ -3518,7 +3367,7 @@ version = "1.15.3" description = "Fundamental algorithms for scientific computing in Python" optional = false python-versions = ">=3.10" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c"}, {file = "scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253"}, @@ -3582,7 +3431,7 @@ version = "3.4.0" description = "Python bindings to FreeDesktop.org Secret Service API" optional = false python-versions = ">=3.10" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] markers = "sys_platform == \"linux\"" files = [ {file = "secretstorage-3.4.0-py3-none-any.whl", hash = "sha256:0e3b6265c2c63509fb7415717607e4b2c9ab767b7f344a57473b779ca13bd02e"}, @@ -3599,7 +3448,7 @@ version = "1.5.4" description = "Tool to Detect Surrounding Shell" optional = false python-versions = ">=3.7" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, @@ -3611,7 +3460,7 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -3623,7 +3472,7 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -3635,7 +3484,7 @@ version = "0.48.0" description = "The little ASGI library that shines." optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main"] files = [ {file = "starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659"}, {file = "starlette-0.48.0.tar.gz", hash = "sha256:7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46"}, @@ -3654,7 +3503,7 @@ version = "9.1.2" description = "Retry code until it succeeds" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138"}, {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"}, @@ -3670,7 +3519,7 @@ version = "3.6.0" description = "threadpoolctl" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb"}, {file = "threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e"}, @@ -3682,7 +3531,7 @@ version = "4.8.2" description = "TinyDB is a tiny, document oriented database optimized for your happiness :)" optional = false python-versions = "<4.0,>=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "tinydb-4.8.2-py3-none-any.whl", hash = "sha256:f97030ee5cbc91eeadd1d7af07ab0e48ceb04aa63d4a983adbaca4cba16e86c3"}, {file = "tinydb-4.8.2.tar.gz", hash = "sha256:f7dfc39b8d7fda7a1ca62a8dbb449ffd340a117c1206b68c50b1a481fb95181d"}, @@ -3694,7 +3543,7 @@ version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" -groups = ["main", "dev", "qm-dev", "test"] +groups = ["main", "dev", "test"] files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -3729,7 +3578,7 @@ files = [ {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] -markers = {main = "python_version < \"3.12\"", dev = "python_version == \"3.10\"", qm-dev = "python_version < \"3.12\"", test = "python_version < \"3.12\""} +markers = {main = "python_version < \"3.12\"", dev = "python_version == \"3.10\"", test = "python_version == \"3.10\""} [[package]] name = "tomli-w" @@ -3737,7 +3586,7 @@ version = "1.2.0" description = "A lil' TOML writer" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90"}, {file = "tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021"}, @@ -3749,7 +3598,7 @@ version = "0.13.3" description = "Style preserving TOML library" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0"}, {file = "tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1"}, @@ -3761,7 +3610,7 @@ version = "2025.9.11.17" description = "Canonical source for classifiers on PyPI (pypi.org)." optional = false python-versions = "*" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "trove_classifiers-2025.9.11.17-py3-none-any.whl", hash = "sha256:5d392f2d244deb1866556457d6f3516792124a23d1c3a463a2e8668a5d1c15dd"}, {file = "trove_classifiers-2025.9.11.17.tar.gz", hash = "sha256:931ca9841a5e9c9408bc2ae67b50d28acf85bef56219b56860876dd1f2d024dd"}, @@ -3773,7 +3622,7 @@ version = "4.4.4" description = "Run-time type checker for Python" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "typeguard-4.4.4-py3-none-any.whl", hash = "sha256:b5f562281b6bfa1f5492470464730ef001646128b180769880468bd84b68b09e"}, {file = "typeguard-4.4.4.tar.gz", hash = "sha256:3a7fd2dffb705d4d0efaed4306a704c89b9dee850b688f060a8b1615a79e5f74"}, @@ -3788,7 +3637,7 @@ version = "3.4.2.20250527" description = "Typing stubs for networkx" optional = false python-versions = ">=3.10" -groups = ["main", "qm-dev", "test"] +groups = ["main"] files = [ {file = "types_networkx-3.4.2.20250527-py3-none-any.whl", hash = "sha256:3716f750e61bf46ceba2a43474d7f9b61f306274a243cee6cc2aa9f4fb37ab41"}, {file = "types_networkx-3.4.2.20250527.tar.gz", hash = "sha256:484914f0dee6ce8d87bda494f641a9227e954d716d2f330bfcbf69c2431b646d"}, @@ -3803,7 +3652,7 @@ version = "4.15.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" -groups = ["main", "dev", "qm-dev", "test"] +groups = ["main", "dev", "test"] files = [ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, @@ -3815,7 +3664,7 @@ version = "0.4.1" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"}, {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"}, @@ -3836,31 +3685,13 @@ files = [ {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}, ] -[[package]] -name = "uncertainties" -version = "3.2.3" -description = "calculations with values with uncertainties, error propagation" -optional = false -python-versions = ">=3.8" -groups = ["test"] -files = [ - {file = "uncertainties-3.2.3-py3-none-any.whl", hash = "sha256:313353900d8f88b283c9bad81e7d2b2d3d4bcc330cbace35403faaed7e78890a"}, - {file = "uncertainties-3.2.3.tar.gz", hash = "sha256:76a5653e686f617a42922d546a239e9efce72e6b35411b7750a1d12dcba03031"}, -] - -[package.extras] -all = ["uncertainties[arrays,doc,test]"] -arrays = ["numpy"] -doc = ["python-docs-theme", "sphinx", "sphinx-copybutton"] -test = ["pytest", "pytest_codspeed", "pytest_cov", "scipy"] - [[package]] name = "urllib3" version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, @@ -3878,7 +3709,7 @@ version = "0.37.0" description = "The lightning-fast ASGI server." optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main"] files = [ {file = "uvicorn-0.37.0-py3-none-any.whl", hash = "sha256:913b2b88672343739927ce381ff9e2ad62541f9f8289664fa1d1d3803fa2ce6c"}, {file = "uvicorn-0.37.0.tar.gz", hash = "sha256:4115c8add6d3fd536c8ee77f0e14a7fd2ebba939fed9b02583a97f80648f9e13"}, @@ -3898,7 +3729,7 @@ version = "20.34.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "virtualenv-20.34.0-py3-none-any.whl", hash = "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026"}, {file = "virtualenv-20.34.0.tar.gz", hash = "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a"}, @@ -4025,7 +3856,7 @@ version = "1.2.0" description = "Python wrapper for extended filesystem attributes" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] markers = "sys_platform == \"darwin\"" files = [ {file = "xattr-1.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3df4d8d91e2996c3c72a390ec82e8544acdcb6c7df67b954f1736ff37ea4293e"}, @@ -4112,7 +3943,7 @@ version = "3.23.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] markers = "python_version < \"3.12\"" files = [ {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, @@ -4133,7 +3964,7 @@ version = "0.25.0" description = "Zstandard bindings for Python" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "zstandard-0.25.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e59fdc271772f6686e01e1b3b74537259800f57e24280be3f29c8a0deb1904dd"}, {file = "zstandard-0.25.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4d441506e9b372386a5271c64125f72d5df6d2a8e8a2a45a0ae09b03cb781ef7"}, @@ -4242,4 +4073,4 @@ cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and pyt [metadata] lock-version = "2.1" python-versions = "^3.10,<3.13" -content-hash = "4657da2105785294b056c411f4051f2519da097de910c60faa3289f85236df02" +content-hash = "ac8ecc77313a00c733a03b8e1f0d98302b24daecf699a2f87141394b5ce3b738" From baa9b96ce9e75f9bd9943bdb666a1d153c000e38 Mon Sep 17 00:00:00 2001 From: Jonatan Kutchinsky Date: Wed, 26 Nov 2025 16:57:44 +0100 Subject: [PATCH 14/26] Trying to fix github actions --- .github/workflows/check.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index e22b73e..2902c6d 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -18,11 +18,9 @@ jobs: - uses: actions/setup-python@v5 with: python-version: "3.10" - cache: "poetry" - cache-dependency-path: poetry.lock - name: Install python deps - run: poetry install --with dev + run: poetry install --with dev --no-root - name: Check python package typing run: poetry run poe type From 4a3bfb08b09cb2a2bdd105bbc1dce7178c74cf46 Mon Sep 17 00:00:00 2001 From: Jonatan Kutchinsky Date: Wed, 26 Nov 2025 17:00:47 +0100 Subject: [PATCH 15/26] Trying to fix github actions --- .github/workflows/check.yaml | 4 +- poetry.lock | 453 ++++++++++++++++++++++++----------- 2 files changed, 314 insertions(+), 143 deletions(-) diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index 2902c6d..9078cb5 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -20,7 +20,9 @@ jobs: python-version: "3.10" - name: Install python deps - run: poetry install --with dev --no-root + run: | + rm poetry.lock + poetry install --with dev --no-root - name: Check python package typing run: poetry run poe type diff --git a/poetry.lock b/poetry.lock index 23acc32..5f4a6bc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,12 +1,27 @@ # This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. +[[package]] +name = "a2wsgi" +version = "1.10.10" +description = "Convert WSGI app to ASGI app or ASGI app to WSGI app." +optional = false +python-versions = ">=3.8.0" +groups = ["test"] +files = [ + {file = "a2wsgi-1.10.10-py3-none-any.whl", hash = "sha256:d2b21379479718539dc15fce53b876251a0efe7615352dfe49f6ad1bc507848d"}, + {file = "a2wsgi-1.10.10.tar.gz", hash = "sha256:a5bcffb52081ba39df0d5e9a884fc6f819d92e3a42389343ba77cbf809fe1f45"}, +] + +[package.dependencies] +typing_extensions = {version = "*", markers = "python_version < \"3.11\""} + [[package]] name = "annotated-types" version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] 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"}, @@ -18,7 +33,7 @@ version = "4.11.0" description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc"}, {file = "anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4"}, @@ -39,7 +54,7 @@ version = "3.6.2" description = "Bash tab completion for argparse" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "argcomplete-3.6.2-py3-none-any.whl", hash = "sha256:65b3133a29ad53fb42c48cf5114752c7ab66c1c38544fdf6460f450c09b42591"}, {file = "argcomplete-3.6.2.tar.gz", hash = "sha256:d0519b1bc867f5f4f4713c41ad0aba73a4a5f007449716b16f385f2166dc6adf"}, @@ -48,13 +63,31 @@ files = [ [package.extras] test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] +[[package]] +name = "asteval" +version = "1.0.7" +description = "Safe, minimalistic evaluator of python expression using ast module" +optional = false +python-versions = ">=3.10" +groups = ["test"] +files = [ + {file = "asteval-1.0.7-py3-none-any.whl", hash = "sha256:d78df08681dfff59031ca624ba7030f9dc576a7a16e2f7a5137c6e7ef3ee60c4"}, + {file = "asteval-1.0.7.tar.gz", hash = "sha256:729fa010511957afcbdbadb0304da09cef6904c145b6901e917afac2dd5668a1"}, +] + +[package.extras] +all = ["asteval[dev,doc,test]"] +dev = ["build", "twine"] +doc = ["Sphinx"] +test = ["coverage", "pytest", "pytest-cov"] + [[package]] name = "backports-tarfile" version = "1.2.0" description = "Backport of CPython tarfile module" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] markers = "python_version < \"3.12\"" files = [ {file = "backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34"}, @@ -71,7 +104,7 @@ version = "2.0.0b7" description = "A better Protobuf / gRPC generator & library" optional = false python-versions = "<4.0,>=3.7" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "betterproto-2.0.0b7-py3-none-any.whl", hash = "sha256:401ab8055e2f814e77b9c88a74d0e1ae3d1e8a969cced6aeb1b59f71ad63fbd2"}, {file = "betterproto-2.0.0b7.tar.gz", hash = "sha256:1b1458ca5278d519bcd62556a4c236f998a91d503f0f71c67b0b954747052af2"}, @@ -92,7 +125,7 @@ version = "25.9.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "black-25.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce41ed2614b706fd55fd0b4a6909d06b5bab344ffbfadc6ef34ae50adba3d4f7"}, {file = "black-25.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ab0ce111ef026790e9b13bd216fa7bc48edd934ffc4cbf78808b235793cbc92"}, @@ -140,7 +173,7 @@ version = "1.3.0" description = "A simple, correct Python build frontend" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "build-1.3.0-py3-none-any.whl", hash = "sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4"}, {file = "build-1.3.0.tar.gz", hash = "sha256:698edd0ea270bde950f53aed21f3a0135672206f3911e0176261a31e0e07b397"}, @@ -163,7 +196,7 @@ version = "0.14.3" description = "httplib2 caching for requests" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "cachecontrol-0.14.3-py3-none-any.whl", hash = "sha256:b35e44a3113f17d2a31c1e6b27b9de6d4405f84ae51baa8c1d3cc5b633010cae"}, {file = "cachecontrol-0.14.3.tar.gz", hash = "sha256:73e7efec4b06b20d9267b441c1f733664f989fb8688391b670ca812d70795d11"}, @@ -185,7 +218,7 @@ version = "2025.8.3" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, @@ -197,8 +230,7 @@ version = "2.0.0" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.9" -groups = ["main", "test"] -markers = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and (sys_platform == \"linux\" or sys_platform == \"darwin\")" +groups = ["main", "qm-dev", "test"] files = [ {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, @@ -285,6 +317,7 @@ files = [ {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"}, {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"}, ] +markers = {main = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and (sys_platform == \"linux\" or sys_platform == \"darwin\")", qm-dev = "(sys_platform == \"linux\" or sys_platform == \"darwin\") and (platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\")", test = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and (sys_platform == \"linux\" or sys_platform == \"darwin\")"} [package.dependencies] pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} @@ -295,7 +328,7 @@ version = "3.4.3" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, @@ -384,7 +417,7 @@ version = "2.1.0" description = "Cleo allows you to create beautiful and testable command-line interfaces." optional = false python-versions = ">=3.7,<4.0" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "cleo-2.1.0-py3-none-any.whl", hash = "sha256:4a31bd4dd45695a64ee3c4758f583f134267c2bc518d8ae9a29cf237d009b07e"}, {file = "cleo-2.1.0.tar.gz", hash = "sha256:0b2c880b5d13660a7ea651001fb4acb527696c01f15c9ee650f377aa543fd523"}, @@ -400,7 +433,7 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -415,12 +448,12 @@ 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" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {main = "platform_system == \"Windows\" or os_name == \"nt\"", test = "platform_system == \"Windows\" or os_name == \"nt\" or sys_platform == \"win32\""} +markers = {main = "platform_system == \"Windows\" or os_name == \"nt\"", qm-dev = "os_name == \"nt\" or platform_system == \"Windows\""} [[package]] name = "contourpy" @@ -428,7 +461,7 @@ version = "1.3.2" description = "Python library for calculating contours of 2D quadrilateral grids" optional = false python-versions = ">=3.10" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934"}, {file = "contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989"}, @@ -505,7 +538,7 @@ version = "0.4.1" description = "Manage Python errors with ease" optional = false python-versions = ">=3.7,<4.0" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "crashtest-0.4.1-py3-none-any.whl", hash = "sha256:8d23eac5fa660409f57472e3851dab7ac18aba459a8d19cbbba86d3d5aecd2a5"}, {file = "crashtest-0.4.1.tar.gz", hash = "sha256:80d7b1f316ebfbd429f648076d6275c877ba30ba48979de4191714a75266f0ce"}, @@ -517,7 +550,7 @@ version = "46.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = "!=3.9.0,!=3.9.1,>=3.8" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] markers = "sys_platform == \"linux\"" files = [ {file = "cryptography-46.0.1-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:1cd6d50c1a8b79af1a6f703709d8973845f677c8e97b1268f5ff323d38ce8475"}, @@ -596,7 +629,7 @@ version = "0.12.1" description = "Composable style cycles" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, @@ -612,7 +645,7 @@ version = "2.43.0" description = "Collection of all Datadog Public endpoints" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "datadog_api_client-2.43.0-py3-none-any.whl", hash = "sha256:1d038b545263ad0b665f46d9d36dc0f0ed491770d9dcd68380be4685a9ae6cc3"}, {file = "datadog_api_client-2.43.0.tar.gz", hash = "sha256:f3aec5db9bcceb39f6315e505eea908da7133a19f8475ef996ef8be8e3c84855"}, @@ -636,7 +669,7 @@ version = "0.33.0" description = "Datamodel Code Generator" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "datamodel_code_generator-0.33.0-py3-none-any.whl", hash = "sha256:e229264aa612b2d5bb4901bcd6c520a799ae0d5c19262577a0f876eb48afaaa3"}, {file = "datamodel_code_generator-0.33.0.tar.gz", hash = "sha256:7635ef788201d69bd3e98ba88ce6afe479400dc2737fe9d5e21f87408f352c08"}, @@ -668,7 +701,7 @@ version = "4.48.2" description = "Dependency injection framework for Python" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "dependency_injector-4.48.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:54d0178be10f17b768afb5c0ed1c5c565abaa2d097b2bc5a529a31c580613df2"}, {file = "dependency_injector-4.48.2-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:12a15979fd534b728b3061c8aa52fd55adb77574758817daae9df8a1c2eb830b"}, @@ -712,13 +745,29 @@ pydantic = ["pydantic"] pydantic2 = ["pydantic-settings"] yaml = ["pyyaml"] +[[package]] +name = "dill" +version = "0.4.0" +description = "serialize all of Python" +optional = false +python-versions = ">=3.8" +groups = ["test"] +files = [ + {file = "dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049"}, + {file = "dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0"}, +] + +[package.extras] +graph = ["objgraph (>=1.7.2)"] +profile = ["gprof2dot (>=2022.7.29)"] + [[package]] name = "distlib" version = "0.4.0" description = "Distribution utilities" optional = false python-versions = "*" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, @@ -730,7 +779,7 @@ version = "0.22.8" description = "Python Git Library" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "dulwich-0.22.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:546176d18b8cc0a492b0f23f07411e38686024cffa7e9d097ae20512a2e57127"}, {file = "dulwich-0.22.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d2434dd72b2ae09b653c9cfe6764a03c25cfbd99fbbb7c426f0478f6fb1100f"}, @@ -797,7 +846,7 @@ version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] markers = "python_version == \"3.10\"" files = [ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, @@ -816,7 +865,7 @@ version = "0.117.1" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "test"] files = [ {file = "fastapi-0.117.1-py3-none-any.whl", hash = "sha256:33c51a0d21cab2b9722d4e56dbb9316f3687155be6b276191790d8da03507552"}, {file = "fastapi-0.117.1.tar.gz", hash = "sha256:fb2d42082d22b185f904ca0ecad2e195b851030bd6c5e4c032d1c981240c631a"}, @@ -838,7 +887,7 @@ version = "2.21.2" description = "Fastest Python implementation of JSON schema" optional = false python-versions = "*" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463"}, {file = "fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de"}, @@ -853,7 +902,7 @@ version = "3.19.1" description = "A platform independent file lock." optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d"}, {file = "filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58"}, @@ -865,7 +914,7 @@ version = "0.6.3" description = "A utility to find python versions on your system" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "findpython-0.6.3-py3-none-any.whl", hash = "sha256:a85bb589b559cdf1b87227cc233736eb7cad894b9e68021ee498850611939ebc"}, {file = "findpython-0.6.3.tar.gz", hash = "sha256:5863ea55556d8aadc693481a14ac4f3624952719efc1c5591abb0b4a9e965c94"}, @@ -880,7 +929,7 @@ version = "4.60.1" description = "Tools to manipulate font files" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "fonttools-4.60.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9a52f254ce051e196b8fe2af4634c2d2f02c981756c6464dc192f1b6050b4e28"}, {file = "fonttools-4.60.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c7420a2696a44650120cdd269a5d2e56a477e2bfa9d95e86229059beb1c19e15"}, @@ -976,7 +1025,7 @@ version = "1.3.0" description = "GenSON is a powerful, user-friendly JSON Schema generator." optional = false python-versions = "*" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "genson-1.3.0-py3-none-any.whl", hash = "sha256:468feccd00274cc7e4c09e84b08704270ba8d95232aa280f65b986139cec67f7"}, {file = "genson-1.3.0.tar.gz", hash = "sha256:e02db9ac2e3fd29e65b5286f7135762e2cd8a986537c075b06fc5f1517308e37"}, @@ -988,7 +1037,7 @@ version = "1.75.1" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "grpcio-1.75.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:1712b5890b22547dd29f3215c5788d8fc759ce6dd0b85a6ba6e2731f2d04c088"}, {file = "grpcio-1.75.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8d04e101bba4b55cea9954e4aa71c24153ba6182481b487ff376da28d4ba46cf"}, @@ -1065,7 +1114,7 @@ version = "0.4.5" description = "Pure-Python gRPC implementation for asyncio" optional = false python-versions = ">=3.7" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "grpclib-0.4.5.tar.gz", hash = "sha256:bf83ed55aca59497e168761d9555056efc54a8f865316c3b39becd007e9f9a73"}, ] @@ -1083,7 +1132,7 @@ version = "0.16.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, @@ -1095,7 +1144,7 @@ version = "4.3.0" description = "Pure-Python HTTP/2 protocol implementation" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd"}, {file = "h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1"}, @@ -1111,7 +1160,7 @@ version = "4.1.0" description = "Pure-Python HPACK header encoding" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496"}, {file = "hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca"}, @@ -1123,7 +1172,7 @@ version = "1.0.9" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, @@ -1145,7 +1194,7 @@ version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -1171,7 +1220,7 @@ version = "6.1.0" description = "Pure-Python HTTP/2 framing" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5"}, {file = "hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08"}, @@ -1183,7 +1232,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -1198,7 +1247,7 @@ version = "8.7.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] markers = "python_version < \"3.12\"" files = [ {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, @@ -1223,7 +1272,7 @@ version = "7.5.0" description = "Correctly generate plurals, singular nouns, ordinals, indefinite articles" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "inflect-7.5.0-py3-none-any.whl", hash = "sha256:2aea70e5e70c35d8350b8097396ec155ffd68def678c7ff97f51aa69c1d92344"}, {file = "inflect-7.5.0.tar.gz", hash = "sha256:faf19801c3742ed5a05a8ce388e0d8fe1a07f8d095c82201eb904f5d27ad571f"}, @@ -1259,7 +1308,7 @@ version = "0.7.0" description = "A library for installing Python wheels." optional = false python-versions = ">=3.7" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "installer-0.7.0-py3-none-any.whl", hash = "sha256:05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53"}, {file = "installer-0.7.0.tar.gz", hash = "sha256:a26d3e3116289bb08216e0d0f7d925fcef0b0194eedfa0c944bcaaa106c4b631"}, @@ -1271,7 +1320,7 @@ version = "6.0.1" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.9.0" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615"}, {file = "isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450"}, @@ -1287,7 +1336,7 @@ version = "3.4.0" description = "Utility functions for Python class constructs" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790"}, {file = "jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd"}, @@ -1306,7 +1355,7 @@ version = "6.0.1" description = "Useful decorators and context managers" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "jaraco.context-6.0.1-py3-none-any.whl", hash = "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4"}, {file = "jaraco_context-6.0.1.tar.gz", hash = "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3"}, @@ -1325,7 +1374,7 @@ version = "4.3.0" description = "Functools like those found in stdlib" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "jaraco_functools-4.3.0-py3-none-any.whl", hash = "sha256:227ff8ed6f7b8f62c56deff101545fa7543cf2c8e7b82a7c2116e672f29c26e8"}, {file = "jaraco_functools-4.3.0.tar.gz", hash = "sha256:cfd13ad0dd2c47a3600b439ef72d8615d482cedcff1632930d6f28924d92f294"}, @@ -1348,7 +1397,7 @@ version = "0.9.0" description = "Low-level, pure Python DBus protocol wrapper." optional = false python-versions = ">=3.7" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] markers = "sys_platform == \"linux\"" files = [ {file = "jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683"}, @@ -1365,7 +1414,7 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -1383,7 +1432,7 @@ version = "1.5.2" description = "Lightweight pipelining with Python functions" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241"}, {file = "joblib-1.5.2.tar.gz", hash = "sha256:3faa5c39054b2f03ca547da9b2f52fde67c06240c31853f306aea97f13647b55"}, @@ -1395,7 +1444,7 @@ version = "1.33" description = "Apply JSON-Patches (RFC 6902)" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, @@ -1410,7 +1459,7 @@ version = "3.0.0" description = "Identify specific nodes in a JSON document (RFC 6901)" optional = false python-versions = ">=3.7" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, @@ -1422,7 +1471,7 @@ version = "25.6.0" description = "Store and access your passwords safely." optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd"}, {file = "keyring-25.6.0.tar.gz", hash = "sha256:0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66"}, @@ -1452,7 +1501,7 @@ version = "1.4.9" description = "A fast implementation of the Cassowary constraint solver" optional = false python-versions = ">=3.10" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b4b4d74bda2b8ebf4da5bd42af11d02d04428b2c32846e4c2c93219df8a7987b"}, {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fb3b8132019ea572f4611d770991000d7f58127560c4889729248eb5852a102f"}, @@ -1557,13 +1606,38 @@ files = [ {file = "kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d"}, ] +[[package]] +name = "lmfit" +version = "1.3.4" +description = "Least-Squares Minimization with Bounds and Constraints" +optional = false +python-versions = ">=3.9" +groups = ["test"] +files = [ + {file = "lmfit-1.3.4-py3-none-any.whl", hash = "sha256:afce1593b42324d37ae2908249b0c55445e2f4c1a0474ff706a8e2f7b5d949fa"}, + {file = "lmfit-1.3.4.tar.gz", hash = "sha256:3c22c28c43f717f6c5b4a3bd81e893a2149739c26a592c046f2e33c23cfbe497"}, +] + +[package.dependencies] +asteval = ">=1.0" +dill = ">=0.3.4" +numpy = ">=1.24" +scipy = ">=1.10.0" +uncertainties = ">=3.2.2" + +[package.extras] +all = ["lmfit[dev,doc,test]"] +dev = ["build", "check-wheel-contents", "flake8-pyproject", "pre-commit", "twine"] +doc = ["Pillow", "Sphinx", "cairosvg", "corner", "emcee (>=3.0.0)", "ipykernel", "jupyter_sphinx (>=0.2.4)", "matplotlib", "numdifftools", "pandas", "pycairo ; platform_system == \"Windows\"", "sphinx-gallery (>=0.10)", "sphinxcontrib-svg2pdfconverter", "sympy"] +test = ["coverage", "flaky", "pytest", "pytest-cov"] + [[package]] name = "markupsafe" version = "3.0.3" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, @@ -1662,7 +1736,7 @@ version = "3.23.3" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "marshmallow-3.23.3-py3-none-any.whl", hash = "sha256:20c0f8c613f68bcb45b2a0d3282e2f172575560170bf220d67aafb42717910e4"}, {file = "marshmallow-3.23.3.tar.gz", hash = "sha256:d586c8685ebdb80bf754e1f96e3f305aaf30951f1fc69175b977453633467e76"}, @@ -1682,7 +1756,7 @@ version = "5.11" description = "An unofficial extension to Marshmallow to allow for polymorphic fields" optional = false python-versions = ">=3.5" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "marshmallow-polyfield-5.11.tar.gz", hash = "sha256:8075a9cc490da4af58b902b4a40a99882dd031adb7aaa96abd147a4fcd53415f"}, ] @@ -1696,7 +1770,7 @@ version = "3.10.6" description = "Python plotting package" optional = false python-versions = ">=3.10" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "matplotlib-3.10.6-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:bc7316c306d97463a9866b89d5cc217824e799fa0de346c8f68f4f3d27c8693d"}, {file = "matplotlib-3.10.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d00932b0d160ef03f59f9c0e16d1e3ac89646f7785165ce6ad40c842db16cc2e"}, @@ -1775,7 +1849,7 @@ version = "10.8.0" description = "More routines for operating on iterables, beyond itertools" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b"}, {file = "more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd"}, @@ -1787,7 +1861,7 @@ version = "1.1.1" description = "MessagePack serializer" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "msgpack-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:353b6fc0c36fde68b661a12949d7d49f8f51ff5fa019c1e47c87c4ff34b080ed"}, {file = "msgpack-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:79c408fcf76a958491b4e3b103d1c417044544b68e96d06432a189b43d1215c8"}, @@ -1856,7 +1930,7 @@ version = "6.6.4" description = "multidict implementation" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "multidict-6.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b8aa6f0bd8125ddd04a6593437bad6a7e70f300ff4180a531654aa2ab3f6d58f"}, {file = "multidict-6.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9e5853bbd7264baca42ffc53391b490d65fe62849bf2c690fa3f6273dbcd0cb"}, @@ -2040,7 +2114,7 @@ version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "qm-dev", "test"] files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, @@ -2052,7 +2126,7 @@ version = "3.4.2" description = "Python package for creating and manipulating graphs and networks" optional = false python-versions = ">=3.10" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f"}, {file = "networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1"}, @@ -2072,7 +2146,7 @@ version = "1.26.4" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, @@ -2118,7 +2192,7 @@ version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, @@ -2242,7 +2316,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "qm-dev", "test"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -2254,7 +2328,7 @@ version = "2025.9.18" description = "Installer for Python Build Standalone" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "pbs_installer-2025.9.18-py3-none-any.whl", hash = "sha256:8ef55d7675698747505c237015d14c81759bd66a0d4c8b20cec9a2dc96e8434c"}, {file = "pbs_installer-2025.9.18.tar.gz", hash = "sha256:c0a51a7c1e015723bd8396f02e15b5876e439f74b0f45bbac436b189f903219f"}, @@ -2275,7 +2349,7 @@ version = "11.3.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860"}, {file = "pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"}, @@ -2400,7 +2474,7 @@ version = "1.12.1.2" description = "Query metadata from sdists / bdists / installed packages." optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "pkginfo-1.12.1.2-py3-none-any.whl", hash = "sha256:c783ac885519cab2c34927ccfa6bf64b5a704d7c69afaea583dd9b7afe969343"}, {file = "pkginfo-1.12.1.2.tar.gz", hash = "sha256:5cd957824ac36f140260964eba3c6be6442a8359b8c48f4adf90210f33a04b7b"}, @@ -2415,7 +2489,7 @@ version = "4.4.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85"}, {file = "platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"}, @@ -2432,7 +2506,7 @@ version = "5.24.1" description = "An open-source, interactive data visualization library for Python" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "plotly-5.24.1-py3-none-any.whl", hash = "sha256:f67073a1e637eb0dc3e46324d9d51e2fe76e9727c892dde64ddf1e1b51f29089"}, {file = "plotly-5.24.1.tar.gz", hash = "sha256:dbc8ac8339d248a4bcc36e08a5659bacfe1b079390b8953533f4eb22169b4bae"}, @@ -2484,7 +2558,7 @@ version = "2.1.3" description = "Python dependency management and packaging made easy." optional = false python-versions = "<4.0,>=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "poetry-2.1.3-py3-none-any.whl", hash = "sha256:7054d3f97ccce7f31961ead16250407c4577bfe57e2037a190ae2913fc40a20c"}, {file = "poetry-2.1.3.tar.gz", hash = "sha256:f2c9bd6790b19475976d88ea4553bcc3533c0dc73f740edc4fffe9e2add50594"}, @@ -2520,7 +2594,7 @@ version = "2.1.3" description = "Poetry PEP 517 Build Backend" optional = false python-versions = "<4.0,>=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "poetry_core-2.1.3-py3-none-any.whl", hash = "sha256:2c704f05016698a54ca1d327f46ce2426d72eaca6ff614132c8477c292266771"}, {file = "poetry_core-2.1.3.tar.gz", hash = "sha256:0522a015477ed622c89aad56a477a57813cace0c8e7ff2a2906b7ef4a2e296a4"}, @@ -2532,7 +2606,7 @@ version = "6.32.1" description = "" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "protobuf-6.32.1-cp310-abi3-win32.whl", hash = "sha256:a8a32a84bc9f2aad712041b8b366190f71dde248926da517bde9e832e4412085"}, {file = "protobuf-6.32.1-cp310-abi3-win_amd64.whl", hash = "sha256:b00a7d8c25fa471f16bc8153d0e53d6c9e827f0953f3c09aaa4331c718cae5e1"}, @@ -2551,12 +2625,12 @@ version = "2.23" description = "C parser in Python" optional = false python-versions = ">=3.8" -groups = ["main", "test"] -markers = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and implementation_name != \"PyPy\" and (sys_platform == \"linux\" or sys_platform == \"darwin\")" +groups = ["main", "qm-dev", "test"] files = [ {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"}, {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}, ] +markers = {main = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and implementation_name != \"PyPy\" and (sys_platform == \"linux\" or sys_platform == \"darwin\")", qm-dev = "(sys_platform == \"linux\" or sys_platform == \"darwin\") and (platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and implementation_name != \"PyPy\"", test = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and implementation_name != \"PyPy\" and (sys_platform == \"linux\" or sys_platform == \"darwin\")"} [[package]] name = "pydantic" @@ -2564,7 +2638,7 @@ version = "2.11.9" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "pydantic-2.11.9-py3-none-any.whl", hash = "sha256:c42dd626f5cfc1c6950ce6205ea58c93efa406da65f479dcb4029d5934857da2"}, {file = "pydantic-2.11.9.tar.gz", hash = "sha256:6b8ffda597a14812a7975c90b82a8a2e777d9257aba3453f973acd3c032a18e2"}, @@ -2586,7 +2660,7 @@ version = "2.33.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, @@ -2698,7 +2772,7 @@ version = "2.11.0" description = "Settings management using Pydantic" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c"}, {file = "pydantic_settings-2.11.0.tar.gz", hash = "sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180"}, @@ -2737,7 +2811,7 @@ version = "3.2.5" description = "pyparsing - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e"}, {file = "pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6"}, @@ -2752,7 +2826,7 @@ version = "1.2.0" description = "Wrappers to call pyproject.toml-based build backend hooks." optional = false python-versions = ">=3.7" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913"}, {file = "pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8"}, @@ -2806,7 +2880,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -2821,7 +2895,7 @@ version = "1.1.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc"}, {file = "python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab"}, @@ -2836,7 +2910,7 @@ version = "3.3.0" description = "JSON Log Formatter for the Python Logging Package" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "python_json_logger-3.3.0-py3-none-any.whl", hash = "sha256:dd980fae8cffb24c13caf6e158d3d61c0d6d22342f932cb6e9deedab3d35eec7"}, {file = "python_json_logger-3.3.0.tar.gz", hash = "sha256:12b7e74b17775e7d565129296105bbe3910842d9d0eb083fc83a6a617aa8df84"}, @@ -2851,7 +2925,7 @@ version = "0.1.10" description = "A Fast, spec compliant Python 3.12+ tokenizer that runs on older Pythons." optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "pytokens-0.1.10-py3-none-any.whl", hash = "sha256:db7b72284e480e69fb085d9f251f66b3d2df8b7166059261258ff35f50fb711b"}, {file = "pytokens-0.1.10.tar.gz", hash = "sha256:c9a4bfa0be1d26aebce03e6884ba454e842f186a59ea43a6d3b25af58223c044"}, @@ -2878,7 +2952,7 @@ version = "0.2.3" description = "A (partial) reimplementation of pywin32 using ctypes/cffi" optional = false python-versions = ">=3.6" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] markers = "sys_platform == \"win32\"" files = [ {file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"}, @@ -2891,7 +2965,7 @@ version = "6.0.3" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "qm-dev", "test"] files = [ {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, @@ -2974,7 +3048,7 @@ version = "2.1.6" description = "SDK to control an Octave with QUA" optional = false python-versions = "<3.13,>=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "qm_octave-2.1.6-py3-none-any.whl", hash = "sha256:2e3df8085330ecf0eb1cc3dd661ebec7b3505959d0fabb3a2dc6175ec7c7cba3"}, {file = "qm_octave-2.1.6.tar.gz", hash = "sha256:8a16e6a8c76b060bc2856020a3f54f2e6d936ffc24e2b7963376ce990a8110be"}, @@ -2992,7 +3066,7 @@ version = "1.2.3" description = "QUA language SDK to control a Quantum Computer" optional = false python-versions = "<3.13,>=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "qm_qua-1.2.3-py3-none-any.whl", hash = "sha256:d719aa0f6ca8dc02e3f9941103942d2f260390ba1527857e08e164cd5e1e1df1"}, {file = "qm_qua-1.2.3.tar.gz", hash = "sha256:529cbb443c04f895cd9b2b663ac4617cb221dbdd490220d1de955a2a9bf1c9c5"}, @@ -3024,7 +3098,7 @@ version = "0.20.0" description = "The qualang_tools package includes various tools related to QUA programs in Python" optional = false python-versions = "<3.13,>=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "qualang_tools-0.20.0-py3-none-any.whl", hash = "sha256:0dc71d2c00eaa98da86b70e082cf0c59d9d61b1494d74bc5aee6b07b8f1bff9c"}, {file = "qualang_tools-0.20.0.tar.gz", hash = "sha256:23de4e0dc81387e45803afdf58c51f1a9c6f8b6ea650becda7d3b31904b0d342"}, @@ -3045,48 +3119,125 @@ interplot = ["dill (>=0.3.4,<0.4.0)", "ipython (>=8.10.0,<9.0.0)", "pypiwin32 (> two-qubit-rb = ["cirq (==1.2.0) ; python_version >= \"3.10\"", "tqdm (>=4.67.1,<5.0.0)", "xarray (>=2023.0.0,<2024.0.0)"] [[package]] -name = "qualibrate-config" -version = "0.1.8" -description = "Configuration for Qualibrate" +name = "qualibrate" +version = "0.3.1" +description = "Qubit calibration software" optional = false -python-versions = "<4.0,>=3.10" -groups = ["main", "test"] +python-versions = "<3.13,>=3.10" +groups = ["test"] files = [ - {file = "qualibrate_config-0.1.8-py3-none-any.whl", hash = "sha256:58969295ada9253540e18208387acad9097a4883051dd8ed72887e89b4f6d37f"}, - {file = "qualibrate_config-0.1.8.tar.gz", hash = "sha256:7deccb0d160a0f1863ca43c87c5712d7a7e40a4c89fa03a8ed06d2ff552a1176"}, + {file = "qualibrate-0.3.1-py3-none-any.whl", hash = "sha256:aa2adde1a62beb27d99681096a46cb71c5a45abf90e6928f1c307490f2801a67"}, + {file = "qualibrate-0.3.1.tar.gz", hash = "sha256:d95fb8557bb5c410cb8431e944c91c9abe763960f6b6ac6cf6041d0eb8668f65"}, ] [package.dependencies] +a2wsgi = ">=1.10.8,<2.0.0" click = ">=8.1.7,<8.2.0" -jsonpatch = ">=1.33,<2.0" -pydantic = ">=2.9.2,<3.0.0" -pydantic-settings = ">=2.6.1,<3.0.0" +fastapi = ">=0.117.1,<0.118.0" +pydantic = ">=2.11.9,<3.0.0" +pydantic-settings = ">=2.11.0,<3.0.0" +qualibrate-app = "0.4.1" +qualibrate-config = "0.1.8" +qualibrate-core = "0.4.1" +qualibrate-runner = "0.4.0" tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""} tomli-w = ">=1.0.0,<2.0.0" +uvicorn = ">=0.37.0,<0.38.0" [[package]] -name = "qualibrate-core" +name = "qualibrate-app" version = "0.4.1" description = "" optional = false python-versions = "<3.13,>=3.10" -groups = ["main"] +groups = ["test"] files = [ - {file = "qualibrate_core-0.4.1-py3-none-any.whl", hash = "sha256:c1124aee019e5774079705d5bd04b91e8e2e4911de26a9e16830256a1c13fd83"}, - {file = "qualibrate_core-0.4.1.tar.gz", hash = "sha256:969047b8ca0156d2f27f4a838cf1198b32732c51610abf39d426a8c8c2e5949f"}, + {file = "qualibrate_app-0.4.1-py3-none-any.whl", hash = "sha256:7157d3996ddfa38fad551666f14d368beac6b544c76fbc3cf5c2d1f84e26d49f"}, + {file = "qualibrate_app-0.4.1.tar.gz", hash = "sha256:f948bfd764042d158ad59e59889a8560c9674813121800f4594e4d466cbeb829"}, ] [package.dependencies] -datamodel-code-generator = ">=0.33.0,<0.34.0" +click = ">=8.1.7,<8.2.0" +fastapi = ">=0.117.1,<0.118.0" jsonpatch = ">=1.33,<2.0" -jsonpointer = ">=3.0.0,<4.0.0" -matplotlib = ">=3.10.6,<4.0.0" -networkx = ">=3.4.2,<3.5.0" -pydantic = ">=2.9.2,<3.0.0" -python-json-logger = ">=3.2.1,<4.0.0" -qualang-tools = {version = ">=0.20.0", markers = "python_version >= \"3.10\" and python_version < \"3.13\""} qualibrate-config = ">=0.1.7,<0.2.0" -types-networkx = ">=3.4.2.20250527,<3.5.0.0" +requests = ">=2.32.4,<3.0.0" +tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""} +tomli-w = ">=1.0.0,<2.0.0" +uvicorn = ">=0.37.0,<0.38.0" + +[[package]] +name = "qualibrate-config" +version = "0.1.8" +description = "Configuration for Qualibrate" +optional = false +python-versions = "^3.10" +groups = ["main", "qm-dev", "test"] +files = [] +develop = true + +[package.dependencies] +click = "^8.1.7,<8.2.0" +jsonpatch = "^1.33" +pydantic = "^2.9.2" +pydantic-settings = "^2.6.1" +tomli = {version = "^2.0.1", markers = "python_version < \"3.11\""} +tomli-w = "^1.0.0" + +[package.source] +type = "directory" +url = "../qualibrate-config" + +[[package]] +name = "qualibrate-core" +version = "0.4.1" +description = "" +optional = false +python-versions = "^3.10,<3.13" +groups = ["main", "qm-dev", "test"] +files = [] +develop = true + +[package.dependencies] +datamodel-code-generator = "^0.33.0" +jsonpatch = "^1.33" +jsonpointer = "^3.0.0" +matplotlib = "^3.10.6" +networkx = "~3.4.2" +pydantic = "^2.9.2" +python-json-logger = "^3.2.1" +qualang-tools = {version = ">=0.20.0", markers = "python_version >= \"3.10\" and python_version < \"3.13\""} +qualibrate-config = "^0.1.7,<0.2.0" +types-networkx = "~3.4.2.20250527" + +[package.source] +type = "directory" +url = "../qualibrate-core" + +[[package]] +name = "qualibration-libs" +version = "0.2.1" +description = "Utility library supporting calibration nodes and graphs for the QUAlibration graphs platform." +optional = false +python-versions = ">=3.9, <3.13" +groups = ["test"] +files = [] +develop = true + +[package.dependencies] +colorama = ">=0.4.6" +lmfit = ">=1.3.3" +matplotlib = ">=3.10.0" +numpy = ">=1.20.0" +qm-qua = ">=1.2.2" +qualibrate = ">=0.2.1" +quam-builder = ">=0.1.2" +scipy = ">=1.13.1" +xarray = ">=2024.7.0" + +[package.source] +type = "directory" +url = "../qualibration-libs" [[package]] name = "quam" @@ -3140,7 +3291,7 @@ version = "3.14.1" description = "rapid fuzzy string matching" optional = false python-versions = ">=3.10" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "rapidfuzz-3.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:489440e4b5eea0d150a31076eb183bed0ec84f934df206c72ae4fc3424501758"}, {file = "rapidfuzz-3.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eff22cc938c3f74d194df03790a6c3325d213b28cf65cdefd6fdeae759b745d5"}, @@ -3245,7 +3396,7 @@ version = "2.32.5" description = "Python HTTP for Humans." optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, @@ -3267,7 +3418,7 @@ version = "1.0.0" description = "A utility belt for advanced users of python-requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, @@ -3311,7 +3462,7 @@ version = "1.7.2" description = "A set of python modules for machine learning and data mining" optional = false python-versions = ">=3.10" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "scikit_learn-1.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b33579c10a3081d076ab403df4a4190da4f4432d443521674637677dc91e61f"}, {file = "scikit_learn-1.7.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:36749fb62b3d961b1ce4fedf08fa57a1986cd409eff2d783bca5d4b9b5fce51c"}, @@ -3367,7 +3518,7 @@ version = "1.15.3" description = "Fundamental algorithms for scientific computing in Python" optional = false python-versions = ">=3.10" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c"}, {file = "scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253"}, @@ -3431,7 +3582,7 @@ version = "3.4.0" description = "Python bindings to FreeDesktop.org Secret Service API" optional = false python-versions = ">=3.10" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] markers = "sys_platform == \"linux\"" files = [ {file = "secretstorage-3.4.0-py3-none-any.whl", hash = "sha256:0e3b6265c2c63509fb7415717607e4b2c9ab767b7f344a57473b779ca13bd02e"}, @@ -3448,7 +3599,7 @@ version = "1.5.4" description = "Tool to Detect Surrounding Shell" optional = false python-versions = ">=3.7" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, @@ -3460,7 +3611,7 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -3472,7 +3623,7 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -3484,7 +3635,7 @@ version = "0.48.0" description = "The little ASGI library that shines." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "test"] files = [ {file = "starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659"}, {file = "starlette-0.48.0.tar.gz", hash = "sha256:7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46"}, @@ -3503,7 +3654,7 @@ version = "9.1.2" description = "Retry code until it succeeds" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138"}, {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"}, @@ -3519,7 +3670,7 @@ version = "3.6.0" description = "threadpoolctl" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb"}, {file = "threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e"}, @@ -3531,7 +3682,7 @@ version = "4.8.2" description = "TinyDB is a tiny, document oriented database optimized for your happiness :)" optional = false python-versions = "<4.0,>=3.8" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "tinydb-4.8.2-py3-none-any.whl", hash = "sha256:f97030ee5cbc91eeadd1d7af07ab0e48ceb04aa63d4a983adbaca4cba16e86c3"}, {file = "tinydb-4.8.2.tar.gz", hash = "sha256:f7dfc39b8d7fda7a1ca62a8dbb449ffd340a117c1206b68c50b1a481fb95181d"}, @@ -3543,7 +3694,7 @@ version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" -groups = ["main", "dev", "test"] +groups = ["main", "dev", "qm-dev", "test"] files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -3578,7 +3729,7 @@ files = [ {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] -markers = {main = "python_version < \"3.12\"", dev = "python_version == \"3.10\"", test = "python_version == \"3.10\""} +markers = {main = "python_version < \"3.12\"", dev = "python_version == \"3.10\"", qm-dev = "python_version < \"3.12\"", test = "python_version < \"3.12\""} [[package]] name = "tomli-w" @@ -3586,7 +3737,7 @@ version = "1.2.0" description = "A lil' TOML writer" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90"}, {file = "tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021"}, @@ -3598,7 +3749,7 @@ version = "0.13.3" description = "Style preserving TOML library" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0"}, {file = "tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1"}, @@ -3610,7 +3761,7 @@ version = "2025.9.11.17" description = "Canonical source for classifiers on PyPI (pypi.org)." optional = false python-versions = "*" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "trove_classifiers-2025.9.11.17-py3-none-any.whl", hash = "sha256:5d392f2d244deb1866556457d6f3516792124a23d1c3a463a2e8668a5d1c15dd"}, {file = "trove_classifiers-2025.9.11.17.tar.gz", hash = "sha256:931ca9841a5e9c9408bc2ae67b50d28acf85bef56219b56860876dd1f2d024dd"}, @@ -3622,7 +3773,7 @@ version = "4.4.4" description = "Run-time type checker for Python" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "typeguard-4.4.4-py3-none-any.whl", hash = "sha256:b5f562281b6bfa1f5492470464730ef001646128b180769880468bd84b68b09e"}, {file = "typeguard-4.4.4.tar.gz", hash = "sha256:3a7fd2dffb705d4d0efaed4306a704c89b9dee850b688f060a8b1615a79e5f74"}, @@ -3637,7 +3788,7 @@ version = "3.4.2.20250527" description = "Typing stubs for networkx" optional = false python-versions = ">=3.10" -groups = ["main"] +groups = ["main", "qm-dev", "test"] files = [ {file = "types_networkx-3.4.2.20250527-py3-none-any.whl", hash = "sha256:3716f750e61bf46ceba2a43474d7f9b61f306274a243cee6cc2aa9f4fb37ab41"}, {file = "types_networkx-3.4.2.20250527.tar.gz", hash = "sha256:484914f0dee6ce8d87bda494f641a9227e954d716d2f330bfcbf69c2431b646d"}, @@ -3652,7 +3803,7 @@ version = "4.15.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" -groups = ["main", "dev", "test"] +groups = ["main", "dev", "qm-dev", "test"] files = [ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, @@ -3664,7 +3815,7 @@ version = "0.4.1" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"}, {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"}, @@ -3685,13 +3836,31 @@ files = [ {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}, ] +[[package]] +name = "uncertainties" +version = "3.2.3" +description = "calculations with values with uncertainties, error propagation" +optional = false +python-versions = ">=3.8" +groups = ["test"] +files = [ + {file = "uncertainties-3.2.3-py3-none-any.whl", hash = "sha256:313353900d8f88b283c9bad81e7d2b2d3d4bcc330cbace35403faaed7e78890a"}, + {file = "uncertainties-3.2.3.tar.gz", hash = "sha256:76a5653e686f617a42922d546a239e9efce72e6b35411b7750a1d12dcba03031"}, +] + +[package.extras] +all = ["uncertainties[arrays,doc,test]"] +arrays = ["numpy"] +doc = ["python-docs-theme", "sphinx", "sphinx-copybutton"] +test = ["pytest", "pytest_codspeed", "pytest_cov", "scipy"] + [[package]] name = "urllib3" version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, @@ -3709,7 +3878,7 @@ version = "0.37.0" description = "The lightning-fast ASGI server." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "test"] files = [ {file = "uvicorn-0.37.0-py3-none-any.whl", hash = "sha256:913b2b88672343739927ce381ff9e2ad62541f9f8289664fa1d1d3803fa2ce6c"}, {file = "uvicorn-0.37.0.tar.gz", hash = "sha256:4115c8add6d3fd536c8ee77f0e14a7fd2ebba939fed9b02583a97f80648f9e13"}, @@ -3729,7 +3898,7 @@ version = "20.34.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "virtualenv-20.34.0-py3-none-any.whl", hash = "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026"}, {file = "virtualenv-20.34.0.tar.gz", hash = "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a"}, @@ -3856,7 +4025,7 @@ version = "1.2.0" description = "Python wrapper for extended filesystem attributes" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] markers = "sys_platform == \"darwin\"" files = [ {file = "xattr-1.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3df4d8d91e2996c3c72a390ec82e8544acdcb6c7df67b954f1736ff37ea4293e"}, @@ -3943,7 +4112,7 @@ version = "3.23.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] markers = "python_version < \"3.12\"" files = [ {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, @@ -3964,7 +4133,7 @@ version = "0.25.0" description = "Zstandard bindings for Python" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "qm-dev", "test"] files = [ {file = "zstandard-0.25.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e59fdc271772f6686e01e1b3b74537259800f57e24280be3f29c8a0deb1904dd"}, {file = "zstandard-0.25.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4d441506e9b372386a5271c64125f72d5df6d2a8e8a2a45a0ae09b03cb781ef7"}, @@ -4073,4 +4242,4 @@ cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and pyt [metadata] lock-version = "2.1" python-versions = "^3.10,<3.13" -content-hash = "ac8ecc77313a00c733a03b8e1f0d98302b24daecf699a2f87141394b5ce3b738" +content-hash = "4657da2105785294b056c411f4051f2519da097de910c60faa3289f85236df02" From e9ab381796e68c94504696b103e9a6e1abd6593a Mon Sep 17 00:00:00 2001 From: Jonatan Kutchinsky Date: Wed, 26 Nov 2025 20:55:41 +0100 Subject: [PATCH 16/26] Fixing github actions --- .github/workflows/check.yaml | 6 +- poetry.lock | 453 +++++++++++------------------------ pyproject.toml | 8 +- 3 files changed, 149 insertions(+), 318 deletions(-) diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index 9078cb5..e22b73e 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -18,11 +18,11 @@ jobs: - uses: actions/setup-python@v5 with: python-version: "3.10" + cache: "poetry" + cache-dependency-path: poetry.lock - name: Install python deps - run: | - rm poetry.lock - poetry install --with dev --no-root + run: poetry install --with dev - name: Check python package typing run: poetry run poe type diff --git a/poetry.lock b/poetry.lock index 5f4a6bc..23acc32 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,27 +1,12 @@ # This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. -[[package]] -name = "a2wsgi" -version = "1.10.10" -description = "Convert WSGI app to ASGI app or ASGI app to WSGI app." -optional = false -python-versions = ">=3.8.0" -groups = ["test"] -files = [ - {file = "a2wsgi-1.10.10-py3-none-any.whl", hash = "sha256:d2b21379479718539dc15fce53b876251a0efe7615352dfe49f6ad1bc507848d"}, - {file = "a2wsgi-1.10.10.tar.gz", hash = "sha256:a5bcffb52081ba39df0d5e9a884fc6f819d92e3a42389343ba77cbf809fe1f45"}, -] - -[package.dependencies] -typing_extensions = {version = "*", markers = "python_version < \"3.11\""} - [[package]] name = "annotated-types" version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] 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"}, @@ -33,7 +18,7 @@ version = "4.11.0" description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc"}, {file = "anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4"}, @@ -54,7 +39,7 @@ version = "3.6.2" description = "Bash tab completion for argparse" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main"] files = [ {file = "argcomplete-3.6.2-py3-none-any.whl", hash = "sha256:65b3133a29ad53fb42c48cf5114752c7ab66c1c38544fdf6460f450c09b42591"}, {file = "argcomplete-3.6.2.tar.gz", hash = "sha256:d0519b1bc867f5f4f4713c41ad0aba73a4a5f007449716b16f385f2166dc6adf"}, @@ -63,31 +48,13 @@ files = [ [package.extras] test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] -[[package]] -name = "asteval" -version = "1.0.7" -description = "Safe, minimalistic evaluator of python expression using ast module" -optional = false -python-versions = ">=3.10" -groups = ["test"] -files = [ - {file = "asteval-1.0.7-py3-none-any.whl", hash = "sha256:d78df08681dfff59031ca624ba7030f9dc576a7a16e2f7a5137c6e7ef3ee60c4"}, - {file = "asteval-1.0.7.tar.gz", hash = "sha256:729fa010511957afcbdbadb0304da09cef6904c145b6901e917afac2dd5668a1"}, -] - -[package.extras] -all = ["asteval[dev,doc,test]"] -dev = ["build", "twine"] -doc = ["Sphinx"] -test = ["coverage", "pytest", "pytest-cov"] - [[package]] name = "backports-tarfile" version = "1.2.0" description = "Backport of CPython tarfile module" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] markers = "python_version < \"3.12\"" files = [ {file = "backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34"}, @@ -104,7 +71,7 @@ version = "2.0.0b7" description = "A better Protobuf / gRPC generator & library" optional = false python-versions = "<4.0,>=3.7" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "betterproto-2.0.0b7-py3-none-any.whl", hash = "sha256:401ab8055e2f814e77b9c88a74d0e1ae3d1e8a969cced6aeb1b59f71ad63fbd2"}, {file = "betterproto-2.0.0b7.tar.gz", hash = "sha256:1b1458ca5278d519bcd62556a4c236f998a91d503f0f71c67b0b954747052af2"}, @@ -125,7 +92,7 @@ version = "25.9.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main"] files = [ {file = "black-25.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce41ed2614b706fd55fd0b4a6909d06b5bab344ffbfadc6ef34ae50adba3d4f7"}, {file = "black-25.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ab0ce111ef026790e9b13bd216fa7bc48edd934ffc4cbf78808b235793cbc92"}, @@ -173,7 +140,7 @@ version = "1.3.0" description = "A simple, correct Python build frontend" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "build-1.3.0-py3-none-any.whl", hash = "sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4"}, {file = "build-1.3.0.tar.gz", hash = "sha256:698edd0ea270bde950f53aed21f3a0135672206f3911e0176261a31e0e07b397"}, @@ -196,7 +163,7 @@ version = "0.14.3" description = "httplib2 caching for requests" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "cachecontrol-0.14.3-py3-none-any.whl", hash = "sha256:b35e44a3113f17d2a31c1e6b27b9de6d4405f84ae51baa8c1d3cc5b633010cae"}, {file = "cachecontrol-0.14.3.tar.gz", hash = "sha256:73e7efec4b06b20d9267b441c1f733664f989fb8688391b670ca812d70795d11"}, @@ -218,7 +185,7 @@ version = "2025.8.3" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, @@ -230,7 +197,8 @@ version = "2.0.0" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] +markers = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and (sys_platform == \"linux\" or sys_platform == \"darwin\")" files = [ {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, @@ -317,7 +285,6 @@ files = [ {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"}, {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"}, ] -markers = {main = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and (sys_platform == \"linux\" or sys_platform == \"darwin\")", qm-dev = "(sys_platform == \"linux\" or sys_platform == \"darwin\") and (platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\")", test = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and (sys_platform == \"linux\" or sys_platform == \"darwin\")"} [package.dependencies] pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} @@ -328,7 +295,7 @@ version = "3.4.3" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, @@ -417,7 +384,7 @@ version = "2.1.0" description = "Cleo allows you to create beautiful and testable command-line interfaces." optional = false python-versions = ">=3.7,<4.0" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "cleo-2.1.0-py3-none-any.whl", hash = "sha256:4a31bd4dd45695a64ee3c4758f583f134267c2bc518d8ae9a29cf237d009b07e"}, {file = "cleo-2.1.0.tar.gz", hash = "sha256:0b2c880b5d13660a7ea651001fb4acb527696c01f15c9ee650f377aa543fd523"}, @@ -433,7 +400,7 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -448,12 +415,12 @@ 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" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {main = "platform_system == \"Windows\" or os_name == \"nt\"", qm-dev = "os_name == \"nt\" or platform_system == \"Windows\""} +markers = {main = "platform_system == \"Windows\" or os_name == \"nt\"", test = "platform_system == \"Windows\" or os_name == \"nt\" or sys_platform == \"win32\""} [[package]] name = "contourpy" @@ -461,7 +428,7 @@ version = "1.3.2" description = "Python library for calculating contours of 2D quadrilateral grids" optional = false python-versions = ">=3.10" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934"}, {file = "contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989"}, @@ -538,7 +505,7 @@ version = "0.4.1" description = "Manage Python errors with ease" optional = false python-versions = ">=3.7,<4.0" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "crashtest-0.4.1-py3-none-any.whl", hash = "sha256:8d23eac5fa660409f57472e3851dab7ac18aba459a8d19cbbba86d3d5aecd2a5"}, {file = "crashtest-0.4.1.tar.gz", hash = "sha256:80d7b1f316ebfbd429f648076d6275c877ba30ba48979de4191714a75266f0ce"}, @@ -550,7 +517,7 @@ version = "46.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = "!=3.9.0,!=3.9.1,>=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] markers = "sys_platform == \"linux\"" files = [ {file = "cryptography-46.0.1-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:1cd6d50c1a8b79af1a6f703709d8973845f677c8e97b1268f5ff323d38ce8475"}, @@ -629,7 +596,7 @@ version = "0.12.1" description = "Composable style cycles" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, @@ -645,7 +612,7 @@ version = "2.43.0" description = "Collection of all Datadog Public endpoints" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "datadog_api_client-2.43.0-py3-none-any.whl", hash = "sha256:1d038b545263ad0b665f46d9d36dc0f0ed491770d9dcd68380be4685a9ae6cc3"}, {file = "datadog_api_client-2.43.0.tar.gz", hash = "sha256:f3aec5db9bcceb39f6315e505eea908da7133a19f8475ef996ef8be8e3c84855"}, @@ -669,7 +636,7 @@ version = "0.33.0" description = "Datamodel Code Generator" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main"] files = [ {file = "datamodel_code_generator-0.33.0-py3-none-any.whl", hash = "sha256:e229264aa612b2d5bb4901bcd6c520a799ae0d5c19262577a0f876eb48afaaa3"}, {file = "datamodel_code_generator-0.33.0.tar.gz", hash = "sha256:7635ef788201d69bd3e98ba88ce6afe479400dc2737fe9d5e21f87408f352c08"}, @@ -701,7 +668,7 @@ version = "4.48.2" description = "Dependency injection framework for Python" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "dependency_injector-4.48.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:54d0178be10f17b768afb5c0ed1c5c565abaa2d097b2bc5a529a31c580613df2"}, {file = "dependency_injector-4.48.2-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:12a15979fd534b728b3061c8aa52fd55adb77574758817daae9df8a1c2eb830b"}, @@ -745,29 +712,13 @@ pydantic = ["pydantic"] pydantic2 = ["pydantic-settings"] yaml = ["pyyaml"] -[[package]] -name = "dill" -version = "0.4.0" -description = "serialize all of Python" -optional = false -python-versions = ">=3.8" -groups = ["test"] -files = [ - {file = "dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049"}, - {file = "dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0"}, -] - -[package.extras] -graph = ["objgraph (>=1.7.2)"] -profile = ["gprof2dot (>=2022.7.29)"] - [[package]] name = "distlib" version = "0.4.0" description = "Distribution utilities" optional = false python-versions = "*" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, @@ -779,7 +730,7 @@ version = "0.22.8" description = "Python Git Library" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "dulwich-0.22.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:546176d18b8cc0a492b0f23f07411e38686024cffa7e9d097ae20512a2e57127"}, {file = "dulwich-0.22.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d2434dd72b2ae09b653c9cfe6764a03c25cfbd99fbbb7c426f0478f6fb1100f"}, @@ -846,7 +797,7 @@ version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] markers = "python_version == \"3.10\"" files = [ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, @@ -865,7 +816,7 @@ version = "0.117.1" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main"] files = [ {file = "fastapi-0.117.1-py3-none-any.whl", hash = "sha256:33c51a0d21cab2b9722d4e56dbb9316f3687155be6b276191790d8da03507552"}, {file = "fastapi-0.117.1.tar.gz", hash = "sha256:fb2d42082d22b185f904ca0ecad2e195b851030bd6c5e4c032d1c981240c631a"}, @@ -887,7 +838,7 @@ version = "2.21.2" description = "Fastest Python implementation of JSON schema" optional = false python-versions = "*" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463"}, {file = "fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de"}, @@ -902,7 +853,7 @@ version = "3.19.1" description = "A platform independent file lock." optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d"}, {file = "filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58"}, @@ -914,7 +865,7 @@ version = "0.6.3" description = "A utility to find python versions on your system" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "findpython-0.6.3-py3-none-any.whl", hash = "sha256:a85bb589b559cdf1b87227cc233736eb7cad894b9e68021ee498850611939ebc"}, {file = "findpython-0.6.3.tar.gz", hash = "sha256:5863ea55556d8aadc693481a14ac4f3624952719efc1c5591abb0b4a9e965c94"}, @@ -929,7 +880,7 @@ version = "4.60.1" description = "Tools to manipulate font files" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "fonttools-4.60.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9a52f254ce051e196b8fe2af4634c2d2f02c981756c6464dc192f1b6050b4e28"}, {file = "fonttools-4.60.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c7420a2696a44650120cdd269a5d2e56a477e2bfa9d95e86229059beb1c19e15"}, @@ -1025,7 +976,7 @@ version = "1.3.0" description = "GenSON is a powerful, user-friendly JSON Schema generator." optional = false python-versions = "*" -groups = ["main", "qm-dev", "test"] +groups = ["main"] files = [ {file = "genson-1.3.0-py3-none-any.whl", hash = "sha256:468feccd00274cc7e4c09e84b08704270ba8d95232aa280f65b986139cec67f7"}, {file = "genson-1.3.0.tar.gz", hash = "sha256:e02db9ac2e3fd29e65b5286f7135762e2cd8a986537c075b06fc5f1517308e37"}, @@ -1037,7 +988,7 @@ version = "1.75.1" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "grpcio-1.75.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:1712b5890b22547dd29f3215c5788d8fc759ce6dd0b85a6ba6e2731f2d04c088"}, {file = "grpcio-1.75.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8d04e101bba4b55cea9954e4aa71c24153ba6182481b487ff376da28d4ba46cf"}, @@ -1114,7 +1065,7 @@ version = "0.4.5" description = "Pure-Python gRPC implementation for asyncio" optional = false python-versions = ">=3.7" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "grpclib-0.4.5.tar.gz", hash = "sha256:bf83ed55aca59497e168761d9555056efc54a8f865316c3b39becd007e9f9a73"}, ] @@ -1132,7 +1083,7 @@ version = "0.16.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, @@ -1144,7 +1095,7 @@ version = "4.3.0" description = "Pure-Python HTTP/2 protocol implementation" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd"}, {file = "h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1"}, @@ -1160,7 +1111,7 @@ version = "4.1.0" description = "Pure-Python HPACK header encoding" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496"}, {file = "hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca"}, @@ -1172,7 +1123,7 @@ version = "1.0.9" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, @@ -1194,7 +1145,7 @@ version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -1220,7 +1171,7 @@ version = "6.1.0" description = "Pure-Python HTTP/2 framing" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5"}, {file = "hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08"}, @@ -1232,7 +1183,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -1247,7 +1198,7 @@ version = "8.7.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] markers = "python_version < \"3.12\"" files = [ {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, @@ -1272,7 +1223,7 @@ version = "7.5.0" description = "Correctly generate plurals, singular nouns, ordinals, indefinite articles" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main"] files = [ {file = "inflect-7.5.0-py3-none-any.whl", hash = "sha256:2aea70e5e70c35d8350b8097396ec155ffd68def678c7ff97f51aa69c1d92344"}, {file = "inflect-7.5.0.tar.gz", hash = "sha256:faf19801c3742ed5a05a8ce388e0d8fe1a07f8d095c82201eb904f5d27ad571f"}, @@ -1308,7 +1259,7 @@ version = "0.7.0" description = "A library for installing Python wheels." optional = false python-versions = ">=3.7" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "installer-0.7.0-py3-none-any.whl", hash = "sha256:05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53"}, {file = "installer-0.7.0.tar.gz", hash = "sha256:a26d3e3116289bb08216e0d0f7d925fcef0b0194eedfa0c944bcaaa106c4b631"}, @@ -1320,7 +1271,7 @@ version = "6.0.1" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.9.0" -groups = ["main", "qm-dev", "test"] +groups = ["main"] files = [ {file = "isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615"}, {file = "isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450"}, @@ -1336,7 +1287,7 @@ version = "3.4.0" description = "Utility functions for Python class constructs" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790"}, {file = "jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd"}, @@ -1355,7 +1306,7 @@ version = "6.0.1" description = "Useful decorators and context managers" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "jaraco.context-6.0.1-py3-none-any.whl", hash = "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4"}, {file = "jaraco_context-6.0.1.tar.gz", hash = "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3"}, @@ -1374,7 +1325,7 @@ version = "4.3.0" description = "Functools like those found in stdlib" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "jaraco_functools-4.3.0-py3-none-any.whl", hash = "sha256:227ff8ed6f7b8f62c56deff101545fa7543cf2c8e7b82a7c2116e672f29c26e8"}, {file = "jaraco_functools-4.3.0.tar.gz", hash = "sha256:cfd13ad0dd2c47a3600b439ef72d8615d482cedcff1632930d6f28924d92f294"}, @@ -1397,7 +1348,7 @@ version = "0.9.0" description = "Low-level, pure Python DBus protocol wrapper." optional = false python-versions = ">=3.7" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] markers = "sys_platform == \"linux\"" files = [ {file = "jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683"}, @@ -1414,7 +1365,7 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" -groups = ["main", "qm-dev", "test"] +groups = ["main"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -1432,7 +1383,7 @@ version = "1.5.2" description = "Lightweight pipelining with Python functions" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241"}, {file = "joblib-1.5.2.tar.gz", hash = "sha256:3faa5c39054b2f03ca547da9b2f52fde67c06240c31853f306aea97f13647b55"}, @@ -1444,7 +1395,7 @@ version = "1.33" description = "Apply JSON-Patches (RFC 6902)" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, @@ -1459,7 +1410,7 @@ version = "3.0.0" description = "Identify specific nodes in a JSON document (RFC 6901)" optional = false python-versions = ">=3.7" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, @@ -1471,7 +1422,7 @@ version = "25.6.0" description = "Store and access your passwords safely." optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd"}, {file = "keyring-25.6.0.tar.gz", hash = "sha256:0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66"}, @@ -1501,7 +1452,7 @@ version = "1.4.9" description = "A fast implementation of the Cassowary constraint solver" optional = false python-versions = ">=3.10" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b4b4d74bda2b8ebf4da5bd42af11d02d04428b2c32846e4c2c93219df8a7987b"}, {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fb3b8132019ea572f4611d770991000d7f58127560c4889729248eb5852a102f"}, @@ -1606,38 +1557,13 @@ files = [ {file = "kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d"}, ] -[[package]] -name = "lmfit" -version = "1.3.4" -description = "Least-Squares Minimization with Bounds and Constraints" -optional = false -python-versions = ">=3.9" -groups = ["test"] -files = [ - {file = "lmfit-1.3.4-py3-none-any.whl", hash = "sha256:afce1593b42324d37ae2908249b0c55445e2f4c1a0474ff706a8e2f7b5d949fa"}, - {file = "lmfit-1.3.4.tar.gz", hash = "sha256:3c22c28c43f717f6c5b4a3bd81e893a2149739c26a592c046f2e33c23cfbe497"}, -] - -[package.dependencies] -asteval = ">=1.0" -dill = ">=0.3.4" -numpy = ">=1.24" -scipy = ">=1.10.0" -uncertainties = ">=3.2.2" - -[package.extras] -all = ["lmfit[dev,doc,test]"] -dev = ["build", "check-wheel-contents", "flake8-pyproject", "pre-commit", "twine"] -doc = ["Pillow", "Sphinx", "cairosvg", "corner", "emcee (>=3.0.0)", "ipykernel", "jupyter_sphinx (>=0.2.4)", "matplotlib", "numdifftools", "pandas", "pycairo ; platform_system == \"Windows\"", "sphinx-gallery (>=0.10)", "sphinxcontrib-svg2pdfconverter", "sympy"] -test = ["coverage", "flaky", "pytest", "pytest-cov"] - [[package]] name = "markupsafe" version = "3.0.3" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main"] files = [ {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, @@ -1736,7 +1662,7 @@ version = "3.23.3" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "marshmallow-3.23.3-py3-none-any.whl", hash = "sha256:20c0f8c613f68bcb45b2a0d3282e2f172575560170bf220d67aafb42717910e4"}, {file = "marshmallow-3.23.3.tar.gz", hash = "sha256:d586c8685ebdb80bf754e1f96e3f305aaf30951f1fc69175b977453633467e76"}, @@ -1756,7 +1682,7 @@ version = "5.11" description = "An unofficial extension to Marshmallow to allow for polymorphic fields" optional = false python-versions = ">=3.5" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "marshmallow-polyfield-5.11.tar.gz", hash = "sha256:8075a9cc490da4af58b902b4a40a99882dd031adb7aaa96abd147a4fcd53415f"}, ] @@ -1770,7 +1696,7 @@ version = "3.10.6" description = "Python plotting package" optional = false python-versions = ">=3.10" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "matplotlib-3.10.6-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:bc7316c306d97463a9866b89d5cc217824e799fa0de346c8f68f4f3d27c8693d"}, {file = "matplotlib-3.10.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d00932b0d160ef03f59f9c0e16d1e3ac89646f7785165ce6ad40c842db16cc2e"}, @@ -1849,7 +1775,7 @@ version = "10.8.0" description = "More routines for operating on iterables, beyond itertools" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b"}, {file = "more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd"}, @@ -1861,7 +1787,7 @@ version = "1.1.1" description = "MessagePack serializer" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "msgpack-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:353b6fc0c36fde68b661a12949d7d49f8f51ff5fa019c1e47c87c4ff34b080ed"}, {file = "msgpack-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:79c408fcf76a958491b4e3b103d1c417044544b68e96d06432a189b43d1215c8"}, @@ -1930,7 +1856,7 @@ version = "6.6.4" description = "multidict implementation" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "multidict-6.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b8aa6f0bd8125ddd04a6593437bad6a7e70f300ff4180a531654aa2ab3f6d58f"}, {file = "multidict-6.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9e5853bbd7264baca42ffc53391b490d65fe62849bf2c690fa3f6273dbcd0cb"}, @@ -2114,7 +2040,7 @@ version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.8" -groups = ["main", "dev", "qm-dev", "test"] +groups = ["main", "dev"] files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, @@ -2126,7 +2052,7 @@ version = "3.4.2" description = "Python package for creating and manipulating graphs and networks" optional = false python-versions = ">=3.10" -groups = ["main", "qm-dev", "test"] +groups = ["main"] files = [ {file = "networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f"}, {file = "networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1"}, @@ -2146,7 +2072,7 @@ version = "1.26.4" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, @@ -2192,7 +2118,7 @@ version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, @@ -2316,7 +2242,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" -groups = ["main", "dev", "qm-dev", "test"] +groups = ["main", "dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -2328,7 +2254,7 @@ version = "2025.9.18" description = "Installer for Python Build Standalone" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "pbs_installer-2025.9.18-py3-none-any.whl", hash = "sha256:8ef55d7675698747505c237015d14c81759bd66a0d4c8b20cec9a2dc96e8434c"}, {file = "pbs_installer-2025.9.18.tar.gz", hash = "sha256:c0a51a7c1e015723bd8396f02e15b5876e439f74b0f45bbac436b189f903219f"}, @@ -2349,7 +2275,7 @@ version = "11.3.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860"}, {file = "pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"}, @@ -2474,7 +2400,7 @@ version = "1.12.1.2" description = "Query metadata from sdists / bdists / installed packages." optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "pkginfo-1.12.1.2-py3-none-any.whl", hash = "sha256:c783ac885519cab2c34927ccfa6bf64b5a704d7c69afaea583dd9b7afe969343"}, {file = "pkginfo-1.12.1.2.tar.gz", hash = "sha256:5cd957824ac36f140260964eba3c6be6442a8359b8c48f4adf90210f33a04b7b"}, @@ -2489,7 +2415,7 @@ version = "4.4.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85"}, {file = "platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"}, @@ -2506,7 +2432,7 @@ version = "5.24.1" description = "An open-source, interactive data visualization library for Python" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "plotly-5.24.1-py3-none-any.whl", hash = "sha256:f67073a1e637eb0dc3e46324d9d51e2fe76e9727c892dde64ddf1e1b51f29089"}, {file = "plotly-5.24.1.tar.gz", hash = "sha256:dbc8ac8339d248a4bcc36e08a5659bacfe1b079390b8953533f4eb22169b4bae"}, @@ -2558,7 +2484,7 @@ version = "2.1.3" description = "Python dependency management and packaging made easy." optional = false python-versions = "<4.0,>=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "poetry-2.1.3-py3-none-any.whl", hash = "sha256:7054d3f97ccce7f31961ead16250407c4577bfe57e2037a190ae2913fc40a20c"}, {file = "poetry-2.1.3.tar.gz", hash = "sha256:f2c9bd6790b19475976d88ea4553bcc3533c0dc73f740edc4fffe9e2add50594"}, @@ -2594,7 +2520,7 @@ version = "2.1.3" description = "Poetry PEP 517 Build Backend" optional = false python-versions = "<4.0,>=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "poetry_core-2.1.3-py3-none-any.whl", hash = "sha256:2c704f05016698a54ca1d327f46ce2426d72eaca6ff614132c8477c292266771"}, {file = "poetry_core-2.1.3.tar.gz", hash = "sha256:0522a015477ed622c89aad56a477a57813cace0c8e7ff2a2906b7ef4a2e296a4"}, @@ -2606,7 +2532,7 @@ version = "6.32.1" description = "" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "protobuf-6.32.1-cp310-abi3-win32.whl", hash = "sha256:a8a32a84bc9f2aad712041b8b366190f71dde248926da517bde9e832e4412085"}, {file = "protobuf-6.32.1-cp310-abi3-win_amd64.whl", hash = "sha256:b00a7d8c25fa471f16bc8153d0e53d6c9e827f0953f3c09aaa4331c718cae5e1"}, @@ -2625,12 +2551,12 @@ version = "2.23" description = "C parser in Python" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] +markers = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and implementation_name != \"PyPy\" and (sys_platform == \"linux\" or sys_platform == \"darwin\")" files = [ {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"}, {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}, ] -markers = {main = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and implementation_name != \"PyPy\" and (sys_platform == \"linux\" or sys_platform == \"darwin\")", qm-dev = "(sys_platform == \"linux\" or sys_platform == \"darwin\") and (platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and implementation_name != \"PyPy\"", test = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and implementation_name != \"PyPy\" and (sys_platform == \"linux\" or sys_platform == \"darwin\")"} [[package]] name = "pydantic" @@ -2638,7 +2564,7 @@ version = "2.11.9" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "pydantic-2.11.9-py3-none-any.whl", hash = "sha256:c42dd626f5cfc1c6950ce6205ea58c93efa406da65f479dcb4029d5934857da2"}, {file = "pydantic-2.11.9.tar.gz", hash = "sha256:6b8ffda597a14812a7975c90b82a8a2e777d9257aba3453f973acd3c032a18e2"}, @@ -2660,7 +2586,7 @@ version = "2.33.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, @@ -2772,7 +2698,7 @@ version = "2.11.0" description = "Settings management using Pydantic" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c"}, {file = "pydantic_settings-2.11.0.tar.gz", hash = "sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180"}, @@ -2811,7 +2737,7 @@ version = "3.2.5" description = "pyparsing - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e"}, {file = "pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6"}, @@ -2826,7 +2752,7 @@ version = "1.2.0" description = "Wrappers to call pyproject.toml-based build backend hooks." optional = false python-versions = ">=3.7" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913"}, {file = "pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8"}, @@ -2880,7 +2806,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -2895,7 +2821,7 @@ version = "1.1.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc"}, {file = "python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab"}, @@ -2910,7 +2836,7 @@ version = "3.3.0" description = "JSON Log Formatter for the Python Logging Package" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main"] files = [ {file = "python_json_logger-3.3.0-py3-none-any.whl", hash = "sha256:dd980fae8cffb24c13caf6e158d3d61c0d6d22342f932cb6e9deedab3d35eec7"}, {file = "python_json_logger-3.3.0.tar.gz", hash = "sha256:12b7e74b17775e7d565129296105bbe3910842d9d0eb083fc83a6a617aa8df84"}, @@ -2925,7 +2851,7 @@ version = "0.1.10" description = "A Fast, spec compliant Python 3.12+ tokenizer that runs on older Pythons." optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main"] files = [ {file = "pytokens-0.1.10-py3-none-any.whl", hash = "sha256:db7b72284e480e69fb085d9f251f66b3d2df8b7166059261258ff35f50fb711b"}, {file = "pytokens-0.1.10.tar.gz", hash = "sha256:c9a4bfa0be1d26aebce03e6884ba454e842f186a59ea43a6d3b25af58223c044"}, @@ -2952,7 +2878,7 @@ version = "0.2.3" description = "A (partial) reimplementation of pywin32 using ctypes/cffi" optional = false python-versions = ">=3.6" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] markers = "sys_platform == \"win32\"" files = [ {file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"}, @@ -2965,7 +2891,7 @@ version = "6.0.3" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" -groups = ["main", "dev", "qm-dev", "test"] +groups = ["main", "dev"] files = [ {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, @@ -3048,7 +2974,7 @@ version = "2.1.6" description = "SDK to control an Octave with QUA" optional = false python-versions = "<3.13,>=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "qm_octave-2.1.6-py3-none-any.whl", hash = "sha256:2e3df8085330ecf0eb1cc3dd661ebec7b3505959d0fabb3a2dc6175ec7c7cba3"}, {file = "qm_octave-2.1.6.tar.gz", hash = "sha256:8a16e6a8c76b060bc2856020a3f54f2e6d936ffc24e2b7963376ce990a8110be"}, @@ -3066,7 +2992,7 @@ version = "1.2.3" description = "QUA language SDK to control a Quantum Computer" optional = false python-versions = "<3.13,>=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "qm_qua-1.2.3-py3-none-any.whl", hash = "sha256:d719aa0f6ca8dc02e3f9941103942d2f260390ba1527857e08e164cd5e1e1df1"}, {file = "qm_qua-1.2.3.tar.gz", hash = "sha256:529cbb443c04f895cd9b2b663ac4617cb221dbdd490220d1de955a2a9bf1c9c5"}, @@ -3098,7 +3024,7 @@ version = "0.20.0" description = "The qualang_tools package includes various tools related to QUA programs in Python" optional = false python-versions = "<3.13,>=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "qualang_tools-0.20.0-py3-none-any.whl", hash = "sha256:0dc71d2c00eaa98da86b70e082cf0c59d9d61b1494d74bc5aee6b07b8f1bff9c"}, {file = "qualang_tools-0.20.0.tar.gz", hash = "sha256:23de4e0dc81387e45803afdf58c51f1a9c6f8b6ea650becda7d3b31904b0d342"}, @@ -3119,125 +3045,48 @@ interplot = ["dill (>=0.3.4,<0.4.0)", "ipython (>=8.10.0,<9.0.0)", "pypiwin32 (> two-qubit-rb = ["cirq (==1.2.0) ; python_version >= \"3.10\"", "tqdm (>=4.67.1,<5.0.0)", "xarray (>=2023.0.0,<2024.0.0)"] [[package]] -name = "qualibrate" -version = "0.3.1" -description = "Qubit calibration software" +name = "qualibrate-config" +version = "0.1.8" +description = "Configuration for Qualibrate" optional = false -python-versions = "<3.13,>=3.10" -groups = ["test"] +python-versions = "<4.0,>=3.10" +groups = ["main", "test"] files = [ - {file = "qualibrate-0.3.1-py3-none-any.whl", hash = "sha256:aa2adde1a62beb27d99681096a46cb71c5a45abf90e6928f1c307490f2801a67"}, - {file = "qualibrate-0.3.1.tar.gz", hash = "sha256:d95fb8557bb5c410cb8431e944c91c9abe763960f6b6ac6cf6041d0eb8668f65"}, + {file = "qualibrate_config-0.1.8-py3-none-any.whl", hash = "sha256:58969295ada9253540e18208387acad9097a4883051dd8ed72887e89b4f6d37f"}, + {file = "qualibrate_config-0.1.8.tar.gz", hash = "sha256:7deccb0d160a0f1863ca43c87c5712d7a7e40a4c89fa03a8ed06d2ff552a1176"}, ] [package.dependencies] -a2wsgi = ">=1.10.8,<2.0.0" click = ">=8.1.7,<8.2.0" -fastapi = ">=0.117.1,<0.118.0" -pydantic = ">=2.11.9,<3.0.0" -pydantic-settings = ">=2.11.0,<3.0.0" -qualibrate-app = "0.4.1" -qualibrate-config = "0.1.8" -qualibrate-core = "0.4.1" -qualibrate-runner = "0.4.0" +jsonpatch = ">=1.33,<2.0" +pydantic = ">=2.9.2,<3.0.0" +pydantic-settings = ">=2.6.1,<3.0.0" tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""} tomli-w = ">=1.0.0,<2.0.0" -uvicorn = ">=0.37.0,<0.38.0" [[package]] -name = "qualibrate-app" +name = "qualibrate-core" version = "0.4.1" description = "" optional = false python-versions = "<3.13,>=3.10" -groups = ["test"] +groups = ["main"] files = [ - {file = "qualibrate_app-0.4.1-py3-none-any.whl", hash = "sha256:7157d3996ddfa38fad551666f14d368beac6b544c76fbc3cf5c2d1f84e26d49f"}, - {file = "qualibrate_app-0.4.1.tar.gz", hash = "sha256:f948bfd764042d158ad59e59889a8560c9674813121800f4594e4d466cbeb829"}, + {file = "qualibrate_core-0.4.1-py3-none-any.whl", hash = "sha256:c1124aee019e5774079705d5bd04b91e8e2e4911de26a9e16830256a1c13fd83"}, + {file = "qualibrate_core-0.4.1.tar.gz", hash = "sha256:969047b8ca0156d2f27f4a838cf1198b32732c51610abf39d426a8c8c2e5949f"}, ] [package.dependencies] -click = ">=8.1.7,<8.2.0" -fastapi = ">=0.117.1,<0.118.0" +datamodel-code-generator = ">=0.33.0,<0.34.0" jsonpatch = ">=1.33,<2.0" -qualibrate-config = ">=0.1.7,<0.2.0" -requests = ">=2.32.4,<3.0.0" -tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""} -tomli-w = ">=1.0.0,<2.0.0" -uvicorn = ">=0.37.0,<0.38.0" - -[[package]] -name = "qualibrate-config" -version = "0.1.8" -description = "Configuration for Qualibrate" -optional = false -python-versions = "^3.10" -groups = ["main", "qm-dev", "test"] -files = [] -develop = true - -[package.dependencies] -click = "^8.1.7,<8.2.0" -jsonpatch = "^1.33" -pydantic = "^2.9.2" -pydantic-settings = "^2.6.1" -tomli = {version = "^2.0.1", markers = "python_version < \"3.11\""} -tomli-w = "^1.0.0" - -[package.source] -type = "directory" -url = "../qualibrate-config" - -[[package]] -name = "qualibrate-core" -version = "0.4.1" -description = "" -optional = false -python-versions = "^3.10,<3.13" -groups = ["main", "qm-dev", "test"] -files = [] -develop = true - -[package.dependencies] -datamodel-code-generator = "^0.33.0" -jsonpatch = "^1.33" -jsonpointer = "^3.0.0" -matplotlib = "^3.10.6" -networkx = "~3.4.2" -pydantic = "^2.9.2" -python-json-logger = "^3.2.1" +jsonpointer = ">=3.0.0,<4.0.0" +matplotlib = ">=3.10.6,<4.0.0" +networkx = ">=3.4.2,<3.5.0" +pydantic = ">=2.9.2,<3.0.0" +python-json-logger = ">=3.2.1,<4.0.0" qualang-tools = {version = ">=0.20.0", markers = "python_version >= \"3.10\" and python_version < \"3.13\""} -qualibrate-config = "^0.1.7,<0.2.0" -types-networkx = "~3.4.2.20250527" - -[package.source] -type = "directory" -url = "../qualibrate-core" - -[[package]] -name = "qualibration-libs" -version = "0.2.1" -description = "Utility library supporting calibration nodes and graphs for the QUAlibration graphs platform." -optional = false -python-versions = ">=3.9, <3.13" -groups = ["test"] -files = [] -develop = true - -[package.dependencies] -colorama = ">=0.4.6" -lmfit = ">=1.3.3" -matplotlib = ">=3.10.0" -numpy = ">=1.20.0" -qm-qua = ">=1.2.2" -qualibrate = ">=0.2.1" -quam-builder = ">=0.1.2" -scipy = ">=1.13.1" -xarray = ">=2024.7.0" - -[package.source] -type = "directory" -url = "../qualibration-libs" +qualibrate-config = ">=0.1.7,<0.2.0" +types-networkx = ">=3.4.2.20250527,<3.5.0.0" [[package]] name = "quam" @@ -3291,7 +3140,7 @@ version = "3.14.1" description = "rapid fuzzy string matching" optional = false python-versions = ">=3.10" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "rapidfuzz-3.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:489440e4b5eea0d150a31076eb183bed0ec84f934df206c72ae4fc3424501758"}, {file = "rapidfuzz-3.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eff22cc938c3f74d194df03790a6c3325d213b28cf65cdefd6fdeae759b745d5"}, @@ -3396,7 +3245,7 @@ version = "2.32.5" description = "Python HTTP for Humans." optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, @@ -3418,7 +3267,7 @@ version = "1.0.0" description = "A utility belt for advanced users of python-requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, @@ -3462,7 +3311,7 @@ version = "1.7.2" description = "A set of python modules for machine learning and data mining" optional = false python-versions = ">=3.10" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "scikit_learn-1.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b33579c10a3081d076ab403df4a4190da4f4432d443521674637677dc91e61f"}, {file = "scikit_learn-1.7.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:36749fb62b3d961b1ce4fedf08fa57a1986cd409eff2d783bca5d4b9b5fce51c"}, @@ -3518,7 +3367,7 @@ version = "1.15.3" description = "Fundamental algorithms for scientific computing in Python" optional = false python-versions = ">=3.10" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c"}, {file = "scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253"}, @@ -3582,7 +3431,7 @@ version = "3.4.0" description = "Python bindings to FreeDesktop.org Secret Service API" optional = false python-versions = ">=3.10" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] markers = "sys_platform == \"linux\"" files = [ {file = "secretstorage-3.4.0-py3-none-any.whl", hash = "sha256:0e3b6265c2c63509fb7415717607e4b2c9ab767b7f344a57473b779ca13bd02e"}, @@ -3599,7 +3448,7 @@ version = "1.5.4" description = "Tool to Detect Surrounding Shell" optional = false python-versions = ">=3.7" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, @@ -3611,7 +3460,7 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -3623,7 +3472,7 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -3635,7 +3484,7 @@ version = "0.48.0" description = "The little ASGI library that shines." optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main"] files = [ {file = "starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659"}, {file = "starlette-0.48.0.tar.gz", hash = "sha256:7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46"}, @@ -3654,7 +3503,7 @@ version = "9.1.2" description = "Retry code until it succeeds" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138"}, {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"}, @@ -3670,7 +3519,7 @@ version = "3.6.0" description = "threadpoolctl" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb"}, {file = "threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e"}, @@ -3682,7 +3531,7 @@ version = "4.8.2" description = "TinyDB is a tiny, document oriented database optimized for your happiness :)" optional = false python-versions = "<4.0,>=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "tinydb-4.8.2-py3-none-any.whl", hash = "sha256:f97030ee5cbc91eeadd1d7af07ab0e48ceb04aa63d4a983adbaca4cba16e86c3"}, {file = "tinydb-4.8.2.tar.gz", hash = "sha256:f7dfc39b8d7fda7a1ca62a8dbb449ffd340a117c1206b68c50b1a481fb95181d"}, @@ -3694,7 +3543,7 @@ version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" -groups = ["main", "dev", "qm-dev", "test"] +groups = ["main", "dev", "test"] files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -3729,7 +3578,7 @@ files = [ {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] -markers = {main = "python_version < \"3.12\"", dev = "python_version == \"3.10\"", qm-dev = "python_version < \"3.12\"", test = "python_version < \"3.12\""} +markers = {main = "python_version < \"3.12\"", dev = "python_version == \"3.10\"", test = "python_version == \"3.10\""} [[package]] name = "tomli-w" @@ -3737,7 +3586,7 @@ version = "1.2.0" description = "A lil' TOML writer" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90"}, {file = "tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021"}, @@ -3749,7 +3598,7 @@ version = "0.13.3" description = "Style preserving TOML library" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0"}, {file = "tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1"}, @@ -3761,7 +3610,7 @@ version = "2025.9.11.17" description = "Canonical source for classifiers on PyPI (pypi.org)." optional = false python-versions = "*" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "trove_classifiers-2025.9.11.17-py3-none-any.whl", hash = "sha256:5d392f2d244deb1866556457d6f3516792124a23d1c3a463a2e8668a5d1c15dd"}, {file = "trove_classifiers-2025.9.11.17.tar.gz", hash = "sha256:931ca9841a5e9c9408bc2ae67b50d28acf85bef56219b56860876dd1f2d024dd"}, @@ -3773,7 +3622,7 @@ version = "4.4.4" description = "Run-time type checker for Python" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "typeguard-4.4.4-py3-none-any.whl", hash = "sha256:b5f562281b6bfa1f5492470464730ef001646128b180769880468bd84b68b09e"}, {file = "typeguard-4.4.4.tar.gz", hash = "sha256:3a7fd2dffb705d4d0efaed4306a704c89b9dee850b688f060a8b1615a79e5f74"}, @@ -3788,7 +3637,7 @@ version = "3.4.2.20250527" description = "Typing stubs for networkx" optional = false python-versions = ">=3.10" -groups = ["main", "qm-dev", "test"] +groups = ["main"] files = [ {file = "types_networkx-3.4.2.20250527-py3-none-any.whl", hash = "sha256:3716f750e61bf46ceba2a43474d7f9b61f306274a243cee6cc2aa9f4fb37ab41"}, {file = "types_networkx-3.4.2.20250527.tar.gz", hash = "sha256:484914f0dee6ce8d87bda494f641a9227e954d716d2f330bfcbf69c2431b646d"}, @@ -3803,7 +3652,7 @@ version = "4.15.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" -groups = ["main", "dev", "qm-dev", "test"] +groups = ["main", "dev", "test"] files = [ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, @@ -3815,7 +3664,7 @@ version = "0.4.1" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"}, {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"}, @@ -3836,31 +3685,13 @@ files = [ {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}, ] -[[package]] -name = "uncertainties" -version = "3.2.3" -description = "calculations with values with uncertainties, error propagation" -optional = false -python-versions = ">=3.8" -groups = ["test"] -files = [ - {file = "uncertainties-3.2.3-py3-none-any.whl", hash = "sha256:313353900d8f88b283c9bad81e7d2b2d3d4bcc330cbace35403faaed7e78890a"}, - {file = "uncertainties-3.2.3.tar.gz", hash = "sha256:76a5653e686f617a42922d546a239e9efce72e6b35411b7750a1d12dcba03031"}, -] - -[package.extras] -all = ["uncertainties[arrays,doc,test]"] -arrays = ["numpy"] -doc = ["python-docs-theme", "sphinx", "sphinx-copybutton"] -test = ["pytest", "pytest_codspeed", "pytest_cov", "scipy"] - [[package]] name = "urllib3" version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, @@ -3878,7 +3709,7 @@ version = "0.37.0" description = "The lightning-fast ASGI server." optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main"] files = [ {file = "uvicorn-0.37.0-py3-none-any.whl", hash = "sha256:913b2b88672343739927ce381ff9e2ad62541f9f8289664fa1d1d3803fa2ce6c"}, {file = "uvicorn-0.37.0.tar.gz", hash = "sha256:4115c8add6d3fd536c8ee77f0e14a7fd2ebba939fed9b02583a97f80648f9e13"}, @@ -3898,7 +3729,7 @@ version = "20.34.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "virtualenv-20.34.0-py3-none-any.whl", hash = "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026"}, {file = "virtualenv-20.34.0.tar.gz", hash = "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a"}, @@ -4025,7 +3856,7 @@ version = "1.2.0" description = "Python wrapper for extended filesystem attributes" optional = false python-versions = ">=3.8" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] markers = "sys_platform == \"darwin\"" files = [ {file = "xattr-1.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3df4d8d91e2996c3c72a390ec82e8544acdcb6c7df67b954f1736ff37ea4293e"}, @@ -4112,7 +3943,7 @@ version = "3.23.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] markers = "python_version < \"3.12\"" files = [ {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, @@ -4133,7 +3964,7 @@ version = "0.25.0" description = "Zstandard bindings for Python" optional = false python-versions = ">=3.9" -groups = ["main", "qm-dev", "test"] +groups = ["main", "test"] files = [ {file = "zstandard-0.25.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e59fdc271772f6686e01e1b3b74537259800f57e24280be3f29c8a0deb1904dd"}, {file = "zstandard-0.25.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4d441506e9b372386a5271c64125f72d5df6d2a8e8a2a45a0ae09b03cb781ef7"}, @@ -4242,4 +4073,4 @@ cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and pyt [metadata] lock-version = "2.1" python-versions = "^3.10,<3.13" -content-hash = "4657da2105785294b056c411f4051f2519da097de910c60faa3289f85236df02" +content-hash = "ac8ecc77313a00c733a03b8e1f0d98302b24daecf699a2f87141394b5ce3b738" diff --git a/pyproject.toml b/pyproject.toml index ec042fa..183134a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,11 +30,11 @@ pytest-mock = "^3.15.1" freezegun = "^1.2.0" xarray = "^2024.1.0" quam-builder = {git = "https://github.com/qua-platform/quam-builder.git"} -qualibration-libs = {path = "../qualibration-libs", develop = true} +#qualibration-libs = {path = "../qualibration-libs", develop = true} -[tool.poetry.group.qm-dev.dependencies] -qualibrate-config = {path = "../qualibrate-config", develop = true} -qualibrate-core = {path = "../qualibrate-core", develop = true} +#[tool.poetry.group.qm-dev.dependencies] +#qualibrate-config = {path = "../qualibrate-config", develop = true} +#qualibrate-core = {path = "../qualibrate-core", develop = true} [tool.poetry.scripts] qualibrate-runner = "qualibrate_runner.__main__:main" From 4d73dbe9de5064416ee22f316005a28d09260347 Mon Sep 17 00:00:00 2001 From: Jonatan Kutchinsky Date: Thu, 27 Nov 2025 10:08:01 +0100 Subject: [PATCH 17/26] Fixing most lint/type errors --- qualibrate_runner/config/models.py | 2 + tests/conftest.py | 47 ++--- .../fixtures/test_nodes/node_with_actions.py | 27 +-- .../test_nodes/node_with_subroutine.py | 12 +- .../test_nodes/test_node_can_raise_in_body.py | 2 +- tests/integration/test_run_job_integration.py | 84 ++++++--- tests/unit/test_config_state.py | 87 +++++++--- tests/unit/test_models_common.py | 65 ++++--- tests/unit/test_models_last_run.py | 87 +++++++--- tests/unit/test_run_job.py | 164 +++++++++++++----- 10 files changed, 390 insertions(+), 187 deletions(-) diff --git a/qualibrate_runner/config/models.py b/qualibrate_runner/config/models.py index fdd9983..9fbfa09 100644 --- a/qualibrate_runner/config/models.py +++ b/qualibrate_runner/config/models.py @@ -17,6 +17,8 @@ from qualibrate_runner.core.models.last_run import LastRun from qualibrate_runner.core.types import QGraphType, QNodeType +__all__ = ["State", "RunStatusEnum"] + class State(BaseModel): """ diff --git a/tests/conftest.py b/tests/conftest.py index e73887d..a4e717b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,8 +5,11 @@ including mock objects, sample data, and test utilities. """ +from __future__ import annotations + from datetime import datetime, timezone from pathlib import Path +from typing import Any, Iterator from unittest.mock import Mock import pytest @@ -18,19 +21,19 @@ @pytest.fixture -def aware_datetime(): +def aware_datetime() -> datetime: """Provide a timezone-aware datetime for testing.""" return datetime(2024, 1, 15, 10, 30, 0, tzinfo=timezone.utc) @pytest.fixture -def later_datetime(): +def later_datetime() -> datetime: """Provide a later timezone-aware datetime for testing durations.""" return datetime(2024, 1, 15, 10, 30, 5, 500000, tzinfo=timezone.utc) @pytest.fixture -def sample_traceback(): +def sample_traceback() -> list[str]: """Provide a sample traceback as list of strings.""" return [ ' File "/path/to/node.py", line 42, in run\n' @@ -41,7 +44,7 @@ def sample_traceback(): @pytest.fixture -def sample_run_error(sample_traceback): +def sample_run_error(sample_traceback: Any) -> RunError: """Provide a sample RunError instance.""" return RunError( error_class="ValueError", @@ -51,7 +54,7 @@ def sample_run_error(sample_traceback): @pytest.fixture -def sample_state_update(): +def sample_state_update() -> StateUpdate: """Provide a sample StateUpdate instance.""" return StateUpdate( key="qubit_0", @@ -63,7 +66,7 @@ def sample_state_update(): @pytest.fixture -def sample_last_run_running(aware_datetime): +def sample_last_run_running(aware_datetime: Any) -> LastRun: """Provide a LastRun instance with RUNNING status.""" return LastRun( status=RunStatusEnum.RUNNING, @@ -76,7 +79,9 @@ def sample_last_run_running(aware_datetime): @pytest.fixture -def sample_last_run_finished(aware_datetime, later_datetime): +def sample_last_run_finished( + aware_datetime: Any, later_datetime: Any +) -> LastRun: """Provide a LastRun instance with FINISHED status.""" return LastRun( status=RunStatusEnum.FINISHED, @@ -91,7 +96,9 @@ def sample_last_run_finished(aware_datetime, later_datetime): @pytest.fixture -def sample_last_run_error(aware_datetime, later_datetime, sample_run_error): +def sample_last_run_error( + aware_datetime: Any, later_datetime: Any, sample_run_error: Any +) -> LastRun: """Provide a LastRun instance with ERROR status.""" return LastRun( status=RunStatusEnum.ERROR, @@ -106,13 +113,13 @@ def sample_last_run_error(aware_datetime, later_datetime, sample_run_error): @pytest.fixture -def fresh_state(): +def fresh_state() -> State: """Provide a fresh State instance with no execution history.""" return State() @pytest.fixture -def mock_node(): +def mock_node() -> Mock: """Provide a mock QualibrationNode. Note: This cannot be used directly with State() due to Pydantic @@ -127,7 +134,7 @@ def mock_node(): @pytest.fixture -def mock_workflow(): +def mock_workflow() -> Mock: """Provide a mock workflow (QGraph). Note: This cannot be used directly with State() due to Pydantic @@ -141,13 +148,13 @@ def mock_workflow(): @pytest.fixture -def state_with_node(mock_node): +def state_with_node(mock_node: Any) -> State: """Provide a State with a mock node (bypassing validation).""" return State.model_construct(run_item=mock_node) @pytest.fixture -def state_with_workflow(mock_workflow): +def state_with_workflow(mock_workflow: Any) -> State: """Provide a State with a mock workflow (bypassing validation).""" return State.model_construct(run_item=mock_workflow) @@ -156,7 +163,7 @@ def state_with_workflow(mock_workflow): @pytest.fixture -def mock_library(mock_workflow): +def mock_library(mock_workflow: Any) -> Mock: """Provide a mock QualibrationLibrary.""" library = Mock() library.graphs = {"test_workflow": mock_workflow} @@ -164,7 +171,7 @@ def mock_library(mock_workflow): @pytest.fixture -def sample_parameters_class(): +def sample_parameters_class() -> type[Any]: """Provide a sample Pydantic model for parameter validation.""" from pydantic import BaseModel, Field @@ -177,13 +184,13 @@ class TestParameters(BaseModel): @pytest.fixture -def sample_workflow_parameters_class(): +def sample_workflow_parameters_class() -> type[Any]: """Provide a sample workflow parameters class with nodes and parameters.""" from pydantic import BaseModel, Field class NodeParams(BaseModel): - node1: dict = Field(default_factory=dict) - node2: dict = Field(default_factory=dict) + node1: dict[str, Any] = Field(default_factory=dict) + node2: dict[str, Any] = Field(default_factory=dict) class WorkflowParams(BaseModel): frequency: float = Field(gt=0.0) @@ -197,7 +204,7 @@ class FullParams(BaseModel): @pytest.fixture(scope="function") -def test_library(): +def test_library() -> Any: """ Provide a QualibrationLibrary instance loaded with test nodes. @@ -215,7 +222,7 @@ def test_library(): # Create library with set_active=False to avoid interfering with # other tests - library = QualibrationLibrary( + library: Any = QualibrationLibrary( library_folder=test_nodes_path, set_active=False ) diff --git a/tests/fixtures/test_nodes/node_with_actions.py b/tests/fixtures/test_nodes/node_with_actions.py index 2053206..2a29f3e 100644 --- a/tests/fixtures/test_nodes/node_with_actions.py +++ b/tests/fixtures/test_nodes/node_with_actions.py @@ -8,15 +8,16 @@ - Conditional action execution via skip_if """ +from typing import Any from unittest.mock import Mock import numpy as np import xarray as xr from pydantic import Field from qualibrate import NodeParameters, QualibrationNode -from qualang_tools.units import unit -from qualang_tools.results import progress_counter -from qualibration_libs.data import XarrayDataFetcher +from qualang_tools.units import unit # type: ignore[import-untyped] +from qualang_tools.results import progress_counter # type: ignore[import-untyped] +from qualibration_libs.data import XarrayDataFetcher # type: ignore[import-untyped] class Parameters(NodeParameters): @@ -47,14 +48,14 @@ class Parameters(NodeParameters): # Create the node -node = QualibrationNode( +node: QualibrationNode[Parameters, Parameters] = QualibrationNode( # type: ignore[type-var] name="node_with_actions", parameters=Parameters(), ) @node.run_action -def prepare_data(node): +def prepare_data(node: Any) -> dict[str, Any]: """Generate measurement data based on parameters.""" data = [ node.parameters.amplitude * i for i in range(node.parameters.num_points) @@ -67,7 +68,7 @@ def prepare_data(node): @node.run_action -def process_data(node): +def process_data(node: Any) -> dict[str, Any]: """Process the prepared data.""" # Access data from previous action via namespace raw_data = node.namespace["data"] @@ -82,8 +83,8 @@ def process_data(node): } -@node.run_action(skip_if=not node.parameters.trigger_deep_error) -def execute_qua_program(node): +@node.run_action(skip_if=not node.parameters.trigger_deep_error) # type: ignore[misc] +def execute_qua_program(node: Any) -> dict[str, Any]: mock_job = Mock() mock_job.result_handles.keys.return_value = [] @@ -112,8 +113,8 @@ def execute_qua_program(node): return {"ds_raw": dataset} -@node.run_action(skip_if=not node.parameters.update_state) -def update_machine_state(node): +@node.run_action(skip_if=not node.parameters.update_state) # type: ignore[misc] +def update_machine_state(node: Any) -> dict[str, Any]: """Update quantum machine state (if enabled).""" # This action only runs if update_state is True state_updated = False @@ -129,15 +130,15 @@ def update_machine_state(node): } -@node.run_action(skip_if=not node.parameters.trigger_error) -def process_data_with_error(node): +@node.run_action(skip_if=not node.parameters.trigger_error) # type: ignore[misc] +def process_data_with_error(node: Any) -> dict[str, Any]: """Process data - this action raises an error when executed.""" # This action always fails when it runs raise ValueError(node.parameters.error_message) @node.run_action -def finalize_results(node): +def finalize_results(node: Any) -> dict[str, Any]: """Collect all results into final summary.""" # Gather all data from namespace summary = { diff --git a/tests/fixtures/test_nodes/node_with_subroutine.py b/tests/fixtures/test_nodes/node_with_subroutine.py index 2cf89e5..55d9a0c 100644 --- a/tests/fixtures/test_nodes/node_with_subroutine.py +++ b/tests/fixtures/test_nodes/node_with_subroutine.py @@ -5,6 +5,8 @@ that is called from an action, ensuring the full call chain is captured. """ +from typing import Any + from pydantic import Field from qualibrate import NodeParameters, QualibrationNode @@ -26,7 +28,7 @@ class Parameters(NodeParameters): # Create the node -node = QualibrationNode( +node: QualibrationNode[Parameters, Parameters] = QualibrationNode( # type: ignore[type-var] name="node_with_subroutine", parameters=Parameters(), ) @@ -59,7 +61,7 @@ def another_helper(value: float, message: str) -> float: @node.run_action -def prepare_data(node): +def prepare_data(node: Any) -> dict[str, Any]: """Generate measurement data based on parameters.""" # Simple data generation without errors data = [ @@ -68,8 +70,8 @@ def prepare_data(node): return {"data": data, "data_length": len(data)} -@node.run_action(skip_if=not node.parameters.trigger_subroutine_error) -def process_with_subroutine(node): +@node.run_action(skip_if=not node.parameters.trigger_subroutine_error) # type: ignore[misc] +def process_with_subroutine(node: Any) -> dict[str, Any]: """Process data using a helper function. This action calls helper_function, which will raise an IndexError @@ -83,7 +85,7 @@ def process_with_subroutine(node): @node.run_action -def finalize(node): +def finalize(node: Any) -> dict[str, str]: """Finalize results.""" return {"status": "completed"} diff --git a/tests/fixtures/test_nodes/test_node_can_raise_in_body.py b/tests/fixtures/test_nodes/test_node_can_raise_in_body.py index 69b899c..22625e5 100644 --- a/tests/fixtures/test_nodes/test_node_can_raise_in_body.py +++ b/tests/fixtures/test_nodes/test_node_can_raise_in_body.py @@ -35,7 +35,7 @@ class Parameters(NodeParameters): # Create the node -node = QualibrationNode( +node: QualibrationNode[Parameters, Parameters] = QualibrationNode( # type: ignore[type-var] name="node_raises_in_body", parameters=Parameters(), ) diff --git a/tests/integration/test_run_job_integration.py b/tests/integration/test_run_job_integration.py index 8c5c426..ee88715 100644 --- a/tests/integration/test_run_job_integration.py +++ b/tests/integration/test_run_job_integration.py @@ -11,16 +11,22 @@ initialization including filepath setting. """ +from __future__ import annotations + + import pytest from qualibrate_runner.config.models import RunStatusEnum from qualibrate_runner.core.run_job import run_node +from typing import Any class TestSimpleNodeExecution: """Integration tests for simple nodes without action system.""" - def test_simple_node_success(self, test_library, fresh_state): + def test_simple_node_success( + self, test_library: Any, fresh_state: Any + ) -> None: """Test successful execution of simple node.""" node = test_library.nodes["node_raises_in_body"] @@ -38,8 +44,8 @@ def test_simple_node_success(self, test_library, fresh_state): assert node.results["error_raised"] is False def test_simple_node_with_different_parameters( - self, test_library, fresh_state - ): + self, test_library: Any, fresh_state: Any + ) -> None: """Test simple node with runtime parameter override.""" node = test_library.nodes["node_raises_in_body"] @@ -51,7 +57,9 @@ def test_simple_node_with_different_parameters( assert fresh_state.last_run.status == RunStatusEnum.FINISHED assert fresh_state.last_run.passed_parameters == params - def test_simple_node_error_in_body(self, test_library, fresh_state): + def test_simple_node_error_in_body( + self, test_library: Any, fresh_state: Any + ) -> None: """Test error capture when node body raises exception.""" node = test_library.nodes["node_raises_in_body"] @@ -75,7 +83,9 @@ def test_simple_node_error_in_body(self, test_library, fresh_state): class TestNodeWithActionsExecution: """Integration tests for nodes with action system.""" - def test_all_actions_execute(self, test_library, fresh_state): + def test_all_actions_execute( + self, test_library: Any, fresh_state: Any + ) -> None: """Test full execution with all default actions.""" node = test_library.nodes["node_with_actions"] @@ -92,7 +102,9 @@ def test_all_actions_execute(self, test_library, fresh_state): assert "state_updated" in node.namespace # update_state defaults True assert "summary" in node.namespace - def test_conditional_action_skip(self, test_library, fresh_state): + def test_conditional_action_skip( + self, test_library: Any, fresh_state: Any + ) -> None: """Test conditional action skipping via parameters.""" node = test_library.nodes["node_with_actions"] @@ -110,7 +122,9 @@ def test_conditional_action_skip(self, test_library, fresh_state): # Verify conditional action was skipped assert "state_updated" not in node.namespace - def test_action_raises_error(self, test_library, fresh_state): + def test_action_raises_error( + self, test_library: Any, fresh_state: Any + ) -> None: """Test error capture when action raises exception.""" node = test_library.nodes["node_with_actions"] @@ -128,7 +142,9 @@ def test_action_raises_error(self, test_library, fresh_state): assert fresh_state.last_run.error is not None assert "ValueError" in fresh_state.last_run.error.error_class - def test_error_from_external_library(self, test_library, fresh_state): + def test_error_from_external_library( + self, test_library: Any, fresh_state: Any + ) -> None: """Test error propagation from external library code.""" node = test_library.nodes["node_with_actions"] @@ -148,8 +164,8 @@ class TestNamespaceAccumulation: """Integration tests for namespace data flow across actions.""" def test_namespace_accumulates_across_actions( - self, test_library, fresh_state - ): + self, test_library: Any, fresh_state: Any + ) -> None: """Test that namespace accumulates data from all actions.""" node = test_library.nodes["node_with_actions"] @@ -167,7 +183,9 @@ def test_namespace_accumulates_across_actions( # From finalize_results assert "summary" in node.namespace - def test_later_actions_use_earlier_data(self, test_library, fresh_state): + def test_later_actions_use_earlier_data( + self, test_library: Any, fresh_state: Any + ) -> None: """Test that actions can access data from previous actions.""" node = test_library.nodes["node_with_actions"] @@ -186,7 +204,9 @@ def test_later_actions_use_earlier_data(self, test_library, fresh_state): class TestStateTracking: """Integration tests for state lifecycle management.""" - def test_state_lifecycle_success(self, test_library, fresh_state): + def test_state_lifecycle_success( + self, test_library: Any, fresh_state: Any + ) -> None: """Test state transitions during successful execution.""" node = test_library.nodes["node_with_actions"] @@ -205,7 +225,9 @@ def test_state_lifecycle_success(self, test_library, fresh_state): fresh_state.last_run.completed_at >= fresh_state.last_run.started_at ) - def test_state_lifecycle_with_error(self, test_library, fresh_state): + def test_state_lifecycle_with_error( + self, test_library: Any, fresh_state: Any + ) -> None: """Test state updates when execution fails.""" node = test_library.nodes["node_with_actions"] @@ -221,7 +243,9 @@ def test_state_lifecycle_with_error(self, test_library, fresh_state): assert fresh_state.last_run.error is not None assert fresh_state.last_run.completed_at is not None - def test_passed_parameters_captured(self, test_library, fresh_state): + def test_passed_parameters_captured( + self, test_library: Any, fresh_state: Any + ) -> None: """Test that passed parameters are captured in state.""" node = test_library.nodes["node_with_actions"] @@ -240,7 +264,9 @@ def test_passed_parameters_captured(self, test_library, fresh_state): class TestParametricBehavior: """Integration tests for parametric node behavior.""" - def test_namespace_persists_across_runs(self, test_library, fresh_state): + def test_namespace_persists_across_runs( + self, test_library: Any, fresh_state: Any + ) -> None: """Test that namespace accumulates across multiple runs""" node = test_library.nodes["node_with_actions"] @@ -259,8 +285,8 @@ def test_namespace_persists_across_runs(self, test_library, fresh_state): assert "state_updated" in node.namespace def test_error_execution_preserves_namespace( - self, test_library, fresh_state - ): + self, test_library: Any, fresh_state: Any + ) -> None: """Test that namespace is preserved even when execution fails.""" node = test_library.nodes["node_with_actions"] @@ -281,7 +307,9 @@ def test_error_execution_preserves_namespace( class TestErrorMessaging: """Integration tests for improved error messaging""" - def test_error_details_for_action_failure(self, test_library, fresh_state): + def test_error_details_for_action_failure( + self, test_library: Any, fresh_state: Any + ) -> None: """Test error details when error occurs in action (inside library error).""" node = test_library.nodes["node_with_actions"] @@ -324,7 +352,9 @@ def test_error_details_for_action_failure(self, test_library, fresh_state): "action_manager.py" in traceback_str or "action.py" in traceback_str ) - def test_error_details_for_body_failure(self, test_library, fresh_state): + def test_error_details_for_body_failure( + self, test_library: Any, fresh_state: Any + ) -> None: """Test error details when error occurs in node body (not in action).""" node = test_library.nodes["node_raises_in_body"] @@ -364,7 +394,9 @@ def test_error_details_for_body_failure(self, test_library, fresh_state): # Body errors should not have action framework in simplified version assert "action_manager.py" not in error.details - def test_error_details_with_action_history(self, test_library, fresh_state): + def test_error_details_with_action_history( + self, test_library: Any, fresh_state: Any + ) -> None: """Test error details include completed and skipped actions.""" node = test_library.nodes["node_with_actions"] @@ -394,8 +426,8 @@ def test_error_details_with_action_history(self, test_library, fresh_state): assert "process_data_with_error" in error.details def test_error_details_for_action_with_subroutine( - self, test_library, fresh_state - ): + self, test_library: Any, fresh_state: Any + ) -> None: """Test error in subroutine called by action shows full call chain.""" node = test_library.nodes["node_with_subroutine"] @@ -430,7 +462,9 @@ def test_error_details_for_action_with_subroutine( # 5. Full traceback should be preserved with all frames assert len(error.traceback) > 5 - def test_error_headline_for_action(self, test_library, fresh_state): + def test_error_headline_for_action( + self, test_library: Any, fresh_state: Any + ) -> None: """Test headline format for action errors.""" node = test_library.nodes["node_with_actions"] @@ -449,7 +483,9 @@ def test_error_headline_for_action(self, test_library, fresh_state): # Should indicate it's in an action assert "action" in headline - def test_error_headline_for_body(self, test_library, fresh_state): + def test_error_headline_for_body( + self, test_library: Any, fresh_state: Any + ) -> None: """Test headline format for body/initialization errors.""" node = test_library.nodes["node_raises_in_body"] diff --git a/tests/unit/test_config_state.py b/tests/unit/test_config_state.py index 6094b84..0788bfd 100644 --- a/tests/unit/test_config_state.py +++ b/tests/unit/test_config_state.py @@ -5,24 +5,28 @@ the single source of truth for the runner's execution state. """ +from __future__ import annotations + + import pytest from qualibrate_runner.config.models import State from qualibrate_runner.core.models.enums import RunnableType, RunStatusEnum from qualibrate_runner.core.models.last_run import LastRun +from typing import Any class TestStateCreation: """Tests for creating State instances.""" - def test_create_fresh_state(self): + def test_create_fresh_state(self) -> None: """Test creating a fresh State with no execution history.""" state = State() assert state.last_run is None assert state.run_item is None - def test_create_with_last_run(self, sample_last_run_running): + def test_create_with_last_run(self, sample_last_run_running: Any) -> None: """Test creating State with a LastRun.""" state = State(last_run=sample_last_run_running) assert state.last_run is not None @@ -33,17 +37,21 @@ def test_create_with_last_run(self, sample_last_run_running): class TestIsRunningProperty: """Tests for the is_running property.""" - def test_not_running_when_finished(self, sample_last_run_finished): + def test_not_running_when_finished( + self, sample_last_run_finished: Any + ) -> None: """Test is_running is False when status is FINISHED.""" state = State(last_run=sample_last_run_finished) assert state.is_running is False - def test_not_running_when_error(self, sample_last_run_error): + def test_not_running_when_error(self, sample_last_run_error: Any) -> None: """Test is_running is False when status is ERROR.""" state = State(last_run=sample_last_run_error) assert state.is_running is False - def test_is_running_changes_with_status(self, aware_datetime, mock_node): + def test_is_running_changes_with_status( + self, aware_datetime: Any, mock_node: Any + ) -> None: """Test is_running property changes as status changes.""" state = State() @@ -57,6 +65,7 @@ def test_is_running_changes_with_status(self, aware_datetime, mock_node): name="test_node", idx=-1, runnable_type=RunnableType.NODE, + passed_parameters={}, ) state.run_item = mock_node @@ -70,6 +79,7 @@ def test_is_running_changes_with_status(self, aware_datetime, mock_node): name="test_node", idx=42, runnable_type=RunnableType.NODE, + passed_parameters={}, ) assert state.is_running is False @@ -78,10 +88,14 @@ def test_is_running_changes_with_status(self, aware_datetime, mock_node): class TestClearMethod: """Tests for the clear() method.""" - def test_clear_finished_state(self, sample_last_run_finished, mock_node): + def test_clear_finished_state( + self, sample_last_run_finished: Any, mock_node: Any + ) -> None: """Test clearing state after finished execution.""" state = State.model_construct( - last_run=sample_last_run_finished, run_item=mock_node + last_run=sample_last_run_finished, + run_item=mock_node, + passed_parameters={}, ) # Should succeed (not running) @@ -90,10 +104,14 @@ def test_clear_finished_state(self, sample_last_run_finished, mock_node): assert state.last_run is None assert state.run_item is None - def test_clear_error_state(self, sample_last_run_error, mock_node): + def test_clear_error_state( + self, sample_last_run_error: Any, mock_node: Any + ) -> None: """Test clearing state after error.""" state = State.model_construct( - last_run=sample_last_run_error, run_item=mock_node + last_run=sample_last_run_error, + run_item=mock_node, + passed_parameters={}, ) # Should succeed (not running) @@ -103,11 +121,13 @@ def test_clear_error_state(self, sample_last_run_error, mock_node): assert state.run_item is None def test_clear_while_running_raises_error( - self, sample_last_run_running, mock_node - ): + self, sample_last_run_running: Any, mock_node: Any + ) -> None: """Test that clearing while running raises RuntimeError.""" state = State.model_construct( - last_run=sample_last_run_running, run_item=mock_node + last_run=sample_last_run_running, + run_item=mock_node, + passed_parameters={}, ) # Should raise RuntimeError @@ -124,7 +144,9 @@ def test_clear_while_running_raises_error( class TestStateLifecycle: """Tests for the typical State lifecycle during execution.""" - def test_full_execution_lifecycle_success(self, aware_datetime, mock_node): + def test_full_execution_lifecycle_success( + self, aware_datetime: Any, mock_node: Any + ) -> None: """Test state through full successful execution lifecycle.""" state = State() @@ -140,6 +162,7 @@ def test_full_execution_lifecycle_success(self, aware_datetime, mock_node): name="test_node", idx=-1, runnable_type=RunnableType.NODE, + passed_parameters={}, ) state.run_item = mock_node @@ -153,6 +176,7 @@ def test_full_execution_lifecycle_success(self, aware_datetime, mock_node): name="test_node", idx=42, runnable_type=RunnableType.NODE, + passed_parameters={}, ) assert state.is_running is False @@ -165,8 +189,8 @@ def test_full_execution_lifecycle_success(self, aware_datetime, mock_node): assert state.run_item is None def test_full_execution_lifecycle_error( - self, aware_datetime, mock_node, sample_run_error - ): + self, aware_datetime: Any, mock_node: Any, sample_run_error: Any + ) -> None: """Test state through full error execution lifecycle.""" state = State() @@ -177,6 +201,7 @@ def test_full_execution_lifecycle_error( name="test_node", idx=-1, runnable_type=RunnableType.NODE, + passed_parameters={}, ) state.run_item = mock_node @@ -191,6 +216,7 @@ def test_full_execution_lifecycle_error( idx=-1, runnable_type=RunnableType.NODE, error=sample_run_error, + passed_parameters={}, ) assert state.is_running is False @@ -202,7 +228,9 @@ def test_full_execution_lifecycle_error( assert state.last_run is None assert state.run_item is None - def test_multiple_sequential_executions(self, aware_datetime, mock_node): + def test_multiple_sequential_executions( + self, aware_datetime: Any, mock_node: Any + ) -> None: """Test state through multiple sequential executions.""" state = State() @@ -213,6 +241,7 @@ def test_multiple_sequential_executions(self, aware_datetime, mock_node): name="node_1", idx=-1, runnable_type=RunnableType.NODE, + passed_parameters={}, ) state.run_item = mock_node assert state.is_running is True @@ -224,6 +253,7 @@ def test_multiple_sequential_executions(self, aware_datetime, mock_node): name="node_1", idx=1, runnable_type=RunnableType.NODE, + passed_parameters={}, ) assert state.is_running is False @@ -234,6 +264,7 @@ def test_multiple_sequential_executions(self, aware_datetime, mock_node): name="node_2", idx=-1, runnable_type=RunnableType.NODE, + passed_parameters={}, ) assert state.is_running is True assert state.last_run.name == "node_2" @@ -242,17 +273,17 @@ def test_multiple_sequential_executions(self, aware_datetime, mock_node): class TestStateArbitraryTypes: """Tests for arbitrary type support (run_item).""" - def test_store_node_object(self, state_with_node): + def test_store_node_object(self, state_with_node: Any) -> None: """Test storing a QualibrationNode object in run_item.""" assert state_with_node.run_item.name == "test_node" assert state_with_node.run_item.snapshot_idx == 42 - def test_store_workflow_object(self, state_with_workflow): + def test_store_workflow_object(self, state_with_workflow: Any) -> None: """Test storing a workflow (QGraph) object in run_item.""" assert state_with_workflow.run_item.name == "test_workflow" assert state_with_workflow.run_item.snapshot_idx == 100 - def test_run_item_can_be_any_object(self): + def test_run_item_can_be_any_object(self) -> None: """Test that run_item can store any object (arbitrary_types_allowed). Note: When using model_construct, Pydantic bypasses validation @@ -261,7 +292,7 @@ def test_run_item_can_be_any_object(self): # Create a simple custom object class CustomRunnable: - def __init__(self): + def __init__(self) -> None: self.name = "custom" custom_obj = CustomRunnable() @@ -273,7 +304,9 @@ def __init__(self): class TestStateEdgeCases: """Tests for edge cases and special scenarios.""" - def test_set_last_run_to_none_explicitly(self, sample_last_run_finished): + def test_set_last_run_to_none_explicitly( + self, sample_last_run_finished: Any + ) -> None: """Test explicitly setting last_run to None.""" state = State(last_run=sample_last_run_finished) assert state.last_run is not None @@ -282,7 +315,9 @@ def test_set_last_run_to_none_explicitly(self, sample_last_run_finished): assert state.last_run is None assert state.is_running is False - def test_set_run_item_to_none_explicitly(self, state_with_node): + def test_set_run_item_to_none_explicitly( + self, state_with_node: Any + ) -> None: """Test explicitly setting run_item to None.""" assert state_with_node.run_item is not None @@ -290,8 +325,8 @@ def test_set_run_item_to_none_explicitly(self, state_with_node): assert state_with_node.run_item is None def test_replace_last_run_while_not_running( - self, sample_last_run_finished, sample_last_run_error - ): + self, sample_last_run_finished: Any, sample_last_run_error: Any + ) -> None: """Test replacing last_run when not running.""" state = State(last_run=sample_last_run_finished) @@ -300,7 +335,7 @@ def test_replace_last_run_while_not_running( assert state.last_run.status == RunStatusEnum.ERROR - def test_state_with_none_values(self): + def test_state_with_none_values(self) -> None: """Test State initialized with explicit None values.""" state = State(last_run=None, run_item=None) @@ -308,7 +343,7 @@ def test_state_with_none_values(self): assert state.run_item is None assert state.is_running is False - def test_is_running_check_is_safe_with_none(self): + def test_is_running_check_is_safe_with_none(self) -> None: """Test that is_running safely handles None last_run.""" state = State() diff --git a/tests/unit/test_models_common.py b/tests/unit/test_models_common.py index 1b50a8e..ab9793b 100644 --- a/tests/unit/test_models_common.py +++ b/tests/unit/test_models_common.py @@ -5,6 +5,11 @@ during calibration execution. """ +from __future__ import annotations + + +from typing import Any + import pytest from qualibrate_runner.core.models.common import RunError, StateUpdate @@ -13,7 +18,7 @@ class TestRunError: """Tests for the RunError model.""" - def test_create_with_all_fields(self, sample_traceback): + def test_create_with_all_fields(self, sample_traceback: Any) -> None: """Test creating RunError with all required fields.""" error = RunError( error_class="ValueError", @@ -26,7 +31,7 @@ def test_create_with_all_fields(self, sample_traceback): assert error.traceback == sample_traceback assert len(error.traceback) == 2 - def test_create_from_exception(self): + def test_create_from_exception(self) -> None: """Test creating RunError from a real exception.""" import traceback as tb @@ -44,7 +49,7 @@ def test_create_from_exception(self): assert len(error.traceback) > 0 assert any("raise ValueError" in line for line in error.traceback) - def test_serialization(self, sample_run_error): + def test_serialization(self, sample_run_error: Any) -> None: """Test that RunError can be serialized to dict.""" data = sample_run_error.model_dump() @@ -53,7 +58,7 @@ def test_serialization(self, sample_run_error): assert isinstance(data["traceback"], list) assert len(data["traceback"]) == 2 - def test_deserialization(self, sample_traceback): + def test_deserialization(self, sample_traceback: Any) -> None: """Test that RunError can be deserialized from dict.""" data = { "error_class": "RuntimeError", @@ -67,7 +72,7 @@ def test_deserialization(self, sample_traceback): assert error.message == "Something went wrong" assert error.traceback == sample_traceback - def test_different_error_classes(self): + def test_different_error_classes(self) -> None: """Test RunError with various exception types.""" error_types = [ "ValueError", @@ -86,7 +91,7 @@ def test_different_error_classes(self): ) assert error.error_class == error_type - def test_multiline_error_message(self): + def test_multiline_error_message(self) -> None: """Test RunError with multiline error message.""" multiline_message = """First line of error Second line with details @@ -101,7 +106,7 @@ def test_multiline_error_message(self): assert error.message == multiline_message assert "\n" in error.message - def test_long_traceback(self): + def test_long_traceback(self) -> None: """Test RunError with a long traceback (many frames).""" long_traceback = [ f' File "/path/frame_{i}.py", line {i * 10}, in func_{i}\n' @@ -116,7 +121,9 @@ def test_long_traceback(self): assert len(error.traceback) == 20 - def test_create_with_optional_detail_fields(self, sample_traceback): + def test_create_with_optional_detail_fields( + self, sample_traceback: Any + ) -> None: """Test creating RunError with optional detail fields.""" error = RunError( error_class="ValueError", @@ -133,7 +140,9 @@ def test_create_with_optional_detail_fields(self, sample_traceback): == "The 'frequency' parameter must be greater than zero." ) - def test_create_without_optional_detail_fields(self, sample_traceback): + def test_create_without_optional_detail_fields( + self, sample_traceback: Any + ) -> None: """Test that optional detail fields default to None.""" error = RunError( error_class="ValueError", @@ -144,7 +153,9 @@ def test_create_without_optional_detail_fields(self, sample_traceback): assert error.details_headline is None assert error.details is None - def test_serialization_with_optional_fields(self, sample_traceback): + def test_serialization_with_optional_fields( + self, sample_traceback: Any + ) -> None: """Test serialization includes optional fields when present.""" error = RunError( error_class="RuntimeError", @@ -160,7 +171,9 @@ def test_serialization_with_optional_fields(self, sample_traceback): assert "details" in serialized assert serialized["details"] == "Check the logs for more information" - def test_deserialization_with_optional_fields(self, sample_traceback): + def test_deserialization_with_optional_fields( + self, sample_traceback: Any + ) -> None: """Test deserialization with optional fields.""" data = { "error_class": "ValueError", @@ -174,7 +187,9 @@ def test_deserialization_with_optional_fields(self, sample_traceback): assert error.details_headline == "Test Headline" assert error.details == "Test Details" - def test_deserialization_without_optional_fields(self, sample_traceback): + def test_deserialization_without_optional_fields( + self, sample_traceback: Any + ) -> None: """Test deserialization without optional fields.""" data = { "error_class": "ValueError", @@ -190,7 +205,7 @@ def test_deserialization_without_optional_fields(self, sample_traceback): class TestStateUpdate: """Tests for the StateUpdate model.""" - def test_create_with_all_fields(self): + def test_create_with_all_fields(self) -> None: """Test creating StateUpdate with all fields.""" update = StateUpdate( key="qubit_0", @@ -206,7 +221,7 @@ def test_create_with_all_fields(self): assert update.new == 5.1e9 assert update.updated is True - def test_create_with_default_updated_false(self): + def test_create_with_default_updated_false(self) -> None: """Test that updated defaults to False.""" update = StateUpdate( key="qubit_1", @@ -217,7 +232,7 @@ def test_create_with_default_updated_false(self): assert update.updated is False - def test_string_attribute(self): + def test_string_attribute(self) -> None: """Test StateUpdate with string attribute name.""" update = StateUpdate( key="resonator_0", @@ -230,7 +245,7 @@ def test_string_attribute(self): assert update.attr == "wiring" assert isinstance(update.attr, str) - def test_integer_attribute_for_list_index(self): + def test_integer_attribute_for_list_index(self) -> None: """Test StateUpdate with integer attribute (list index).""" update = StateUpdate( key="calibration_array", @@ -243,7 +258,7 @@ def test_integer_attribute_for_list_index(self): assert update.attr == 3 assert isinstance(update.attr, int) - def test_various_value_types(self): + def test_various_value_types(self) -> None: """Test StateUpdate with different value types.""" # Float values update_float = StateUpdate( @@ -290,7 +305,7 @@ def test_various_value_types(self): ) assert isinstance(update_dict.old, dict) - def test_nested_key_path(self): + def test_nested_key_path(self) -> None: """Test StateUpdate with nested key path (dot notation).""" update = StateUpdate( key="qubit_0.readout.resonator", @@ -303,7 +318,7 @@ def test_nested_key_path(self): assert update.key == "qubit_0.readout.resonator" assert "." in update.key - def test_serialization(self, sample_state_update): + def test_serialization(self, sample_state_update: Any) -> None: """Test that StateUpdate can be serialized to dict.""" data = sample_state_update.model_dump() @@ -313,7 +328,7 @@ def test_serialization(self, sample_state_update): assert data["new"] == 5.1e9 assert data["updated"] is True - def test_deserialization(self): + def test_deserialization(self) -> None: """Test that StateUpdate can be deserialized from dict.""" data = { "key": "qubit_2", @@ -331,7 +346,7 @@ def test_deserialization(self): assert update.new == 55e-6 assert update.updated is True - def test_no_change_update(self): + def test_no_change_update(self) -> None: """Test StateUpdate where old and new values are the same.""" update = StateUpdate( key="qubit_0", @@ -345,7 +360,7 @@ def test_no_change_update(self): assert update.old == update.new assert update.updated is False - def test_update_to_none(self): + def test_update_to_none(self) -> None: """Test StateUpdate where new value is None (clearing a value).""" update = StateUpdate( key="qubit_0", @@ -358,7 +373,7 @@ def test_update_to_none(self): assert update.old == 0.05 assert update.new is None - def test_update_from_none(self): + def test_update_from_none(self) -> None: """Test StateUpdate where old value is None (setting initial value).""" update = StateUpdate( key="qubit_0", @@ -371,7 +386,7 @@ def test_update_from_none(self): assert update.old is None assert update.new == 0.1 - def test_complex_number_values(self): + def test_complex_number_values(self) -> None: """Test StateUpdate with complex number values.""" update = StateUpdate( key="mixer_0", @@ -384,7 +399,7 @@ def test_complex_number_values(self): assert isinstance(update.old, complex) assert isinstance(update.new, complex) - def test_very_long_key(self): + def test_very_long_key(self) -> None: """Test StateUpdate with very long nested key path.""" long_key = ( "system.rack_1.chassis_2.module_3.channel_4.qubit_5.subsystem_6" diff --git a/tests/unit/test_models_last_run.py b/tests/unit/test_models_last_run.py index 6f8156e..ffc9ff9 100644 --- a/tests/unit/test_models_last_run.py +++ b/tests/unit/test_models_last_run.py @@ -5,6 +5,9 @@ workflows, including status, timing, results, and errors. """ +from __future__ import annotations + + from datetime import datetime, timedelta, timezone from unittest.mock import Mock @@ -14,12 +17,13 @@ from qualibrate_runner.core.models.common import RunError, StateUpdate from qualibrate_runner.core.models.enums import RunnableType, RunStatusEnum from qualibrate_runner.core.models.last_run import LastRun +from typing import Any class TestLastRunCreation: """Tests for creating LastRun instances.""" - def test_create_running_node(self, aware_datetime): + def test_create_running_node(self, aware_datetime: Any) -> None: """Test creating LastRun for a running node.""" last_run = LastRun( status=RunStatusEnum.RUNNING, @@ -27,6 +31,7 @@ def test_create_running_node(self, aware_datetime): name="test_node", idx=-1, runnable_type=RunnableType.NODE, + passed_parameters={}, ) assert last_run.status == RunStatusEnum.RUNNING @@ -39,7 +44,9 @@ def test_create_running_node(self, aware_datetime): assert last_run.run_result is None assert last_run.error is None - def test_create_finished_node(self, aware_datetime, later_datetime): + def test_create_finished_node( + self, aware_datetime: Any, later_datetime: Any + ) -> None: """Test creating LastRun for a finished node.""" # Use model_construct to bypass validation for mock run_result last_run = LastRun.model_construct( @@ -50,6 +57,7 @@ def test_create_finished_node(self, aware_datetime, later_datetime): idx=42, runnable_type=RunnableType.NODE, run_result=Mock(success=True), + passed_parameters={}, ) assert last_run.status == RunStatusEnum.FINISHED @@ -59,8 +67,8 @@ def test_create_finished_node(self, aware_datetime, later_datetime): assert last_run.error is None def test_create_error_node( - self, aware_datetime, later_datetime, sample_run_error - ): + self, aware_datetime: Any, later_datetime: Any, sample_run_error: Any + ) -> None: """Test creating LastRun for a failed node.""" last_run = LastRun( status=RunStatusEnum.ERROR, @@ -70,6 +78,7 @@ def test_create_error_node( idx=-1, runnable_type=RunnableType.NODE, error=sample_run_error, + passed_parameters={}, ) assert last_run.status == RunStatusEnum.ERROR @@ -77,7 +86,7 @@ def test_create_error_node( assert last_run.error.error_class == "ValueError" assert last_run.run_result is None - def test_create_running_workflow(self, aware_datetime): + def test_create_running_workflow(self, aware_datetime: Any) -> None: """Test creating LastRun for a running workflow.""" last_run = LastRun( status=RunStatusEnum.RUNNING, @@ -85,12 +94,13 @@ def test_create_running_workflow(self, aware_datetime): name="test_workflow", idx=-1, runnable_type=RunnableType.GRAPH, + passed_parameters={}, ) assert last_run.runnable_type == RunnableType.GRAPH assert last_run.name == "test_workflow" - def test_create_with_passed_parameters(self, aware_datetime): + def test_create_with_passed_parameters(self, aware_datetime: Any) -> None: """Test creating LastRun with passed parameters.""" params = { "amplitude": 0.5, @@ -110,7 +120,7 @@ def test_create_with_passed_parameters(self, aware_datetime): assert last_run.passed_parameters == params assert last_run.passed_parameters["amplitude"] == 0.5 - def test_create_with_state_updates(self, aware_datetime): + def test_create_with_state_updates(self, aware_datetime: Any) -> None: """Test creating LastRun with state updates.""" state_updates = { "qubit_0.frequency": StateUpdate( @@ -130,6 +140,7 @@ def test_create_with_state_updates(self, aware_datetime): idx=42, runnable_type=RunnableType.NODE, state_updates=state_updates, + passed_parameters={}, ) assert len(last_run.state_updates) == 1 @@ -139,7 +150,9 @@ def test_create_with_state_updates(self, aware_datetime): class TestLastRunDuration: """Tests for the run_duration computed field.""" - def test_duration_for_completed_run(self, aware_datetime, later_datetime): + def test_duration_for_completed_run( + self, aware_datetime: Any, later_datetime: Any + ) -> None: """Test run_duration calculation for completed run.""" # later_datetime is 5.5 seconds after aware_datetime last_run = LastRun( @@ -149,12 +162,13 @@ def test_duration_for_completed_run(self, aware_datetime, later_datetime): name="test_node", idx=42, runnable_type=RunnableType.NODE, + passed_parameters={}, ) # Should be 5.5 seconds assert last_run.run_duration == 5.5 - def test_duration_for_running_execution(self): + def test_duration_for_running_execution(self) -> None: """Test run_duration for still-running execution (live duration).""" # Started a moment ago started = datetime.now(timezone.utc) - timedelta(milliseconds=100) @@ -165,12 +179,13 @@ def test_duration_for_running_execution(self): name="test_node", idx=-1, runnable_type=RunnableType.NODE, + passed_parameters={}, ) # Duration should be positive and small (less than 1 second) assert 0 < last_run.run_duration < 1.0 - def test_duration_for_very_short_run(self, aware_datetime): + def test_duration_for_very_short_run(self, aware_datetime: Any) -> None: """Test duration for very short execution (milliseconds).""" completed = aware_datetime + timedelta(milliseconds=150) @@ -181,11 +196,12 @@ def test_duration_for_very_short_run(self, aware_datetime): name="test_node", idx=42, runnable_type=RunnableType.NODE, + passed_parameters={}, ) assert last_run.run_duration == 0.15 - def test_duration_for_long_run(self, aware_datetime): + def test_duration_for_long_run(self, aware_datetime: Any) -> None: """Test duration for long execution (minutes).""" # 5 minutes and 30 seconds completed = aware_datetime + timedelta(minutes=5, seconds=30) @@ -197,11 +213,12 @@ def test_duration_for_long_run(self, aware_datetime): name="test_node", idx=42, runnable_type=RunnableType.NODE, + passed_parameters={}, ) assert last_run.run_duration == 330.0 - def test_duration_recalculated_on_access(self): + def test_duration_recalculated_on_access(self) -> None: """Test that duration is recalculated each time for running jobs.""" import time @@ -214,6 +231,7 @@ def test_duration_recalculated_on_access(self): name="test_node", idx=-1, runnable_type=RunnableType.NODE, + passed_parameters={}, ) # First access @@ -231,7 +249,7 @@ def test_duration_recalculated_on_access(self): class TestLastRunSerialization: """Tests for LastRun serialization and deserialization.""" - def test_serialize_running_node(self, sample_last_run_running): + def test_serialize_running_node(self, sample_last_run_running: Any) -> None: """Test serializing a running node to dict.""" data = sample_last_run_running.model_dump() @@ -243,7 +261,9 @@ def test_serialize_running_node(self, sample_last_run_running): assert data["run_result"] is None assert data["error"] is None - def test_serialize_finished_node(self, sample_last_run_finished): + def test_serialize_finished_node( + self, sample_last_run_finished: Any + ) -> None: """Test serializing a finished node to dict.""" data = sample_last_run_finished.model_dump() @@ -253,7 +273,7 @@ def test_serialize_finished_node(self, sample_last_run_finished): assert "run_result" in data assert "amplitude" in data["passed_parameters"] - def test_serialize_error_node(self, sample_last_run_error): + def test_serialize_error_node(self, sample_last_run_error: Any) -> None: """Test serializing a failed node to dict.""" data = sample_last_run_error.model_dump() @@ -262,7 +282,7 @@ def test_serialize_error_node(self, sample_last_run_error): assert data["error"]["error_class"] == "ValueError" assert data["error"]["message"] == "Invalid data" - def test_deserialize_from_dict(self, aware_datetime): + def test_deserialize_from_dict(self, aware_datetime: Any) -> None: """Test deserializing LastRun from dict.""" data = { "status": RunStatusEnum.RUNNING, @@ -286,7 +306,7 @@ def test_deserialize_from_dict(self, aware_datetime): class TestLastRunStatusTransitions: """Tests for different status values and transitions.""" - def test_all_status_values(self, aware_datetime): + def test_all_status_values(self, aware_datetime: Any) -> None: """Test that all status enum values can be used.""" for status in RunStatusEnum: last_run = LastRun( @@ -298,10 +318,11 @@ def test_all_status_values(self, aware_datetime): name="test_node", idx=-1, runnable_type=RunnableType.NODE, + passed_parameters={}, ) assert last_run.status == status - def test_running_to_finished_transition(self, aware_datetime): + def test_running_to_finished_transition(self, aware_datetime: Any) -> None: """Test updating from RUNNING to FINISHED status.""" # Initial RUNNING state last_run = LastRun( @@ -310,6 +331,7 @@ def test_running_to_finished_transition(self, aware_datetime): name="test_node", idx=-1, runnable_type=RunnableType.NODE, + passed_parameters={}, ) # Simulate update to FINISHED (use model_construct for mock run_result) @@ -322,6 +344,7 @@ def test_running_to_finished_transition(self, aware_datetime): idx=42, runnable_type=last_run.runnable_type, run_result=Mock(success=True), + passed_parameters={}, ) assert updated_run.status == RunStatusEnum.FINISHED @@ -329,8 +352,8 @@ def test_running_to_finished_transition(self, aware_datetime): assert updated_run.idx == 42 def test_running_to_error_transition( - self, aware_datetime, sample_run_error - ): + self, aware_datetime: Any, sample_run_error: Any + ) -> None: """Test updating from RUNNING to ERROR status.""" # Initial RUNNING state last_run = LastRun( @@ -339,6 +362,7 @@ def test_running_to_error_transition( name="test_node", idx=-1, runnable_type=RunnableType.NODE, + passed_parameters={}, ) # Simulate update to ERROR @@ -351,6 +375,7 @@ def test_running_to_error_transition( idx=-1, runnable_type=last_run.runnable_type, error=sample_run_error, + passed_parameters={}, ) assert updated_run.status == RunStatusEnum.ERROR @@ -361,7 +386,9 @@ def test_running_to_error_transition( class TestLastRunEdgeCases: """Tests for edge cases and special scenarios.""" - def test_idx_negative_one_for_no_snapshot(self, aware_datetime): + def test_idx_negative_one_for_no_snapshot( + self, aware_datetime: Any + ) -> None: """Test that idx=-1 indicates no snapshot was created.""" last_run = LastRun( status=RunStatusEnum.RUNNING, @@ -369,11 +396,12 @@ def test_idx_negative_one_for_no_snapshot(self, aware_datetime): name="test_node", idx=-1, runnable_type=RunnableType.NODE, + passed_parameters={}, ) assert last_run.idx == -1 - def test_empty_passed_parameters(self, aware_datetime): + def test_empty_passed_parameters(self, aware_datetime: Any) -> None: """Test with empty passed_parameters dict.""" last_run = LastRun( status=RunStatusEnum.RUNNING, @@ -387,7 +415,7 @@ def test_empty_passed_parameters(self, aware_datetime): assert last_run.passed_parameters == {} assert len(last_run.passed_parameters) == 0 - def test_empty_state_updates(self, aware_datetime): + def test_empty_state_updates(self, aware_datetime: Any) -> None: """Test with empty state_updates dict.""" last_run = LastRun( status=RunStatusEnum.FINISHED, @@ -397,12 +425,13 @@ def test_empty_state_updates(self, aware_datetime): idx=42, runnable_type=RunnableType.NODE, state_updates={}, + passed_parameters={}, ) assert last_run.state_updates == {} assert len(last_run.state_updates) == 0 - def test_node_vs_graph_runnable_types(self, aware_datetime): + def test_node_vs_graph_runnable_types(self, aware_datetime: Any) -> None: """Test both NODE and GRAPH runnable types.""" node_run = LastRun( status=RunStatusEnum.RUNNING, @@ -410,6 +439,7 @@ def test_node_vs_graph_runnable_types(self, aware_datetime): name="test_node", idx=-1, runnable_type=RunnableType.NODE, + passed_parameters={}, ) graph_run = LastRun( @@ -418,12 +448,13 @@ def test_node_vs_graph_runnable_types(self, aware_datetime): name="test_workflow", idx=-1, runnable_type=RunnableType.GRAPH, + passed_parameters={}, ) assert node_run.runnable_type == RunnableType.NODE assert graph_run.runnable_type == RunnableType.GRAPH - def test_very_long_node_name(self, aware_datetime): + def test_very_long_node_name(self, aware_datetime: Any) -> None: """Test with very long node name.""" long_name = "very_long_node_name_" * 20 @@ -433,12 +464,13 @@ def test_very_long_node_name(self, aware_datetime): name=long_name, idx=-1, runnable_type=RunnableType.NODE, + passed_parameters={}, ) assert last_run.name == long_name assert len(last_run.name) > 300 - def test_many_state_updates(self, aware_datetime): + def test_many_state_updates(self, aware_datetime: Any) -> None: """Test with many state updates.""" state_updates = { f"qubit_{i}.frequency": StateUpdate( @@ -459,11 +491,12 @@ def test_many_state_updates(self, aware_datetime): idx=42, runnable_type=RunnableType.NODE, state_updates=state_updates, + passed_parameters={}, ) assert len(last_run.state_updates) == 50 - def test_complex_passed_parameters(self, aware_datetime): + def test_complex_passed_parameters(self, aware_datetime: Any) -> None: """Test with complex nested passed_parameters.""" params = { "basic": 42, diff --git a/tests/unit/test_run_job.py b/tests/unit/test_run_job.py index 0d58781..09355d5 100644 --- a/tests/unit/test_run_job.py +++ b/tests/unit/test_run_job.py @@ -2,6 +2,10 @@ Tests for run_job.py orchestration layer (non-interactive mode only). """ +from __future__ import annotations + + +from typing import Any from unittest.mock import Mock, patch import pytest @@ -21,7 +25,9 @@ class TestValidateInputParameters: """Tests for validate_input_parameters function.""" - def test_valid_parameters_pass_validation(self, sample_parameters_class): + def test_valid_parameters_pass_validation( + self, sample_parameters_class: Any + ) -> None: """Test that valid parameters pass validation.""" params = {"amplitude": 0.5, "frequency": 5.0e9, "num_averages": 100} @@ -31,7 +37,9 @@ def test_valid_parameters_pass_validation(self, sample_parameters_class): assert result.frequency == 5.0e9 assert result.num_averages == 100 - def test_valid_parameters_with_defaults(self, sample_parameters_class): + def test_valid_parameters_with_defaults( + self, sample_parameters_class: Any + ) -> None: """Test that validation works with default values.""" params = {"amplitude": 0.5, "frequency": 5.0e9} @@ -42,8 +50,8 @@ def test_valid_parameters_with_defaults(self, sample_parameters_class): assert result.num_averages == 100 # Default value def test_invalid_parameter_raises_http_exception( - self, sample_parameters_class - ): + self, sample_parameters_class: Any + ) -> None: """Test that invalid parameters raise HTTPException with 422 status.""" params = {"amplitude": 1.5, "frequency": 5.0e9} # amplitude > 1.0 @@ -54,8 +62,8 @@ def test_invalid_parameter_raises_http_exception( assert exc_info.value.detail is not None def test_missing_required_field_raises_http_exception( - self, sample_parameters_class - ): + self, sample_parameters_class: Any + ) -> None: """Test that missing required field raises HTTPException.""" params = {"amplitude": 0.5} # Missing required 'frequency' @@ -67,7 +75,7 @@ def test_missing_required_field_raises_http_exception( errors = exc_info.value.detail assert any("frequency" in str(error).lower() for error in errors) - def test_type_coercion_works(self, sample_parameters_class): + def test_type_coercion_works(self, sample_parameters_class: Any) -> None: """Test that Pydantic type coercion works as expected.""" params = { "amplitude": "0.5", # String that can be coerced to float @@ -86,7 +94,9 @@ class TestGetActiveLibraryOrError: """Tests for get_active_library_or_error function.""" @patch("qualibrate_runner.core.run_job.QualibrationLibrary") - def test_returns_library_when_exists(self, mock_lib_class, mock_library): + def test_returns_library_when_exists( + self, mock_lib_class: Any, mock_library: Any + ) -> None: """Test that function returns library when one exists.""" mock_lib_class.get_active_library.return_value = mock_library @@ -96,7 +106,9 @@ def test_returns_library_when_exists(self, mock_lib_class, mock_library): assert result is mock_library @patch("qualibrate_runner.core.run_job.QualibrationLibrary") - def test_raises_exception_when_no_library(self, mock_lib_class): + def test_raises_exception_when_no_library( + self, mock_lib_class: Any + ) -> None: """Test that function raises exception when no library exists.""" mock_lib_class.get_active_library.side_effect = RuntimeError( "No active library" @@ -111,7 +123,9 @@ def test_raises_exception_when_no_library(self, mock_lib_class): class TestRunNodeHappyPath: """Tests for run_node function - happy path scenarios.""" - def test_sets_run_item_to_node(self, mock_node, fresh_state): + def test_sets_run_item_to_node( + self, mock_node: Any, fresh_state: Any + ) -> None: """Test that state.run_item is set to the node.""" mock_node.run = Mock(return_value=None) @@ -119,12 +133,14 @@ def test_sets_run_item_to_node(self, mock_node, fresh_state): assert fresh_state.run_item is mock_node - def test_creates_last_run_with_running_status(self, mock_node, fresh_state): + def test_creates_last_run_with_running_status( + self, mock_node: Any, fresh_state: Any + ) -> None: """Test that initial LastRun has RUNNING status.""" # Track state during execution last_run_during_execution = None - def capture_state(*args, **kwargs): + def capture_state(*args: Any, **kwargs: Any) -> None: nonlocal last_run_during_execution last_run_during_execution = fresh_state.last_run @@ -136,7 +152,9 @@ def capture_state(*args, **kwargs): assert last_run_during_execution.status == RunStatusEnum.RUNNING assert last_run_during_execution.name == "test_node" - def test_updates_state_with_finished_status(self, mock_node, fresh_state): + def test_updates_state_with_finished_status( + self, mock_node: Any, fresh_state: Any + ) -> None: """Test that final state has FINISHED status on success.""" mock_node.run = Mock(return_value=None) @@ -145,7 +163,9 @@ def test_updates_state_with_finished_status(self, mock_node, fresh_state): assert fresh_state.last_run.status == RunStatusEnum.FINISHED assert fresh_state.last_run.error is None - def test_captures_snapshot_idx(self, mock_node, fresh_state): + def test_captures_snapshot_idx( + self, mock_node: Any, fresh_state: Any + ) -> None: """Test that snapshot_idx is captured from node.""" mock_node.run = Mock(return_value=None) mock_node.snapshot_idx = 42 @@ -154,7 +174,9 @@ def test_captures_snapshot_idx(self, mock_node, fresh_state): assert fresh_state.last_run.idx == 42 - def test_captures_run_summary(self, mock_node, fresh_state): + def test_captures_run_summary( + self, mock_node: Any, fresh_state: Any + ) -> None: """Test that run_summary is captured from node.""" mock_node.run = Mock(return_value=None) # run_summary is None by default in fixture @@ -165,8 +187,8 @@ def test_captures_run_summary(self, mock_node, fresh_state): assert fresh_state.last_run.run_result is None def test_captures_state_updates( - self, mock_node, fresh_state, sample_state_update - ): + self, mock_node: Any, fresh_state: Any, sample_state_update: Any + ) -> None: """Test that state_updates are captured from node.""" from qualibrate_runner.core.models.common import StateUpdate @@ -178,7 +200,9 @@ def test_captures_state_updates( assert fresh_state.last_run.state_updates == state_updates - def test_sets_completed_at_timestamp(self, mock_node, fresh_state): + def test_sets_completed_at_timestamp( + self, mock_node: Any, fresh_state: Any + ) -> None: """Test that completed_at is set after execution.""" mock_node.run = Mock(return_value=None) @@ -193,7 +217,9 @@ def test_sets_completed_at_timestamp(self, mock_node, fresh_state): class TestRunNodeErrorPath: """Tests for run_node function - error scenarios.""" - def test_captures_exception_in_state(self, mock_node, fresh_state): + def test_captures_exception_in_state( + self, mock_node: Any, fresh_state: Any + ) -> None: """Test that exceptions are captured in state.last_run.error.""" mock_node.run = Mock(side_effect=ValueError("Test error")) @@ -205,7 +231,7 @@ def test_captures_exception_in_state(self, mock_node, fresh_state): assert fresh_state.last_run.error.error_class == "ValueError" assert fresh_state.last_run.error.message == "Test error" - def test_captures_traceback(self, mock_node, fresh_state): + def test_captures_traceback(self, mock_node: Any, fresh_state: Any) -> None: """Test that full traceback is captured.""" mock_node.run = Mock(side_effect=RuntimeError("Node failed")) @@ -219,7 +245,9 @@ def test_captures_traceback(self, mock_node, fresh_state): for line in fresh_state.last_run.error.traceback ) - def test_re_raises_original_exception(self, mock_node, fresh_state): + def test_re_raises_original_exception( + self, mock_node: Any, fresh_state: Any + ) -> None: """Test that the original exception is re-raised.""" original_error = ValueError("Original error") mock_node.run = Mock(side_effect=original_error) @@ -230,7 +258,9 @@ def test_re_raises_original_exception(self, mock_node, fresh_state): # Should be the same exception object assert exc_info.value is original_error - def test_state_updated_even_on_error(self, mock_node, fresh_state): + def test_state_updated_even_on_error( + self, mock_node: Any, fresh_state: Any + ) -> None: """Test that state is updated in finally block even on error.""" mock_node.run = Mock(side_effect=RuntimeError("Error")) @@ -242,7 +272,9 @@ def test_state_updated_even_on_error(self, mock_node, fresh_state): assert fresh_state.last_run.completed_at is not None assert fresh_state.last_run.name == "test_node" - def test_snapshot_idx_negative_one_on_error(self, mock_node, fresh_state): + def test_snapshot_idx_negative_one_on_error( + self, mock_node: Any, fresh_state: Any + ) -> None: """Test that snapshot_idx remains -1 on error.""" mock_node.run = Mock(side_effect=ValueError("Error")) mock_node.snapshot_idx = 42 # Should not be used on error @@ -258,8 +290,12 @@ class TestRunWorkflowHappyPath: @patch("qualibrate_runner.core.run_job.get_active_library_or_error") def test_retrieves_fresh_workflow_from_library( - self, mock_get_library, mock_library, mock_workflow, fresh_state - ): + self, + mock_get_library: Any, + mock_library: Any, + mock_workflow: Any, + fresh_state: Any, + ) -> None: """Test that workflow is retrieved fresh from library.""" mock_get_library.return_value = mock_library @@ -288,8 +324,12 @@ def test_retrieves_fresh_workflow_from_library( @patch("qualibrate_runner.core.run_job.get_active_library_or_error") def test_creates_last_run_with_graph_type( - self, mock_get_library, mock_library, mock_workflow, fresh_state - ): + self, + mock_get_library: Any, + mock_library: Any, + mock_workflow: Any, + fresh_state: Any, + ) -> None: """Test that LastRun is created with GRAPH runnable_type.""" mock_get_library.return_value = mock_library @@ -303,7 +343,7 @@ def test_creates_last_run_with_graph_type( # Track state during execution last_run_during_execution = None - def capture_state(*args, **kwargs): + def capture_state(*args: Any, **kwargs: Any) -> None: nonlocal last_run_during_execution last_run_during_execution = fresh_state.last_run @@ -315,8 +355,12 @@ def capture_state(*args, **kwargs): @patch("qualibrate_runner.core.run_job.get_active_library_or_error") def test_validates_parameters_with_full_parameters_class( - self, mock_get_library, mock_library, mock_workflow, fresh_state - ): + self, + mock_get_library: Any, + mock_library: Any, + mock_workflow: Any, + fresh_state: Any, + ) -> None: """Test that parameters are validated using full_parameters_class.""" mock_get_library.return_value = mock_library @@ -338,8 +382,12 @@ def test_validates_parameters_with_full_parameters_class( @patch("qualibrate_runner.core.run_job.get_active_library_or_error") def test_splits_parameters_into_nodes_and_params( - self, mock_get_library, mock_library, mock_workflow, fresh_state - ): + self, + mock_get_library: Any, + mock_library: Any, + mock_workflow: Any, + fresh_state: Any, + ) -> None: """Test that parameters are split into nodes and parameters.""" mock_get_library.return_value = mock_library @@ -366,8 +414,12 @@ def test_splits_parameters_into_nodes_and_params( @patch("qualibrate_runner.core.run_job.get_active_library_or_error") def test_updates_state_with_finished_status( - self, mock_get_library, mock_library, mock_workflow, fresh_state - ): + self, + mock_get_library: Any, + mock_library: Any, + mock_workflow: Any, + fresh_state: Any, + ) -> None: """Test that final state has FINISHED status on success.""" mock_get_library.return_value = mock_library @@ -385,8 +437,12 @@ def test_updates_state_with_finished_status( @patch("qualibrate_runner.core.run_job.get_active_library_or_error") def test_captures_workflow_snapshot_idx( - self, mock_get_library, mock_library, mock_workflow, fresh_state - ): + self, + mock_get_library: Any, + mock_library: Any, + mock_workflow: Any, + fresh_state: Any, + ) -> None: """Test that snapshot_idx is captured from workflow.""" mock_get_library.return_value = mock_library @@ -408,8 +464,8 @@ class TestRunWorkflowErrorPath: @patch("qualibrate_runner.core.run_job.get_active_library_or_error") def test_library_not_found_raises_exception( - self, mock_get_library, mock_workflow, fresh_state - ): + self, mock_get_library: Any, mock_workflow: Any, fresh_state: Any + ) -> None: """Test that missing library raises exception.""" mock_get_library.side_effect = RuntimeError("No active library") @@ -420,8 +476,12 @@ def test_library_not_found_raises_exception( @patch("qualibrate_runner.core.run_job.get_active_library_or_error") def test_workflow_not_in_library_raises_exception( - self, mock_get_library, mock_library, mock_workflow, fresh_state - ): + self, + mock_get_library: Any, + mock_library: Any, + mock_workflow: Any, + fresh_state: Any, + ) -> None: """Test that missing workflow in library raises exception.""" mock_get_library.return_value = mock_library mock_library.graphs = {} # Empty, no workflows @@ -433,8 +493,12 @@ def test_workflow_not_in_library_raises_exception( @patch("qualibrate_runner.core.run_job.get_active_library_or_error") def test_parameter_validation_errors_raise_exception( - self, mock_get_library, mock_library, mock_workflow, fresh_state - ): + self, + mock_get_library: Any, + mock_library: Any, + mock_workflow: Any, + fresh_state: Any, + ) -> None: """Test that parameter validation errors are raised.""" mock_get_library.return_value = mock_library @@ -460,8 +524,12 @@ def test_parameter_validation_errors_raise_exception( @patch("qualibrate_runner.core.run_job.get_active_library_or_error") def test_runtime_errors_captured_and_reraised( - self, mock_get_library, mock_library, mock_workflow, fresh_state - ): + self, + mock_get_library: Any, + mock_library: Any, + mock_workflow: Any, + fresh_state: Any, + ) -> None: """Test that runtime errors during workflow.run are captured.""" mock_get_library.return_value = mock_library @@ -480,8 +548,12 @@ def test_runtime_errors_captured_and_reraised( @patch("qualibrate_runner.core.run_job.get_active_library_or_error") def test_state_updated_even_on_error( - self, mock_get_library, mock_library, mock_workflow, fresh_state - ): + self, + mock_get_library: Any, + mock_library: Any, + mock_workflow: Any, + fresh_state: Any, + ) -> None: """Test that state is updated in finally block even on error.""" mock_get_library.return_value = mock_library From a41f633118dfeeb3a366558c0d32a412a269e870 Mon Sep 17 00:00:00 2001 From: Jonatan Kutchinsky Date: Thu, 27 Nov 2025 10:25:27 +0100 Subject: [PATCH 18/26] Fixing remaining type problems --- tests/conftest.py | 2 +- .../fixtures/test_nodes/node_with_actions.py | 12 ++++++--- .../test_nodes/node_with_subroutine.py | 1 - .../test_nodes/test_node_can_raise_in_body.py | 1 - tests/integration/test_run_job_integration.py | 2 +- tests/unit/test_config_state.py | 5 ++-- tests/unit/test_models_common.py | 3 --- tests/unit/test_models_last_run.py | 20 ++++++-------- tests/unit/test_run_job.py | 26 +++++++++---------- 9 files changed, 33 insertions(+), 39 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index a4e717b..0431e5c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,7 +9,7 @@ from datetime import datetime, timezone from pathlib import Path -from typing import Any, Iterator +from typing import Any from unittest.mock import Mock import pytest diff --git a/tests/fixtures/test_nodes/node_with_actions.py b/tests/fixtures/test_nodes/node_with_actions.py index 2a29f3e..bc33fff 100644 --- a/tests/fixtures/test_nodes/node_with_actions.py +++ b/tests/fixtures/test_nodes/node_with_actions.py @@ -10,14 +10,18 @@ from typing import Any from unittest.mock import Mock + import numpy as np import xarray as xr from pydantic import Field - -from qualibrate import NodeParameters, QualibrationNode +from qualang_tools.results import ( # type: ignore[import-untyped] + progress_counter, +) from qualang_tools.units import unit # type: ignore[import-untyped] -from qualang_tools.results import progress_counter # type: ignore[import-untyped] -from qualibration_libs.data import XarrayDataFetcher # type: ignore[import-untyped] +from qualibrate import NodeParameters, QualibrationNode +from qualibration_libs.data import ( # type: ignore[import-untyped] + XarrayDataFetcher, +) class Parameters(NodeParameters): diff --git a/tests/fixtures/test_nodes/node_with_subroutine.py b/tests/fixtures/test_nodes/node_with_subroutine.py index 55d9a0c..433eaa8 100644 --- a/tests/fixtures/test_nodes/node_with_subroutine.py +++ b/tests/fixtures/test_nodes/node_with_subroutine.py @@ -8,7 +8,6 @@ from typing import Any from pydantic import Field - from qualibrate import NodeParameters, QualibrationNode diff --git a/tests/fixtures/test_nodes/test_node_can_raise_in_body.py b/tests/fixtures/test_nodes/test_node_can_raise_in_body.py index 22625e5..4e1a22f 100644 --- a/tests/fixtures/test_nodes/test_node_can_raise_in_body.py +++ b/tests/fixtures/test_nodes/test_node_can_raise_in_body.py @@ -8,7 +8,6 @@ """ from pydantic import Field - from qualibrate import NodeParameters, QualibrationNode diff --git a/tests/integration/test_run_job_integration.py b/tests/integration/test_run_job_integration.py index ee88715..35cefbb 100644 --- a/tests/integration/test_run_job_integration.py +++ b/tests/integration/test_run_job_integration.py @@ -13,12 +13,12 @@ from __future__ import annotations +from typing import Any import pytest from qualibrate_runner.config.models import RunStatusEnum from qualibrate_runner.core.run_job import run_node -from typing import Any class TestSimpleNodeExecution: diff --git a/tests/unit/test_config_state.py b/tests/unit/test_config_state.py index 0788bfd..52dfa7d 100644 --- a/tests/unit/test_config_state.py +++ b/tests/unit/test_config_state.py @@ -7,13 +7,13 @@ from __future__ import annotations +from typing import Any import pytest from qualibrate_runner.config.models import State from qualibrate_runner.core.models.enums import RunnableType, RunStatusEnum from qualibrate_runner.core.models.last_run import LastRun -from typing import Any class TestStateCreation: @@ -296,8 +296,9 @@ def __init__(self) -> None: self.name = "custom" custom_obj = CustomRunnable() - state = State.model_construct(run_item=custom_obj) + state = State.model_construct(run_item=custom_obj) # type: ignore[arg-type] + assert state.run_item is not None assert state.run_item.name == "custom" diff --git a/tests/unit/test_models_common.py b/tests/unit/test_models_common.py index ab9793b..c130260 100644 --- a/tests/unit/test_models_common.py +++ b/tests/unit/test_models_common.py @@ -7,11 +7,8 @@ from __future__ import annotations - from typing import Any -import pytest - from qualibrate_runner.core.models.common import RunError, StateUpdate diff --git a/tests/unit/test_models_last_run.py b/tests/unit/test_models_last_run.py index ffc9ff9..38a3e36 100644 --- a/tests/unit/test_models_last_run.py +++ b/tests/unit/test_models_last_run.py @@ -7,17 +7,13 @@ from __future__ import annotations - from datetime import datetime, timedelta, timezone +from typing import Any from unittest.mock import Mock -import pytest -from freezegun import freeze_time - -from qualibrate_runner.core.models.common import RunError, StateUpdate +from qualibrate_runner.core.models.common import StateUpdate from qualibrate_runner.core.models.enums import RunnableType, RunStatusEnum from qualibrate_runner.core.models.last_run import LastRun -from typing import Any class TestLastRunCreation: @@ -166,7 +162,7 @@ def test_duration_for_completed_run( ) # Should be 5.5 seconds - assert last_run.run_duration == 5.5 + assert last_run.run_duration == 5.5 # type: ignore[comparison-overlap] def test_duration_for_running_execution(self) -> None: """Test run_duration for still-running execution (live duration).""" @@ -183,7 +179,7 @@ def test_duration_for_running_execution(self) -> None: ) # Duration should be positive and small (less than 1 second) - assert 0 < last_run.run_duration < 1.0 + assert 0 < last_run.run_duration < 1.0 # type: ignore[operator] def test_duration_for_very_short_run(self, aware_datetime: Any) -> None: """Test duration for very short execution (milliseconds).""" @@ -199,7 +195,7 @@ def test_duration_for_very_short_run(self, aware_datetime: Any) -> None: passed_parameters={}, ) - assert last_run.run_duration == 0.15 + assert last_run.run_duration == 0.15 # type: ignore[comparison-overlap] def test_duration_for_long_run(self, aware_datetime: Any) -> None: """Test duration for long execution (minutes).""" @@ -216,7 +212,7 @@ def test_duration_for_long_run(self, aware_datetime: Any) -> None: passed_parameters={}, ) - assert last_run.run_duration == 330.0 + assert last_run.run_duration == 330.0 # type: ignore[comparison-overlap] def test_duration_recalculated_on_access(self) -> None: """Test that duration is recalculated each time for running jobs.""" @@ -236,14 +232,14 @@ def test_duration_recalculated_on_access(self) -> None: # First access duration1 = last_run.run_duration - assert duration1 > 0 + assert duration1 > 0 # type: ignore[operator] # Wait a bit time.sleep(0.1) # Second access should show increased duration duration2 = last_run.run_duration - assert duration2 > duration1 + assert duration2 > duration1 # type: ignore[operator] class TestLastRunSerialization: diff --git a/tests/unit/test_run_job.py b/tests/unit/test_run_job.py index 09355d5..04e2ddc 100644 --- a/tests/unit/test_run_job.py +++ b/tests/unit/test_run_job.py @@ -4,15 +4,13 @@ from __future__ import annotations - from typing import Any from unittest.mock import Mock, patch import pytest from fastapi import HTTPException -from pydantic import BaseModel, Field, ValidationError +from pydantic import ValidationError -from qualibrate_runner.config.models import State from qualibrate_runner.core.models.enums import RunnableType, RunStatusEnum from qualibrate_runner.core.run_job import ( get_active_library_or_error, @@ -33,9 +31,9 @@ def test_valid_parameters_pass_validation( result = validate_input_parameters(sample_parameters_class, params) - assert result.amplitude == 0.5 - assert result.frequency == 5.0e9 - assert result.num_averages == 100 + assert result.amplitude == 0.5 # type: ignore[attr-defined] + assert result.frequency == 5.0e9 # type: ignore[attr-defined] + assert result.num_averages == 100 # type: ignore[attr-defined] def test_valid_parameters_with_defaults( self, sample_parameters_class: Any @@ -45,9 +43,9 @@ def test_valid_parameters_with_defaults( result = validate_input_parameters(sample_parameters_class, params) - assert result.amplitude == 0.5 - assert result.frequency == 5.0e9 - assert result.num_averages == 100 # Default value + assert result.amplitude == 0.5 # type: ignore[attr-defined] + assert result.frequency == 5.0e9 # type: ignore[attr-defined] + assert result.num_averages == 100 # type: ignore[attr-defined] # Default value def test_invalid_parameter_raises_http_exception( self, sample_parameters_class: Any @@ -85,9 +83,9 @@ def test_type_coercion_works(self, sample_parameters_class: Any) -> None: result = validate_input_parameters(sample_parameters_class, params) - assert result.amplitude == 0.5 - assert result.frequency == 5.0e9 - assert result.num_averages == 50 + assert result.amplitude == 0.5 # type: ignore[attr-defined] + assert result.frequency == 5.0e9 # type: ignore[attr-defined] + assert result.num_averages == 50 # type: ignore[attr-defined] class TestGetActiveLibraryOrError: @@ -149,6 +147,7 @@ def capture_state(*args: Any, **kwargs: Any) -> None: run_node(mock_node, {}, fresh_state) # During execution, status should have been RUNNING + assert last_run_during_execution is not None assert last_run_during_execution.status == RunStatusEnum.RUNNING assert last_run_during_execution.name == "test_node" @@ -190,7 +189,6 @@ def test_captures_state_updates( self, mock_node: Any, fresh_state: Any, sample_state_update: Any ) -> None: """Test that state_updates are captured from node.""" - from qualibrate_runner.core.models.common import StateUpdate mock_node.run = Mock(return_value=None) state_updates = {"qubit_0_frequency": sample_state_update} @@ -351,6 +349,7 @@ def capture_state(*args: Any, **kwargs: Any) -> None: run_workflow(mock_workflow, {}, fresh_state) + assert last_run_during_execution is not None assert last_run_during_execution.runnable_type == RunnableType.GRAPH @patch("qualibrate_runner.core.run_job.get_active_library_or_error") @@ -510,7 +509,6 @@ def test_parameter_validation_errors_raise_exception( { "type": "missing", "loc": ("frequency",), - "msg": "Field required", "input": {}, } ], From 3ed653adbab0ab15c2a70ac3abe8b49484703812 Mon Sep 17 00:00:00 2001 From: Jonatan Kutchinsky Date: Thu, 27 Nov 2025 10:33:59 +0100 Subject: [PATCH 19/26] Fixing lint --- qualibrate_runner/core/models/common.py | 3 ++- qualibrate_runner/core/run_job.py | 9 +++++---- tests/fixtures/test_nodes/node_with_actions.py | 9 ++++++--- .../fixtures/test_nodes/node_with_subroutine.py | 2 +- .../test_nodes/test_node_can_raise_in_body.py | 8 +++++--- tests/integration/test_run_job_integration.py | 16 +++++++++------- 6 files changed, 28 insertions(+), 19 deletions(-) diff --git a/qualibrate_runner/core/models/common.py b/qualibrate_runner/core/models/common.py index 1569e81..1948863 100644 --- a/qualibrate_runner/core/models/common.py +++ b/qualibrate_runner/core/models/common.py @@ -45,7 +45,8 @@ class StateUpdate(BaseModel): """ Record of a single change to quantum machine state (QuAM). - During calibration execution, nodes may update the quantum machine state with: + During calibration execution, nodes may update the quantum machine + state with: - Audit trails (what changed and when) - Rollback capability (knowing old values) - Display in UI (showing calibration effects) diff --git a/qualibrate_runner/core/run_job.py b/qualibrate_runner/core/run_job.py index edb589a..495fcf0 100644 --- a/qualibrate_runner/core/run_job.py +++ b/qualibrate_runner/core/run_job.py @@ -136,7 +136,8 @@ def run_node( try: # Execute the node in interactive mode with provided parameters - # interactive=True enables that the user approves changes to variables in the frontend after execution + # interactive=True enables that the user approves changes to + # variables in the frontend after execution node.run(interactive=True, **passed_input_parameters) except Exception as ex: # Capture error details for state tracking @@ -192,9 +193,9 @@ def run_workflow( """ Execute a calibration workflow (DAG of QualibrationNodes). - This function orchestrates the execution of a workflow (also called a graph), - which is a directed acyclic graph (DAG) of interconnected QualibrationNodes. - The workflow execution follows these steps: + This function orchestrates the execution of a workflow (also called a + graph), which is a directed acyclic graph (DAG) of interconnected + QualibrationNodes. The workflow execution follows these steps: 1. Initialize state with RUNNING status 2. Get a fresh copy of the workflow from the active library 3. Validate and structure the input parameters diff --git a/tests/fixtures/test_nodes/node_with_actions.py b/tests/fixtures/test_nodes/node_with_actions.py index bc33fff..3d14909 100644 --- a/tests/fixtures/test_nodes/node_with_actions.py +++ b/tests/fixtures/test_nodes/node_with_actions.py @@ -47,7 +47,10 @@ class Parameters(NodeParameters): ) trigger_deep_error: bool = Field( default=False, - description="Whether to run the action that raises an error inside XarrayDataFetcher", + description=( + "Whether to run the action that raises an error inside " + "XarrayDataFetcher" + ), ) @@ -104,7 +107,7 @@ def execute_qua_program(node: Any) -> dict[str, Any]: ), } data_fetcher = XarrayDataFetcher(mock_job, sweep_axes) - for dataset in data_fetcher: + for _dataset in data_fetcher: progress_counter( data_fetcher[ "nonexistent_key" @@ -114,7 +117,7 @@ def execute_qua_program(node: Any) -> dict[str, Any]: ) node.log(mock_job.execution_report()) - return {"ds_raw": dataset} + return {"ds_raw": _dataset} @node.run_action(skip_if=not node.parameters.update_state) # type: ignore[misc] diff --git a/tests/fixtures/test_nodes/node_with_subroutine.py b/tests/fixtures/test_nodes/node_with_subroutine.py index 433eaa8..9bea728 100644 --- a/tests/fixtures/test_nodes/node_with_subroutine.py +++ b/tests/fixtures/test_nodes/node_with_subroutine.py @@ -44,7 +44,7 @@ def helper_function(amplitude: float, num_points: int) -> list[float]: # This line will raise if we access out of bounds # We use this to trigger an error in the subroutine - result = data[num_points] # IndexError: list index out of range + _ = data[num_points] # IndexError: list index out of range return data diff --git a/tests/fixtures/test_nodes/test_node_can_raise_in_body.py b/tests/fixtures/test_nodes/test_node_can_raise_in_body.py index 4e1a22f..1d4f19c 100644 --- a/tests/fixtures/test_nodes/test_node_can_raise_in_body.py +++ b/tests/fixtures/test_nodes/test_node_can_raise_in_body.py @@ -1,10 +1,12 @@ """ Simple test node without any actions. -This node provides basic functionality for testing node execution without the complexity of the action system. +This node provides basic functionality for testing node execution without +the complexity of the action system. -It can raise an exception in the main body, and can test error handling in the orchestration layer when the error -occurs in the node's main body, before any actions are executed. +It can raise an exception in the main body, and can test error handling in +the orchestration layer when the error occurs in the node's main body, +before any actions are executed. """ from pydantic import Field diff --git a/tests/integration/test_run_job_integration.py b/tests/integration/test_run_job_integration.py index 35cefbb..7ad7f92 100644 --- a/tests/integration/test_run_job_integration.py +++ b/tests/integration/test_run_job_integration.py @@ -310,7 +310,7 @@ class TestErrorMessaging: def test_error_details_for_action_failure( self, test_library: Any, fresh_state: Any ) -> None: - """Test error details when error occurs in action (inside library error).""" + """Test error details when error occurs in action.""" node = test_library.nodes["node_with_actions"] # Trigger error from inside XarrayDataFetcher @@ -335,13 +335,13 @@ def test_error_details_for_action_failure( assert "prepare_data" in error.details assert "process_data" in error.details - # 4. Details should contain simplified traceback (simple format, no code blocks) + # 4. Details should contain simplified traceback assert "Traceback:" in error.details # Should NOT contain framework files in simplified version assert "action_manager.py" not in error.details assert "action.py" not in error.details - # 5. Details should contain source snippet (simple format, no code blocks) + # 5. Details should contain source snippet assert "Source Code:" in error.details # 6. Full traceback should be preserved @@ -386,10 +386,10 @@ def test_error_details_for_body_failure( or "prepare_data" not in error.details ) - # 3. Details should contain source snippet from node file (simple format) + # 3. Details should contain source snippet from node file assert "Source Code:" in error.details - # 4. Details should contain simplified traceback (simple format) + # 4. Details should contain simplified traceback assert "Traceback:" in error.details # Body errors should not have action framework in simplified version assert "action_manager.py" not in error.details @@ -444,14 +444,16 @@ def test_error_details_for_action_with_subroutine( # 1. Headline should mention the action (not the subroutine) assert "process_with_subroutine" in error.details_headline - # 2. Simplified traceback should show BOTH the action AND the subroutine (simple format) + # 2. Simplified traceback should show BOTH the action AND the + # subroutine assert "Traceback:" in error.details # Should include the action function assert "process_with_subroutine" in error.details # Should include the subroutine assert "helper_function" in error.details - # 3. Source snippet should show the subroutine (where error occurred, simple format) + # 3. Source snippet should show the subroutine (where error + # occurred) assert "Source Code:" in error.details # The error occurs at: result = data[num_points] # So the snippet should show this line From a036f0cc66342df58d2ce6c8d7bead0ac692b8ef Mon Sep 17 00:00:00 2001 From: Jonatan Kutchinsky Date: Thu, 27 Nov 2025 10:38:58 +0100 Subject: [PATCH 20/26] Ensuring that github actions work for linting, by using qualibrate-core from githib instead of from pip --- poetry.lock | 260 +++++++++++++++++++++++++------------------------ pyproject.toml | 1 + 2 files changed, 133 insertions(+), 128 deletions(-) diff --git a/poetry.lock b/poetry.lock index 23acc32..f94a187 100644 --- a/poetry.lock +++ b/poetry.lock @@ -6,7 +6,7 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "dev", "test"] 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"}, @@ -18,7 +18,7 @@ version = "4.11.0" description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc"}, {file = "anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4"}, @@ -39,7 +39,7 @@ version = "3.6.2" description = "Bash tab completion for argparse" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "argcomplete-3.6.2-py3-none-any.whl", hash = "sha256:65b3133a29ad53fb42c48cf5114752c7ab66c1c38544fdf6460f450c09b42591"}, {file = "argcomplete-3.6.2.tar.gz", hash = "sha256:d0519b1bc867f5f4f4713c41ad0aba73a4a5f007449716b16f385f2166dc6adf"}, @@ -54,7 +54,7 @@ version = "1.2.0" description = "Backport of CPython tarfile module" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "dev", "test"] markers = "python_version < \"3.12\"" files = [ {file = "backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34"}, @@ -71,7 +71,7 @@ version = "2.0.0b7" description = "A better Protobuf / gRPC generator & library" optional = false python-versions = "<4.0,>=3.7" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "betterproto-2.0.0b7-py3-none-any.whl", hash = "sha256:401ab8055e2f814e77b9c88a74d0e1ae3d1e8a969cced6aeb1b59f71ad63fbd2"}, {file = "betterproto-2.0.0b7.tar.gz", hash = "sha256:1b1458ca5278d519bcd62556a4c236f998a91d503f0f71c67b0b954747052af2"}, @@ -92,7 +92,7 @@ version = "25.9.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "black-25.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce41ed2614b706fd55fd0b4a6909d06b5bab344ffbfadc6ef34ae50adba3d4f7"}, {file = "black-25.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ab0ce111ef026790e9b13bd216fa7bc48edd934ffc4cbf78808b235793cbc92"}, @@ -140,7 +140,7 @@ version = "1.3.0" description = "A simple, correct Python build frontend" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "build-1.3.0-py3-none-any.whl", hash = "sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4"}, {file = "build-1.3.0.tar.gz", hash = "sha256:698edd0ea270bde950f53aed21f3a0135672206f3911e0176261a31e0e07b397"}, @@ -163,7 +163,7 @@ version = "0.14.3" description = "httplib2 caching for requests" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "cachecontrol-0.14.3-py3-none-any.whl", hash = "sha256:b35e44a3113f17d2a31c1e6b27b9de6d4405f84ae51baa8c1d3cc5b633010cae"}, {file = "cachecontrol-0.14.3.tar.gz", hash = "sha256:73e7efec4b06b20d9267b441c1f733664f989fb8688391b670ca812d70795d11"}, @@ -185,7 +185,7 @@ version = "2025.8.3" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, @@ -197,8 +197,7 @@ version = "2.0.0" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.9" -groups = ["main", "test"] -markers = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and (sys_platform == \"linux\" or sys_platform == \"darwin\")" +groups = ["main", "dev", "test"] files = [ {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, @@ -285,6 +284,7 @@ files = [ {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"}, {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"}, ] +markers = {main = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and (sys_platform == \"linux\" or sys_platform == \"darwin\")", dev = "(sys_platform == \"linux\" or sys_platform == \"darwin\") and (platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\")", test = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and (sys_platform == \"linux\" or sys_platform == \"darwin\")"} [package.dependencies] pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} @@ -295,7 +295,7 @@ version = "3.4.3" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, @@ -384,7 +384,7 @@ version = "2.1.0" description = "Cleo allows you to create beautiful and testable command-line interfaces." optional = false python-versions = ">=3.7,<4.0" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "cleo-2.1.0-py3-none-any.whl", hash = "sha256:4a31bd4dd45695a64ee3c4758f583f134267c2bc518d8ae9a29cf237d009b07e"}, {file = "cleo-2.1.0.tar.gz", hash = "sha256:0b2c880b5d13660a7ea651001fb4acb527696c01f15c9ee650f377aa543fd523"}, @@ -400,7 +400,7 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -415,12 +415,12 @@ 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" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {main = "platform_system == \"Windows\" or os_name == \"nt\"", test = "platform_system == \"Windows\" or os_name == \"nt\" or sys_platform == \"win32\""} +markers = {main = "platform_system == \"Windows\" or os_name == \"nt\"", dev = "os_name == \"nt\" or platform_system == \"Windows\"", test = "platform_system == \"Windows\" or os_name == \"nt\" or sys_platform == \"win32\""} [[package]] name = "contourpy" @@ -428,7 +428,7 @@ version = "1.3.2" description = "Python library for calculating contours of 2D quadrilateral grids" optional = false python-versions = ">=3.10" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934"}, {file = "contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989"}, @@ -505,7 +505,7 @@ version = "0.4.1" description = "Manage Python errors with ease" optional = false python-versions = ">=3.7,<4.0" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "crashtest-0.4.1-py3-none-any.whl", hash = "sha256:8d23eac5fa660409f57472e3851dab7ac18aba459a8d19cbbba86d3d5aecd2a5"}, {file = "crashtest-0.4.1.tar.gz", hash = "sha256:80d7b1f316ebfbd429f648076d6275c877ba30ba48979de4191714a75266f0ce"}, @@ -517,7 +517,7 @@ version = "46.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = "!=3.9.0,!=3.9.1,>=3.8" -groups = ["main", "test"] +groups = ["main", "dev", "test"] markers = "sys_platform == \"linux\"" files = [ {file = "cryptography-46.0.1-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:1cd6d50c1a8b79af1a6f703709d8973845f677c8e97b1268f5ff323d38ce8475"}, @@ -596,7 +596,7 @@ version = "0.12.1" description = "Composable style cycles" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, @@ -612,7 +612,7 @@ version = "2.43.0" description = "Collection of all Datadog Public endpoints" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "datadog_api_client-2.43.0-py3-none-any.whl", hash = "sha256:1d038b545263ad0b665f46d9d36dc0f0ed491770d9dcd68380be4685a9ae6cc3"}, {file = "datadog_api_client-2.43.0.tar.gz", hash = "sha256:f3aec5db9bcceb39f6315e505eea908da7133a19f8475ef996ef8be8e3c84855"}, @@ -636,7 +636,7 @@ version = "0.33.0" description = "Datamodel Code Generator" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "datamodel_code_generator-0.33.0-py3-none-any.whl", hash = "sha256:e229264aa612b2d5bb4901bcd6c520a799ae0d5c19262577a0f876eb48afaaa3"}, {file = "datamodel_code_generator-0.33.0.tar.gz", hash = "sha256:7635ef788201d69bd3e98ba88ce6afe479400dc2737fe9d5e21f87408f352c08"}, @@ -668,7 +668,7 @@ version = "4.48.2" description = "Dependency injection framework for Python" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "dependency_injector-4.48.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:54d0178be10f17b768afb5c0ed1c5c565abaa2d097b2bc5a529a31c580613df2"}, {file = "dependency_injector-4.48.2-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:12a15979fd534b728b3061c8aa52fd55adb77574758817daae9df8a1c2eb830b"}, @@ -718,7 +718,7 @@ version = "0.4.0" description = "Distribution utilities" optional = false python-versions = "*" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, @@ -730,7 +730,7 @@ version = "0.22.8" description = "Python Git Library" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "dulwich-0.22.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:546176d18b8cc0a492b0f23f07411e38686024cffa7e9d097ae20512a2e57127"}, {file = "dulwich-0.22.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d2434dd72b2ae09b653c9cfe6764a03c25cfbd99fbbb7c426f0478f6fb1100f"}, @@ -797,7 +797,7 @@ version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" -groups = ["main", "test"] +groups = ["main", "dev", "test"] markers = "python_version == \"3.10\"" files = [ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, @@ -838,7 +838,7 @@ version = "2.21.2" description = "Fastest Python implementation of JSON schema" optional = false python-versions = "*" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463"}, {file = "fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de"}, @@ -853,7 +853,7 @@ version = "3.19.1" description = "A platform independent file lock." optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d"}, {file = "filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58"}, @@ -865,7 +865,7 @@ version = "0.6.3" description = "A utility to find python versions on your system" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "findpython-0.6.3-py3-none-any.whl", hash = "sha256:a85bb589b559cdf1b87227cc233736eb7cad894b9e68021ee498850611939ebc"}, {file = "findpython-0.6.3.tar.gz", hash = "sha256:5863ea55556d8aadc693481a14ac4f3624952719efc1c5591abb0b4a9e965c94"}, @@ -880,7 +880,7 @@ version = "4.60.1" description = "Tools to manipulate font files" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "fonttools-4.60.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9a52f254ce051e196b8fe2af4634c2d2f02c981756c6464dc192f1b6050b4e28"}, {file = "fonttools-4.60.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c7420a2696a44650120cdd269a5d2e56a477e2bfa9d95e86229059beb1c19e15"}, @@ -976,7 +976,7 @@ version = "1.3.0" description = "GenSON is a powerful, user-friendly JSON Schema generator." optional = false python-versions = "*" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "genson-1.3.0-py3-none-any.whl", hash = "sha256:468feccd00274cc7e4c09e84b08704270ba8d95232aa280f65b986139cec67f7"}, {file = "genson-1.3.0.tar.gz", hash = "sha256:e02db9ac2e3fd29e65b5286f7135762e2cd8a986537c075b06fc5f1517308e37"}, @@ -988,7 +988,7 @@ version = "1.75.1" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "grpcio-1.75.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:1712b5890b22547dd29f3215c5788d8fc759ce6dd0b85a6ba6e2731f2d04c088"}, {file = "grpcio-1.75.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8d04e101bba4b55cea9954e4aa71c24153ba6182481b487ff376da28d4ba46cf"}, @@ -1065,7 +1065,7 @@ version = "0.4.5" description = "Pure-Python gRPC implementation for asyncio" optional = false python-versions = ">=3.7" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "grpclib-0.4.5.tar.gz", hash = "sha256:bf83ed55aca59497e168761d9555056efc54a8f865316c3b39becd007e9f9a73"}, ] @@ -1083,7 +1083,7 @@ version = "0.16.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, @@ -1095,7 +1095,7 @@ version = "4.3.0" description = "Pure-Python HTTP/2 protocol implementation" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd"}, {file = "h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1"}, @@ -1111,7 +1111,7 @@ version = "4.1.0" description = "Pure-Python HPACK header encoding" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496"}, {file = "hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca"}, @@ -1123,7 +1123,7 @@ version = "1.0.9" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, @@ -1145,7 +1145,7 @@ version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -1171,7 +1171,7 @@ version = "6.1.0" description = "Pure-Python HTTP/2 framing" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5"}, {file = "hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08"}, @@ -1183,7 +1183,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -1198,7 +1198,7 @@ version = "8.7.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] markers = "python_version < \"3.12\"" files = [ {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, @@ -1223,7 +1223,7 @@ version = "7.5.0" description = "Correctly generate plurals, singular nouns, ordinals, indefinite articles" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "inflect-7.5.0-py3-none-any.whl", hash = "sha256:2aea70e5e70c35d8350b8097396ec155ffd68def678c7ff97f51aa69c1d92344"}, {file = "inflect-7.5.0.tar.gz", hash = "sha256:faf19801c3742ed5a05a8ce388e0d8fe1a07f8d095c82201eb904f5d27ad571f"}, @@ -1259,7 +1259,7 @@ version = "0.7.0" description = "A library for installing Python wheels." optional = false python-versions = ">=3.7" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "installer-0.7.0-py3-none-any.whl", hash = "sha256:05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53"}, {file = "installer-0.7.0.tar.gz", hash = "sha256:a26d3e3116289bb08216e0d0f7d925fcef0b0194eedfa0c944bcaaa106c4b631"}, @@ -1271,7 +1271,7 @@ version = "6.0.1" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.9.0" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615"}, {file = "isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450"}, @@ -1287,7 +1287,7 @@ version = "3.4.0" description = "Utility functions for Python class constructs" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790"}, {file = "jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd"}, @@ -1306,7 +1306,7 @@ version = "6.0.1" description = "Useful decorators and context managers" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "jaraco.context-6.0.1-py3-none-any.whl", hash = "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4"}, {file = "jaraco_context-6.0.1.tar.gz", hash = "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3"}, @@ -1325,7 +1325,7 @@ version = "4.3.0" description = "Functools like those found in stdlib" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "jaraco_functools-4.3.0-py3-none-any.whl", hash = "sha256:227ff8ed6f7b8f62c56deff101545fa7543cf2c8e7b82a7c2116e672f29c26e8"}, {file = "jaraco_functools-4.3.0.tar.gz", hash = "sha256:cfd13ad0dd2c47a3600b439ef72d8615d482cedcff1632930d6f28924d92f294"}, @@ -1348,7 +1348,7 @@ version = "0.9.0" description = "Low-level, pure Python DBus protocol wrapper." optional = false python-versions = ">=3.7" -groups = ["main", "test"] +groups = ["main", "dev", "test"] markers = "sys_platform == \"linux\"" files = [ {file = "jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683"}, @@ -1365,7 +1365,7 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -1383,7 +1383,7 @@ version = "1.5.2" description = "Lightweight pipelining with Python functions" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241"}, {file = "joblib-1.5.2.tar.gz", hash = "sha256:3faa5c39054b2f03ca547da9b2f52fde67c06240c31853f306aea97f13647b55"}, @@ -1395,7 +1395,7 @@ version = "1.33" description = "Apply JSON-Patches (RFC 6902)" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, @@ -1410,7 +1410,7 @@ version = "3.0.0" description = "Identify specific nodes in a JSON document (RFC 6901)" optional = false python-versions = ">=3.7" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, @@ -1422,7 +1422,7 @@ version = "25.6.0" description = "Store and access your passwords safely." optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd"}, {file = "keyring-25.6.0.tar.gz", hash = "sha256:0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66"}, @@ -1452,7 +1452,7 @@ version = "1.4.9" description = "A fast implementation of the Cassowary constraint solver" optional = false python-versions = ">=3.10" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b4b4d74bda2b8ebf4da5bd42af11d02d04428b2c32846e4c2c93219df8a7987b"}, {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fb3b8132019ea572f4611d770991000d7f58127560c4889729248eb5852a102f"}, @@ -1563,7 +1563,7 @@ version = "3.0.3" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, @@ -1662,7 +1662,7 @@ version = "3.23.3" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "marshmallow-3.23.3-py3-none-any.whl", hash = "sha256:20c0f8c613f68bcb45b2a0d3282e2f172575560170bf220d67aafb42717910e4"}, {file = "marshmallow-3.23.3.tar.gz", hash = "sha256:d586c8685ebdb80bf754e1f96e3f305aaf30951f1fc69175b977453633467e76"}, @@ -1682,7 +1682,7 @@ version = "5.11" description = "An unofficial extension to Marshmallow to allow for polymorphic fields" optional = false python-versions = ">=3.5" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "marshmallow-polyfield-5.11.tar.gz", hash = "sha256:8075a9cc490da4af58b902b4a40a99882dd031adb7aaa96abd147a4fcd53415f"}, ] @@ -1696,7 +1696,7 @@ version = "3.10.6" description = "Python plotting package" optional = false python-versions = ">=3.10" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "matplotlib-3.10.6-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:bc7316c306d97463a9866b89d5cc217824e799fa0de346c8f68f4f3d27c8693d"}, {file = "matplotlib-3.10.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d00932b0d160ef03f59f9c0e16d1e3ac89646f7785165ce6ad40c842db16cc2e"}, @@ -1775,7 +1775,7 @@ version = "10.8.0" description = "More routines for operating on iterables, beyond itertools" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b"}, {file = "more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd"}, @@ -1787,7 +1787,7 @@ version = "1.1.1" description = "MessagePack serializer" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "msgpack-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:353b6fc0c36fde68b661a12949d7d49f8f51ff5fa019c1e47c87c4ff34b080ed"}, {file = "msgpack-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:79c408fcf76a958491b4e3b103d1c417044544b68e96d06432a189b43d1215c8"}, @@ -1856,7 +1856,7 @@ version = "6.6.4" description = "multidict implementation" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "multidict-6.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b8aa6f0bd8125ddd04a6593437bad6a7e70f300ff4180a531654aa2ab3f6d58f"}, {file = "multidict-6.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9e5853bbd7264baca42ffc53391b490d65fe62849bf2c690fa3f6273dbcd0cb"}, @@ -2052,7 +2052,7 @@ version = "3.4.2" description = "Python package for creating and manipulating graphs and networks" optional = false python-versions = ">=3.10" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f"}, {file = "networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1"}, @@ -2072,7 +2072,7 @@ version = "1.26.4" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, @@ -2118,7 +2118,7 @@ version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, @@ -2254,7 +2254,7 @@ version = "2025.9.18" description = "Installer for Python Build Standalone" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "pbs_installer-2025.9.18-py3-none-any.whl", hash = "sha256:8ef55d7675698747505c237015d14c81759bd66a0d4c8b20cec9a2dc96e8434c"}, {file = "pbs_installer-2025.9.18.tar.gz", hash = "sha256:c0a51a7c1e015723bd8396f02e15b5876e439f74b0f45bbac436b189f903219f"}, @@ -2275,7 +2275,7 @@ version = "11.3.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860"}, {file = "pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"}, @@ -2400,7 +2400,7 @@ version = "1.12.1.2" description = "Query metadata from sdists / bdists / installed packages." optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "pkginfo-1.12.1.2-py3-none-any.whl", hash = "sha256:c783ac885519cab2c34927ccfa6bf64b5a704d7c69afaea583dd9b7afe969343"}, {file = "pkginfo-1.12.1.2.tar.gz", hash = "sha256:5cd957824ac36f140260964eba3c6be6442a8359b8c48f4adf90210f33a04b7b"}, @@ -2415,7 +2415,7 @@ version = "4.4.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85"}, {file = "platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"}, @@ -2432,7 +2432,7 @@ version = "5.24.1" description = "An open-source, interactive data visualization library for Python" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "plotly-5.24.1-py3-none-any.whl", hash = "sha256:f67073a1e637eb0dc3e46324d9d51e2fe76e9727c892dde64ddf1e1b51f29089"}, {file = "plotly-5.24.1.tar.gz", hash = "sha256:dbc8ac8339d248a4bcc36e08a5659bacfe1b079390b8953533f4eb22169b4bae"}, @@ -2484,7 +2484,7 @@ version = "2.1.3" description = "Python dependency management and packaging made easy." optional = false python-versions = "<4.0,>=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "poetry-2.1.3-py3-none-any.whl", hash = "sha256:7054d3f97ccce7f31961ead16250407c4577bfe57e2037a190ae2913fc40a20c"}, {file = "poetry-2.1.3.tar.gz", hash = "sha256:f2c9bd6790b19475976d88ea4553bcc3533c0dc73f740edc4fffe9e2add50594"}, @@ -2520,7 +2520,7 @@ version = "2.1.3" description = "Poetry PEP 517 Build Backend" optional = false python-versions = "<4.0,>=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "poetry_core-2.1.3-py3-none-any.whl", hash = "sha256:2c704f05016698a54ca1d327f46ce2426d72eaca6ff614132c8477c292266771"}, {file = "poetry_core-2.1.3.tar.gz", hash = "sha256:0522a015477ed622c89aad56a477a57813cace0c8e7ff2a2906b7ef4a2e296a4"}, @@ -2532,7 +2532,7 @@ version = "6.32.1" description = "" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "protobuf-6.32.1-cp310-abi3-win32.whl", hash = "sha256:a8a32a84bc9f2aad712041b8b366190f71dde248926da517bde9e832e4412085"}, {file = "protobuf-6.32.1-cp310-abi3-win_amd64.whl", hash = "sha256:b00a7d8c25fa471f16bc8153d0e53d6c9e827f0953f3c09aaa4331c718cae5e1"}, @@ -2551,12 +2551,12 @@ version = "2.23" description = "C parser in Python" optional = false python-versions = ">=3.8" -groups = ["main", "test"] -markers = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and implementation_name != \"PyPy\" and (sys_platform == \"linux\" or sys_platform == \"darwin\")" +groups = ["main", "dev", "test"] files = [ {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"}, {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}, ] +markers = {main = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and implementation_name != \"PyPy\" and (sys_platform == \"linux\" or sys_platform == \"darwin\")", dev = "(sys_platform == \"linux\" or sys_platform == \"darwin\") and (platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and implementation_name != \"PyPy\"", test = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and implementation_name != \"PyPy\" and (sys_platform == \"linux\" or sys_platform == \"darwin\")"} [[package]] name = "pydantic" @@ -2564,7 +2564,7 @@ version = "2.11.9" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "pydantic-2.11.9-py3-none-any.whl", hash = "sha256:c42dd626f5cfc1c6950ce6205ea58c93efa406da65f479dcb4029d5934857da2"}, {file = "pydantic-2.11.9.tar.gz", hash = "sha256:6b8ffda597a14812a7975c90b82a8a2e777d9257aba3453f973acd3c032a18e2"}, @@ -2586,7 +2586,7 @@ version = "2.33.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, @@ -2698,7 +2698,7 @@ version = "2.11.0" description = "Settings management using Pydantic" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c"}, {file = "pydantic_settings-2.11.0.tar.gz", hash = "sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180"}, @@ -2737,7 +2737,7 @@ version = "3.2.5" description = "pyparsing - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e"}, {file = "pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6"}, @@ -2752,7 +2752,7 @@ version = "1.2.0" description = "Wrappers to call pyproject.toml-based build backend hooks." optional = false python-versions = ">=3.7" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913"}, {file = "pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8"}, @@ -2806,7 +2806,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -2821,7 +2821,7 @@ version = "1.1.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc"}, {file = "python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab"}, @@ -2836,7 +2836,7 @@ version = "3.3.0" description = "JSON Log Formatter for the Python Logging Package" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "python_json_logger-3.3.0-py3-none-any.whl", hash = "sha256:dd980fae8cffb24c13caf6e158d3d61c0d6d22342f932cb6e9deedab3d35eec7"}, {file = "python_json_logger-3.3.0.tar.gz", hash = "sha256:12b7e74b17775e7d565129296105bbe3910842d9d0eb083fc83a6a617aa8df84"}, @@ -2851,7 +2851,7 @@ version = "0.1.10" description = "A Fast, spec compliant Python 3.12+ tokenizer that runs on older Pythons." optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "pytokens-0.1.10-py3-none-any.whl", hash = "sha256:db7b72284e480e69fb085d9f251f66b3d2df8b7166059261258ff35f50fb711b"}, {file = "pytokens-0.1.10.tar.gz", hash = "sha256:c9a4bfa0be1d26aebce03e6884ba454e842f186a59ea43a6d3b25af58223c044"}, @@ -2878,7 +2878,7 @@ version = "0.2.3" description = "A (partial) reimplementation of pywin32 using ctypes/cffi" optional = false python-versions = ">=3.6" -groups = ["main", "test"] +groups = ["main", "dev", "test"] markers = "sys_platform == \"win32\"" files = [ {file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"}, @@ -2974,7 +2974,7 @@ version = "2.1.6" description = "SDK to control an Octave with QUA" optional = false python-versions = "<3.13,>=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "qm_octave-2.1.6-py3-none-any.whl", hash = "sha256:2e3df8085330ecf0eb1cc3dd661ebec7b3505959d0fabb3a2dc6175ec7c7cba3"}, {file = "qm_octave-2.1.6.tar.gz", hash = "sha256:8a16e6a8c76b060bc2856020a3f54f2e6d936ffc24e2b7963376ce990a8110be"}, @@ -2992,7 +2992,7 @@ version = "1.2.3" description = "QUA language SDK to control a Quantum Computer" optional = false python-versions = "<3.13,>=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "qm_qua-1.2.3-py3-none-any.whl", hash = "sha256:d719aa0f6ca8dc02e3f9941103942d2f260390ba1527857e08e164cd5e1e1df1"}, {file = "qm_qua-1.2.3.tar.gz", hash = "sha256:529cbb443c04f895cd9b2b663ac4617cb221dbdd490220d1de955a2a9bf1c9c5"}, @@ -3024,7 +3024,7 @@ version = "0.20.0" description = "The qualang_tools package includes various tools related to QUA programs in Python" optional = false python-versions = "<3.13,>=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "qualang_tools-0.20.0-py3-none-any.whl", hash = "sha256:0dc71d2c00eaa98da86b70e082cf0c59d9d61b1494d74bc5aee6b07b8f1bff9c"}, {file = "qualang_tools-0.20.0.tar.gz", hash = "sha256:23de4e0dc81387e45803afdf58c51f1a9c6f8b6ea650becda7d3b31904b0d342"}, @@ -3050,7 +3050,7 @@ version = "0.1.8" description = "Configuration for Qualibrate" optional = false python-versions = "<4.0,>=3.10" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "qualibrate_config-0.1.8-py3-none-any.whl", hash = "sha256:58969295ada9253540e18208387acad9097a4883051dd8ed72887e89b4f6d37f"}, {file = "qualibrate_config-0.1.8.tar.gz", hash = "sha256:7deccb0d160a0f1863ca43c87c5712d7a7e40a4c89fa03a8ed06d2ff552a1176"}, @@ -3069,24 +3069,28 @@ name = "qualibrate-core" version = "0.4.1" description = "" optional = false -python-versions = "<3.13,>=3.10" -groups = ["main"] -files = [ - {file = "qualibrate_core-0.4.1-py3-none-any.whl", hash = "sha256:c1124aee019e5774079705d5bd04b91e8e2e4911de26a9e16830256a1c13fd83"}, - {file = "qualibrate_core-0.4.1.tar.gz", hash = "sha256:969047b8ca0156d2f27f4a838cf1198b32732c51610abf39d426a8c8c2e5949f"}, -] +python-versions = "^3.10,<3.13" +groups = ["main", "dev"] +files = [] +develop = false [package.dependencies] -datamodel-code-generator = ">=0.33.0,<0.34.0" -jsonpatch = ">=1.33,<2.0" -jsonpointer = ">=3.0.0,<4.0.0" -matplotlib = ">=3.10.6,<4.0.0" -networkx = ">=3.4.2,<3.5.0" -pydantic = ">=2.9.2,<3.0.0" -python-json-logger = ">=3.2.1,<4.0.0" +datamodel-code-generator = "^0.33.0" +jsonpatch = "^1.33" +jsonpointer = "^3.0.0" +matplotlib = "^3.10.6" +networkx = "~3.4.2" +pydantic = "^2.9.2" +python-json-logger = "^3.2.1" qualang-tools = {version = ">=0.20.0", markers = "python_version >= \"3.10\" and python_version < \"3.13\""} -qualibrate-config = ">=0.1.7,<0.2.0" -types-networkx = ">=3.4.2.20250527,<3.5.0.0" +qualibrate-config = "^0.1.7,<0.2.0" +types-networkx = "~3.4.2.20250527" + +[package.source] +type = "git" +url = "https://github.com/qua-platform/qualibrate-core.git" +reference = "HEAD" +resolved_reference = "8fe5f724e0069876b5f390a1008671943659f3b1" [[package]] name = "quam" @@ -3140,7 +3144,7 @@ version = "3.14.1" description = "rapid fuzzy string matching" optional = false python-versions = ">=3.10" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "rapidfuzz-3.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:489440e4b5eea0d150a31076eb183bed0ec84f934df206c72ae4fc3424501758"}, {file = "rapidfuzz-3.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eff22cc938c3f74d194df03790a6c3325d213b28cf65cdefd6fdeae759b745d5"}, @@ -3245,7 +3249,7 @@ version = "2.32.5" description = "Python HTTP for Humans." optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, @@ -3267,7 +3271,7 @@ version = "1.0.0" description = "A utility belt for advanced users of python-requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, @@ -3311,7 +3315,7 @@ version = "1.7.2" description = "A set of python modules for machine learning and data mining" optional = false python-versions = ">=3.10" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "scikit_learn-1.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b33579c10a3081d076ab403df4a4190da4f4432d443521674637677dc91e61f"}, {file = "scikit_learn-1.7.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:36749fb62b3d961b1ce4fedf08fa57a1986cd409eff2d783bca5d4b9b5fce51c"}, @@ -3367,7 +3371,7 @@ version = "1.15.3" description = "Fundamental algorithms for scientific computing in Python" optional = false python-versions = ">=3.10" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c"}, {file = "scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253"}, @@ -3431,7 +3435,7 @@ version = "3.4.0" description = "Python bindings to FreeDesktop.org Secret Service API" optional = false python-versions = ">=3.10" -groups = ["main", "test"] +groups = ["main", "dev", "test"] markers = "sys_platform == \"linux\"" files = [ {file = "secretstorage-3.4.0-py3-none-any.whl", hash = "sha256:0e3b6265c2c63509fb7415717607e4b2c9ab767b7f344a57473b779ca13bd02e"}, @@ -3448,7 +3452,7 @@ version = "1.5.4" description = "Tool to Detect Surrounding Shell" optional = false python-versions = ">=3.7" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, @@ -3460,7 +3464,7 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -3472,7 +3476,7 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -3503,7 +3507,7 @@ version = "9.1.2" description = "Retry code until it succeeds" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138"}, {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"}, @@ -3519,7 +3523,7 @@ version = "3.6.0" description = "threadpoolctl" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb"}, {file = "threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e"}, @@ -3531,7 +3535,7 @@ version = "4.8.2" description = "TinyDB is a tiny, document oriented database optimized for your happiness :)" optional = false python-versions = "<4.0,>=3.8" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "tinydb-4.8.2-py3-none-any.whl", hash = "sha256:f97030ee5cbc91eeadd1d7af07ab0e48ceb04aa63d4a983adbaca4cba16e86c3"}, {file = "tinydb-4.8.2.tar.gz", hash = "sha256:f7dfc39b8d7fda7a1ca62a8dbb449ffd340a117c1206b68c50b1a481fb95181d"}, @@ -3578,7 +3582,7 @@ files = [ {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] -markers = {main = "python_version < \"3.12\"", dev = "python_version == \"3.10\"", test = "python_version == \"3.10\""} +markers = {main = "python_version < \"3.12\"", dev = "python_version < \"3.12\"", test = "python_version == \"3.10\""} [[package]] name = "tomli-w" @@ -3586,7 +3590,7 @@ version = "1.2.0" description = "A lil' TOML writer" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90"}, {file = "tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021"}, @@ -3598,7 +3602,7 @@ version = "0.13.3" description = "Style preserving TOML library" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0"}, {file = "tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1"}, @@ -3610,7 +3614,7 @@ version = "2025.9.11.17" description = "Canonical source for classifiers on PyPI (pypi.org)." optional = false python-versions = "*" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "trove_classifiers-2025.9.11.17-py3-none-any.whl", hash = "sha256:5d392f2d244deb1866556457d6f3516792124a23d1c3a463a2e8668a5d1c15dd"}, {file = "trove_classifiers-2025.9.11.17.tar.gz", hash = "sha256:931ca9841a5e9c9408bc2ae67b50d28acf85bef56219b56860876dd1f2d024dd"}, @@ -3622,7 +3626,7 @@ version = "4.4.4" description = "Run-time type checker for Python" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "typeguard-4.4.4-py3-none-any.whl", hash = "sha256:b5f562281b6bfa1f5492470464730ef001646128b180769880468bd84b68b09e"}, {file = "typeguard-4.4.4.tar.gz", hash = "sha256:3a7fd2dffb705d4d0efaed4306a704c89b9dee850b688f060a8b1615a79e5f74"}, @@ -3637,7 +3641,7 @@ version = "3.4.2.20250527" description = "Typing stubs for networkx" optional = false python-versions = ">=3.10" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "types_networkx-3.4.2.20250527-py3-none-any.whl", hash = "sha256:3716f750e61bf46ceba2a43474d7f9b61f306274a243cee6cc2aa9f4fb37ab41"}, {file = "types_networkx-3.4.2.20250527.tar.gz", hash = "sha256:484914f0dee6ce8d87bda494f641a9227e954d716d2f330bfcbf69c2431b646d"}, @@ -3664,7 +3668,7 @@ version = "0.4.1" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"}, {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"}, @@ -3691,7 +3695,7 @@ version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, @@ -3729,7 +3733,7 @@ version = "20.34.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "virtualenv-20.34.0-py3-none-any.whl", hash = "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026"}, {file = "virtualenv-20.34.0.tar.gz", hash = "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a"}, @@ -3856,7 +3860,7 @@ version = "1.2.0" description = "Python wrapper for extended filesystem attributes" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "dev", "test"] markers = "sys_platform == \"darwin\"" files = [ {file = "xattr-1.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3df4d8d91e2996c3c72a390ec82e8544acdcb6c7df67b954f1736ff37ea4293e"}, @@ -3943,7 +3947,7 @@ version = "3.23.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] markers = "python_version < \"3.12\"" files = [ {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, @@ -3964,7 +3968,7 @@ version = "0.25.0" description = "Zstandard bindings for Python" optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] files = [ {file = "zstandard-0.25.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e59fdc271772f6686e01e1b3b74537259800f57e24280be3f29c8a0deb1904dd"}, {file = "zstandard-0.25.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4d441506e9b372386a5271c64125f72d5df6d2a8e8a2a45a0ae09b03cb781ef7"}, @@ -4073,4 +4077,4 @@ cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and pyt [metadata] lock-version = "2.1" python-versions = "^3.10,<3.13" -content-hash = "ac8ecc77313a00c733a03b8e1f0d98302b24daecf699a2f87141394b5ce3b738" +content-hash = "72d3290c4ff4f0afea2b387d2cde3904d6f84d60cf591af5f9048e1581aa8193" diff --git a/pyproject.toml b/pyproject.toml index 183134a..ebc5f99 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ quam = "^0.4.2" mypy = "^1.18.2" ruff = "^0.13.1" poethepoet = "^0.37.0" +qualibrate-core = {git = "https://github.com/qua-platform/qualibrate-core.git"} [tool.poetry.group.test.dependencies] pytest = "^8.4.2" From 4f532a6f48f0915dc5868c0ab1d63b3f6059fd64 Mon Sep 17 00:00:00 2001 From: Jonatan Kutchinsky Date: Thu, 27 Nov 2025 10:51:53 +0100 Subject: [PATCH 21/26] Fixing final type errors --- pyproject.toml | 2 +- tests/fixtures/test_nodes/node_with_actions.py | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ebc5f99..778b0ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ warn_return_any = true plugins = ['pydantic.mypy'] [[tool.mypy.overrides]] -module = ["jsonpatch", "jsonpointer"] +module = ["jsonpatch", "jsonpointer", "qualibration_libs.*", "qualang_tools.*"] ignore_missing_imports = true [tool.ruff] diff --git a/tests/fixtures/test_nodes/node_with_actions.py b/tests/fixtures/test_nodes/node_with_actions.py index 3d14909..499c405 100644 --- a/tests/fixtures/test_nodes/node_with_actions.py +++ b/tests/fixtures/test_nodes/node_with_actions.py @@ -14,14 +14,10 @@ import numpy as np import xarray as xr from pydantic import Field -from qualang_tools.results import ( # type: ignore[import-untyped] - progress_counter, -) -from qualang_tools.units import unit # type: ignore[import-untyped] +from qualang_tools.results import progress_counter +from qualang_tools.units import unit from qualibrate import NodeParameters, QualibrationNode -from qualibration_libs.data import ( # type: ignore[import-untyped] - XarrayDataFetcher, -) +from qualibration_libs.data import XarrayDataFetcher class Parameters(NodeParameters): From 7834eb4ec47018cb286e8dbf8bc5e71855ef9dfc Mon Sep 17 00:00:00 2001 From: Jonatan Kutchinsky Date: Tue, 23 Dec 2025 15:16:33 +0100 Subject: [PATCH 22/26] Fixing test that broke with latest version of qualibration-libs --- .../fixtures/test_nodes/node_with_actions.py | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/tests/fixtures/test_nodes/node_with_actions.py b/tests/fixtures/test_nodes/node_with_actions.py index 499c405..8d4a72f 100644 --- a/tests/fixtures/test_nodes/node_with_actions.py +++ b/tests/fixtures/test_nodes/node_with_actions.py @@ -9,7 +9,7 @@ """ from typing import Any -from unittest.mock import Mock +from unittest.mock import Mock, patch import numpy as np import xarray as xr @@ -89,7 +89,12 @@ def process_data(node: Any) -> dict[str, Any]: @node.run_action(skip_if=not node.parameters.trigger_deep_error) # type: ignore[misc] def execute_qua_program(node: Any) -> dict[str, Any]: mock_job = Mock() - mock_job.result_handles.keys.return_value = [] + mock_job.result_handles.keys.return_value = ["I"] + + # Mock fetching_tool to return a result with is_processing method + mock_result = Mock() + mock_result.is_processing.return_value = False + mock_result.fetch_all.return_value = {"I": Mock()} # Code supposed to display the progress bar (won't work) u = unit(coerce_to_integer=True) @@ -102,15 +107,19 @@ def execute_qua_program(node: Any) -> dict[str, Any]: dfs, attrs={"long_name": "readout frequency", "units": "Hz"} ), } - data_fetcher = XarrayDataFetcher(mock_job, sweep_axes) - for _dataset in data_fetcher: - progress_counter( - data_fetcher[ - "nonexistent_key" - ], # This will raise KeyError from inside XarrayDataFetcher - node.parameters.num_shots, - start_time=data_fetcher.t_start, - ) + + with patch( + "qualibration_libs.data.fetcher.fetching_tool", return_value=mock_result + ): + data_fetcher = XarrayDataFetcher(mock_job, sweep_axes) + for _dataset in data_fetcher: + progress_counter( + data_fetcher[ + "nonexistent_key" + ], # This will raise KeyError from inside XarrayDataFetcher + node.parameters.num_shots, + start_time=data_fetcher.t_start, + ) node.log(mock_job.execution_report()) return {"ds_raw": _dataset} From e653b4f116d805c0a8556781809eafb7a4b3baf6 Mon Sep 17 00:00:00 2001 From: Jonatan Kutchinsky Date: Tue, 23 Dec 2025 17:16:51 +0100 Subject: [PATCH 23/26] Some fixes based on PR feedback. --- tests/conftest.py | 57 ++++++++++++------- .../fixtures/test_nodes/node_with_actions.py | 22 ++++--- .../test_nodes/node_with_subroutine.py | 10 ++-- 3 files changed, 58 insertions(+), 31 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 0431e5c..06e51aa 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,10 +9,11 @@ from datetime import datetime, timezone from pathlib import Path -from typing import Any -from unittest.mock import Mock +from typing import Any, cast +from unittest.mock import Mock, create_autospec import pytest +from qualibrate import QualibrationGraph, QualibrationLibrary, QualibrationNode from qualibrate_runner.config.models import State from qualibrate_runner.core.models.common import RunError, StateUpdate @@ -44,7 +45,7 @@ def sample_traceback() -> list[str]: @pytest.fixture -def sample_run_error(sample_traceback: Any) -> RunError: +def sample_run_error(sample_traceback: list[str]) -> RunError: """Provide a sample RunError instance.""" return RunError( error_class="ValueError", @@ -66,7 +67,7 @@ def sample_state_update() -> StateUpdate: @pytest.fixture -def sample_last_run_running(aware_datetime: Any) -> LastRun: +def sample_last_run_running(aware_datetime: datetime) -> LastRun: """Provide a LastRun instance with RUNNING status.""" return LastRun( status=RunStatusEnum.RUNNING, @@ -80,7 +81,7 @@ def sample_last_run_running(aware_datetime: Any) -> LastRun: @pytest.fixture def sample_last_run_finished( - aware_datetime: Any, later_datetime: Any + aware_datetime: datetime, later_datetime: datetime ) -> LastRun: """Provide a LastRun instance with FINISHED status.""" return LastRun( @@ -97,7 +98,9 @@ def sample_last_run_finished( @pytest.fixture def sample_last_run_error( - aware_datetime: Any, later_datetime: Any, sample_run_error: Any + aware_datetime: datetime, + later_datetime: datetime, + sample_run_error: RunError, ) -> LastRun: """Provide a LastRun instance with ERROR status.""" return LastRun( @@ -118,43 +121,61 @@ def fresh_state() -> State: return State() -@pytest.fixture +@pytest.fixture(scope="function") def mock_node() -> Mock: - """Provide a mock QualibrationNode. + """Provide a mock QualibrationNode with enforced interface. + + Uses create_autospec to ensure the mock conforms to QualibrationNode's + actual interface, catching typos and signature mismatches early. + + Scope is explicitly set to "function" to ensure each test gets a fresh + mock instance with clean state (empty state_updates dict, no call history). + This prevents test pollution from mutable state. Note: This cannot be used directly with State() due to Pydantic validation. Use state_with_node fixture instead. """ - node = Mock() + node = cast(Mock, create_autospec(QualibrationNode, instance=True)) + node.name = "test_node" node.snapshot_idx = 42 node.run_summary = None node.state_updates = {} + return node -@pytest.fixture +@pytest.fixture(scope="function") def mock_workflow() -> Mock: - """Provide a mock workflow (QGraph). + """Provide a mock QualibrationGraph with enforced interface. + + Uses create_autospec to ensure the mock conforms to QualibrationGraph's + actual interface, catching typos and signature mismatches early. + + Scope is explicitly set to "function" to ensure each test gets a fresh + mock instance with clean call history. This prevents test pollution from + accumulated mock state. Note: This cannot be used directly with State() due to Pydantic validation. Use state_with_workflow fixture instead. """ - workflow = Mock() + workflow = cast(Mock, create_autospec(QualibrationGraph, instance=True)) + workflow.name = "test_workflow" workflow.snapshot_idx = 100 workflow.run_summary = None + return workflow @pytest.fixture -def state_with_node(mock_node: Any) -> State: +def state_with_node(mock_node: Mock) -> State: """Provide a State with a mock node (bypassing validation).""" return State.model_construct(run_item=mock_node) @pytest.fixture -def state_with_workflow(mock_workflow: Any) -> State: +def state_with_workflow(mock_workflow: Mock) -> State: """Provide a State with a mock workflow (bypassing validation).""" return State.model_construct(run_item=mock_workflow) @@ -163,7 +184,7 @@ def state_with_workflow(mock_workflow: Any) -> State: @pytest.fixture -def mock_library(mock_workflow: Any) -> Mock: +def mock_library(mock_workflow: Mock) -> Mock: """Provide a mock QualibrationLibrary.""" library = Mock() library.graphs = {"test_workflow": mock_workflow} @@ -204,7 +225,7 @@ class FullParams(BaseModel): @pytest.fixture(scope="function") -def test_library() -> Any: +def test_library() -> QualibrationLibrary[Any, Any]: """ Provide a QualibrationLibrary instance loaded with test nodes. @@ -215,14 +236,12 @@ def test_library() -> Any: Scope is explicitly set to "function" to ensure each test gets fresh node instances, maintaining test independence. """ - from qualibrate import QualibrationLibrary - # Get path to test_nodes directory test_nodes_path = Path(__file__).parent / "fixtures" / "test_nodes" # Create library with set_active=False to avoid interfering with # other tests - library: Any = QualibrationLibrary( + library: QualibrationLibrary[Any, Any] = QualibrationLibrary( library_folder=test_nodes_path, set_active=False ) diff --git a/tests/fixtures/test_nodes/node_with_actions.py b/tests/fixtures/test_nodes/node_with_actions.py index 8d4a72f..38991e8 100644 --- a/tests/fixtures/test_nodes/node_with_actions.py +++ b/tests/fixtures/test_nodes/node_with_actions.py @@ -51,14 +51,14 @@ class Parameters(NodeParameters): # Create the node -node: QualibrationNode[Parameters, Parameters] = QualibrationNode( # type: ignore[type-var] +node: QualibrationNode[Parameters, Any] = QualibrationNode( name="node_with_actions", parameters=Parameters(), ) @node.run_action -def prepare_data(node: Any) -> dict[str, Any]: +def prepare_data(node: QualibrationNode[Parameters, Any]) -> dict[str, Any]: """Generate measurement data based on parameters.""" data = [ node.parameters.amplitude * i for i in range(node.parameters.num_points) @@ -71,12 +71,12 @@ def prepare_data(node: Any) -> dict[str, Any]: @node.run_action -def process_data(node: Any) -> dict[str, Any]: +def process_data(node: QualibrationNode[Parameters, Any]) -> dict[str, Any]: """Process the prepared data.""" # Access data from previous action via namespace raw_data = node.namespace["data"] processed = [x * 2 for x in raw_data] - mean_value = sum(processed) / len(processed) + mean_value = sum(raw_data) * 2 / len(raw_data) return { "processed_data": processed, @@ -87,7 +87,9 @@ def process_data(node: Any) -> dict[str, Any]: @node.run_action(skip_if=not node.parameters.trigger_deep_error) # type: ignore[misc] -def execute_qua_program(node: Any) -> dict[str, Any]: +def execute_qua_program( + node: QualibrationNode[Parameters, Any], +) -> dict[str, Any]: mock_job = Mock() mock_job.result_handles.keys.return_value = ["I"] @@ -126,7 +128,9 @@ def execute_qua_program(node: Any) -> dict[str, Any]: @node.run_action(skip_if=not node.parameters.update_state) # type: ignore[misc] -def update_machine_state(node: Any) -> dict[str, Any]: +def update_machine_state( + node: QualibrationNode[Parameters, Any], +) -> dict[str, Any]: """Update quantum machine state (if enabled).""" # This action only runs if update_state is True state_updated = False @@ -143,14 +147,16 @@ def update_machine_state(node: Any) -> dict[str, Any]: @node.run_action(skip_if=not node.parameters.trigger_error) # type: ignore[misc] -def process_data_with_error(node: Any) -> dict[str, Any]: +def process_data_with_error( + node: QualibrationNode[Parameters, Any], +) -> dict[str, Any]: """Process data - this action raises an error when executed.""" # This action always fails when it runs raise ValueError(node.parameters.error_message) @node.run_action -def finalize_results(node: Any) -> dict[str, Any]: +def finalize_results(node: QualibrationNode[Parameters, Any]) -> dict[str, Any]: """Collect all results into final summary.""" # Gather all data from namespace summary = { diff --git a/tests/fixtures/test_nodes/node_with_subroutine.py b/tests/fixtures/test_nodes/node_with_subroutine.py index 9bea728..73b5c62 100644 --- a/tests/fixtures/test_nodes/node_with_subroutine.py +++ b/tests/fixtures/test_nodes/node_with_subroutine.py @@ -27,7 +27,7 @@ class Parameters(NodeParameters): # Create the node -node: QualibrationNode[Parameters, Parameters] = QualibrationNode( # type: ignore[type-var] +node: QualibrationNode[Parameters, Any] = QualibrationNode( name="node_with_subroutine", parameters=Parameters(), ) @@ -60,7 +60,7 @@ def another_helper(value: float, message: str) -> float: @node.run_action -def prepare_data(node: Any) -> dict[str, Any]: +def prepare_data(node: QualibrationNode[Parameters, Any]) -> dict[str, Any]: """Generate measurement data based on parameters.""" # Simple data generation without errors data = [ @@ -70,7 +70,9 @@ def prepare_data(node: Any) -> dict[str, Any]: @node.run_action(skip_if=not node.parameters.trigger_subroutine_error) # type: ignore[misc] -def process_with_subroutine(node: Any) -> dict[str, Any]: +def process_with_subroutine( + node: QualibrationNode[Parameters, Any], +) -> dict[str, Any]: """Process data using a helper function. This action calls helper_function, which will raise an IndexError @@ -84,7 +86,7 @@ def process_with_subroutine(node: Any) -> dict[str, Any]: @node.run_action -def finalize(node: Any) -> dict[str, str]: +def finalize(node: QualibrationNode[Parameters, Any]) -> dict[str, str]: """Finalize results.""" return {"status": "completed"} From c3ac0900e5bb097f355daaad5d58d3215298adf3 Mon Sep 17 00:00:00 2001 From: Jonatan Kutchinsky Date: Tue, 23 Dec 2025 22:10:23 +0100 Subject: [PATCH 24/26] More fixes based on PR feedback. --- .../test_nodes/test_node_can_raise_in_body.py | 2 +- tests/integration/test_run_job_integration.py | 87 +++-- tests/unit/test_config_state.py | 71 ++-- tests/unit/test_models_common.py | 292 +-------------- tests/unit/test_models_last_run.py | 338 +----------------- tests/unit/test_run_job.py | 161 +++++---- 6 files changed, 188 insertions(+), 763 deletions(-) diff --git a/tests/fixtures/test_nodes/test_node_can_raise_in_body.py b/tests/fixtures/test_nodes/test_node_can_raise_in_body.py index 1d4f19c..9081b4b 100644 --- a/tests/fixtures/test_nodes/test_node_can_raise_in_body.py +++ b/tests/fixtures/test_nodes/test_node_can_raise_in_body.py @@ -37,7 +37,7 @@ class Parameters(NodeParameters): # Create the node node: QualibrationNode[Parameters, Parameters] = QualibrationNode( # type: ignore[type-var] - name="node_raises_in_body", + name="node_can_raise_in_body", parameters=Parameters(), ) diff --git a/tests/integration/test_run_job_integration.py b/tests/integration/test_run_job_integration.py index 7ad7f92..2948b7e 100644 --- a/tests/integration/test_run_job_integration.py +++ b/tests/integration/test_run_job_integration.py @@ -16,8 +16,9 @@ from typing import Any import pytest +from qualibrate import QualibrationLibrary -from qualibrate_runner.config.models import RunStatusEnum +from qualibrate_runner.config.models import RunStatusEnum, State from qualibrate_runner.core.run_job import run_node @@ -25,15 +26,16 @@ class TestSimpleNodeExecution: """Integration tests for simple nodes without action system.""" def test_simple_node_success( - self, test_library: Any, fresh_state: Any + self, test_library: QualibrationLibrary[Any, Any], fresh_state: State ) -> None: """Test successful execution of simple node.""" - node = test_library.nodes["node_raises_in_body"] + node = test_library.nodes["node_can_raise_in_body"] # Execute through run_node (full orchestration) run_node(node, {}, fresh_state) # Verify state tracking + assert fresh_state.last_run is not None assert fresh_state.last_run.status == RunStatusEnum.FINISHED assert fresh_state.last_run.completed_at is not None assert fresh_state.last_run.error is None @@ -44,24 +46,25 @@ def test_simple_node_success( assert node.results["error_raised"] is False def test_simple_node_with_different_parameters( - self, test_library: Any, fresh_state: Any + self, test_library: QualibrationLibrary[Any, Any], fresh_state: State ) -> None: """Test simple node with runtime parameter override.""" - node = test_library.nodes["node_raises_in_body"] + node = test_library.nodes["node_can_raise_in_body"] # Execute with custom parameters params = {"amplitude": 0.8, "num_points": 5} run_node(node, params, fresh_state) # Verify execution succeeded + assert fresh_state.last_run is not None assert fresh_state.last_run.status == RunStatusEnum.FINISHED assert fresh_state.last_run.passed_parameters == params def test_simple_node_error_in_body( - self, test_library: Any, fresh_state: Any + self, test_library: QualibrationLibrary[Any, Any], fresh_state: State ) -> None: """Test error capture when node body raises exception.""" - node = test_library.nodes["node_raises_in_body"] + node = test_library.nodes["node_can_raise_in_body"] # Execute with should_fail=True params = { @@ -74,6 +77,7 @@ def test_simple_node_error_in_body( run_node(node, params, fresh_state) # Verify error was captured in state + assert fresh_state.last_run is not None assert fresh_state.last_run.status == RunStatusEnum.ERROR assert fresh_state.last_run.error is not None assert "ValueError" in fresh_state.last_run.error.error_class @@ -84,7 +88,7 @@ class TestNodeWithActionsExecution: """Integration tests for nodes with action system.""" def test_all_actions_execute( - self, test_library: Any, fresh_state: Any + self, test_library: QualibrationLibrary[Any, Any], fresh_state: State ) -> None: """Test full execution with all default actions.""" node = test_library.nodes["node_with_actions"] @@ -93,6 +97,7 @@ def test_all_actions_execute( run_node(node, {}, fresh_state) # Verify state tracking + assert fresh_state.last_run is not None assert fresh_state.last_run.status == RunStatusEnum.FINISHED assert fresh_state.last_run.error is None @@ -103,7 +108,7 @@ def test_all_actions_execute( assert "summary" in node.namespace def test_conditional_action_skip( - self, test_library: Any, fresh_state: Any + self, test_library: QualibrationLibrary[Any, Any], fresh_state: State ) -> None: """Test conditional action skipping via parameters.""" node = test_library.nodes["node_with_actions"] @@ -113,6 +118,7 @@ def test_conditional_action_skip( run_node(node, params, fresh_state) # Verify execution succeeded + assert fresh_state.last_run is not None assert fresh_state.last_run.status == RunStatusEnum.FINISHED # Verify normal actions ran @@ -123,7 +129,7 @@ def test_conditional_action_skip( assert "state_updated" not in node.namespace def test_action_raises_error( - self, test_library: Any, fresh_state: Any + self, test_library: QualibrationLibrary[Any, Any], fresh_state: State ) -> None: """Test error capture when action raises exception.""" node = test_library.nodes["node_with_actions"] @@ -138,12 +144,13 @@ def test_action_raises_error( run_node(node, params, fresh_state) # Verify error was captured + assert fresh_state.last_run is not None assert fresh_state.last_run.status == RunStatusEnum.ERROR assert fresh_state.last_run.error is not None assert "ValueError" in fresh_state.last_run.error.error_class def test_error_from_external_library( - self, test_library: Any, fresh_state: Any + self, test_library: QualibrationLibrary[Any, Any], fresh_state: State ) -> None: """Test error propagation from external library code.""" node = test_library.nodes["node_with_actions"] @@ -156,6 +163,7 @@ def test_error_from_external_library( run_node(node, params, fresh_state) # Verify error was captured + assert fresh_state.last_run is not None assert fresh_state.last_run.status == RunStatusEnum.ERROR assert fresh_state.last_run.error is not None @@ -164,7 +172,7 @@ class TestNamespaceAccumulation: """Integration tests for namespace data flow across actions.""" def test_namespace_accumulates_across_actions( - self, test_library: Any, fresh_state: Any + self, test_library: QualibrationLibrary[Any, Any], fresh_state: State ) -> None: """Test that namespace accumulates data from all actions.""" node = test_library.nodes["node_with_actions"] @@ -184,7 +192,7 @@ def test_namespace_accumulates_across_actions( assert "summary" in node.namespace def test_later_actions_use_earlier_data( - self, test_library: Any, fresh_state: Any + self, test_library: QualibrationLibrary[Any, Any], fresh_state: State ) -> None: """Test that actions can access data from previous actions.""" node = test_library.nodes["node_with_actions"] @@ -205,7 +213,7 @@ class TestStateTracking: """Integration tests for state lifecycle management.""" def test_state_lifecycle_success( - self, test_library: Any, fresh_state: Any + self, test_library: QualibrationLibrary[Any, Any], fresh_state: State ) -> None: """Test state transitions during successful execution.""" node = test_library.nodes["node_with_actions"] @@ -226,7 +234,7 @@ def test_state_lifecycle_success( ) def test_state_lifecycle_with_error( - self, test_library: Any, fresh_state: Any + self, test_library: QualibrationLibrary[Any, Any], fresh_state: State ) -> None: """Test state updates when execution fails.""" node = test_library.nodes["node_with_actions"] @@ -244,7 +252,7 @@ def test_state_lifecycle_with_error( assert fresh_state.last_run.completed_at is not None def test_passed_parameters_captured( - self, test_library: Any, fresh_state: Any + self, test_library: QualibrationLibrary[Any, Any], fresh_state: State ) -> None: """Test that passed parameters are captured in state.""" node = test_library.nodes["node_with_actions"] @@ -258,6 +266,7 @@ def test_passed_parameters_captured( run_node(node, params, fresh_state) # Verify parameters were captured + assert fresh_state.last_run is not None assert fresh_state.last_run.passed_parameters == params @@ -265,40 +274,45 @@ class TestParametricBehavior: """Integration tests for parametric node behavior.""" def test_namespace_persists_across_runs( - self, test_library: Any, fresh_state: Any + self, test_library: QualibrationLibrary[Any, Any], fresh_state: State ) -> None: """Test that namespace accumulates across multiple runs""" node = test_library.nodes["node_with_actions"] # First execution: normal (with update_state=True by default) run_node(node, {}, fresh_state) + assert fresh_state.last_run is not None assert fresh_state.last_run.status == RunStatusEnum.FINISHED assert "state_updated" in node.namespace first_run_keys = set(node.namespace.keys()) # Second execution: namespace should accumulate, not reset run_node(node, {"update_state": False}, fresh_state) + assert fresh_state.last_run is not None assert fresh_state.last_run.status == RunStatusEnum.FINISHED - # Verify namespace accumulated - all keys from first run still present + # Verify namespace accumulated - all keys from first run are still + # present (none have disappeared) assert first_run_keys.issubset(set(node.namespace.keys())) # "state_updated" still present from first run (correct behavior) assert "state_updated" in node.namespace def test_error_execution_preserves_namespace( - self, test_library: Any, fresh_state: Any + self, test_library: QualibrationLibrary[Any, Any], fresh_state: State ) -> None: """Test that namespace is preserved even when execution fails.""" node = test_library.nodes["node_with_actions"] # First execution: normal run_node(node, {}, fresh_state) + assert fresh_state.last_run is not None assert fresh_state.last_run.status == RunStatusEnum.FINISHED successful_run_keys = set(node.namespace.keys()) # Second execution: trigger error with pytest.raises(ValueError): run_node(node, {"trigger_error": True}, fresh_state) - assert fresh_state.last_run.status == RunStatusEnum.ERROR + assert fresh_state.last_run is not None + assert fresh_state.last_run.status == RunStatusEnum.ERROR # type: ignore[comparison-overlap] # Namespace from successful run should still be present assert successful_run_keys.issubset(set(node.namespace.keys())) @@ -308,7 +322,7 @@ class TestErrorMessaging: """Integration tests for improved error messaging""" def test_error_details_for_action_failure( - self, test_library: Any, fresh_state: Any + self, test_library: QualibrationLibrary[Any, Any], fresh_state: State ) -> None: """Test error details when error occurs in action.""" node = test_library.nodes["node_with_actions"] @@ -320,6 +334,7 @@ def test_error_details_for_action_failure( run_node(node, params, fresh_state) # Verify error was captured + assert fresh_state.last_run is not None assert fresh_state.last_run.status == RunStatusEnum.ERROR assert fresh_state.last_run.error is not None error = fresh_state.last_run.error @@ -353,10 +368,10 @@ def test_error_details_for_action_failure( ) def test_error_details_for_body_failure( - self, test_library: Any, fresh_state: Any + self, test_library: QualibrationLibrary[Any, Any], fresh_state: State ) -> None: """Test error details when error occurs in node body (not in action).""" - node = test_library.nodes["node_raises_in_body"] + node = test_library.nodes["node_can_raise_in_body"] # Trigger error in node body params = { @@ -369,11 +384,13 @@ def test_error_details_for_body_failure( run_node(node, params, fresh_state) # Verify error was captured + assert fresh_state.last_run is not None assert fresh_state.last_run.status == RunStatusEnum.ERROR assert fresh_state.last_run.error is not None error = fresh_state.last_run.error # 1. Headline should indicate body error (no action) + assert error.details_headline is not None assert ( "body" in error.details_headline or "initialization" in error.details_headline @@ -381,6 +398,7 @@ def test_error_details_for_body_failure( # 2. Details should NOT contain action execution summary # (since no actions were used) + assert error.details is not None assert ( "Completed actions" not in error.details or "prepare_data" not in error.details @@ -395,7 +413,7 @@ def test_error_details_for_body_failure( assert "action_manager.py" not in error.details def test_error_details_with_action_history( - self, test_library: Any, fresh_state: Any + self, test_library: QualibrationLibrary[Any, Any], fresh_state: State ) -> None: """Test error details include completed and skipped actions.""" node = test_library.nodes["node_with_actions"] @@ -410,10 +428,13 @@ def test_error_details_with_action_history( run_node(node, params, fresh_state) # Verify error was captured + assert fresh_state.last_run is not None assert fresh_state.last_run.status == RunStatusEnum.ERROR + assert fresh_state.last_run.error is not None error = fresh_state.last_run.error # 1. Should show completed actions before the error + assert error.details is not None assert "Completed actions" in error.details assert "prepare_data" in error.details assert "process_data" in error.details @@ -426,7 +447,7 @@ def test_error_details_with_action_history( assert "process_data_with_error" in error.details def test_error_details_for_action_with_subroutine( - self, test_library: Any, fresh_state: Any + self, test_library: QualibrationLibrary[Any, Any], fresh_state: State ) -> None: """Test error in subroutine called by action shows full call chain.""" node = test_library.nodes["node_with_subroutine"] @@ -438,14 +459,18 @@ def test_error_details_for_action_with_subroutine( run_node(node, params, fresh_state) # Verify error was captured + assert fresh_state.last_run is not None assert fresh_state.last_run.status == RunStatusEnum.ERROR + assert fresh_state.last_run.error is not None error = fresh_state.last_run.error # 1. Headline should mention the action (not the subroutine) + assert error.details_headline is not None assert "process_with_subroutine" in error.details_headline # 2. Simplified traceback should show BOTH the action AND the # subroutine + assert error.details is not None assert "Traceback:" in error.details # Should include the action function assert "process_with_subroutine" in error.details @@ -465,7 +490,7 @@ def test_error_details_for_action_with_subroutine( assert len(error.traceback) > 5 def test_error_headline_for_action( - self, test_library: Any, fresh_state: Any + self, test_library: QualibrationLibrary[Any, Any], fresh_state: State ) -> None: """Test headline format for action errors.""" node = test_library.nodes["node_with_actions"] @@ -478,7 +503,10 @@ def test_error_headline_for_action( with pytest.raises(ValueError): run_node(node, params, fresh_state) + assert fresh_state.last_run is not None + assert fresh_state.last_run.error is not None error = fresh_state.last_run.error + assert error.details_headline is not None headline = error.details_headline assert "process_data_with_error" in headline @@ -486,10 +514,10 @@ def test_error_headline_for_action( assert "action" in headline def test_error_headline_for_body( - self, test_library: Any, fresh_state: Any + self, test_library: QualibrationLibrary[Any, Any], fresh_state: State ) -> None: """Test headline format for body/initialization errors.""" - node = test_library.nodes["node_raises_in_body"] + node = test_library.nodes["node_can_raise_in_body"] params = { "should_fail": True, @@ -500,7 +528,10 @@ def test_error_headline_for_body( with pytest.raises(RuntimeError): run_node(node, params, fresh_state) + assert fresh_state.last_run is not None + assert fresh_state.last_run.error is not None error = fresh_state.last_run.error + assert error.details_headline is not None headline = error.details_headline # Should indicate location (body or initialization) diff --git a/tests/unit/test_config_state.py b/tests/unit/test_config_state.py index 52dfa7d..dc34c0d 100644 --- a/tests/unit/test_config_state.py +++ b/tests/unit/test_config_state.py @@ -7,11 +7,13 @@ from __future__ import annotations -from typing import Any +from datetime import datetime +from unittest.mock import Mock import pytest from qualibrate_runner.config.models import State +from qualibrate_runner.core.models.common import RunError from qualibrate_runner.core.models.enums import RunnableType, RunStatusEnum from qualibrate_runner.core.models.last_run import LastRun @@ -26,7 +28,9 @@ def test_create_fresh_state(self) -> None: assert state.last_run is None assert state.run_item is None - def test_create_with_last_run(self, sample_last_run_running: Any) -> None: + def test_create_with_last_run( + self, sample_last_run_running: LastRun + ) -> None: """Test creating State with a LastRun.""" state = State(last_run=sample_last_run_running) assert state.last_run is not None @@ -38,19 +42,21 @@ class TestIsRunningProperty: """Tests for the is_running property.""" def test_not_running_when_finished( - self, sample_last_run_finished: Any + self, sample_last_run_finished: LastRun ) -> None: """Test is_running is False when status is FINISHED.""" state = State(last_run=sample_last_run_finished) assert state.is_running is False - def test_not_running_when_error(self, sample_last_run_error: Any) -> None: + def test_not_running_when_error( + self, sample_last_run_error: LastRun + ) -> None: """Test is_running is False when status is ERROR.""" state = State(last_run=sample_last_run_error) assert state.is_running is False def test_is_running_changes_with_status( - self, aware_datetime: Any, mock_node: Any + self, aware_datetime: datetime, mock_node: Mock ) -> None: """Test is_running property changes as status changes.""" state = State() @@ -89,7 +95,7 @@ class TestClearMethod: """Tests for the clear() method.""" def test_clear_finished_state( - self, sample_last_run_finished: Any, mock_node: Any + self, sample_last_run_finished: LastRun, mock_node: Mock ) -> None: """Test clearing state after finished execution.""" state = State.model_construct( @@ -105,7 +111,7 @@ def test_clear_finished_state( assert state.run_item is None def test_clear_error_state( - self, sample_last_run_error: Any, mock_node: Any + self, sample_last_run_error: LastRun, mock_node: Mock ) -> None: """Test clearing state after error.""" state = State.model_construct( @@ -121,7 +127,7 @@ def test_clear_error_state( assert state.run_item is None def test_clear_while_running_raises_error( - self, sample_last_run_running: Any, mock_node: Any + self, sample_last_run_running: LastRun, mock_node: Mock ) -> None: """Test that clearing while running raises RuntimeError.""" state = State.model_construct( @@ -145,7 +151,7 @@ class TestStateLifecycle: """Tests for the typical State lifecycle during execution.""" def test_full_execution_lifecycle_success( - self, aware_datetime: Any, mock_node: Any + self, aware_datetime: datetime, mock_node: Mock ) -> None: """Test state through full successful execution lifecycle.""" state = State() @@ -189,7 +195,10 @@ def test_full_execution_lifecycle_success( assert state.run_item is None def test_full_execution_lifecycle_error( - self, aware_datetime: Any, mock_node: Any, sample_run_error: Any + self, + aware_datetime: datetime, + mock_node: Mock, + sample_run_error: RunError, ) -> None: """Test state through full error execution lifecycle.""" state = State() @@ -229,7 +238,7 @@ def test_full_execution_lifecycle_error( assert state.run_item is None def test_multiple_sequential_executions( - self, aware_datetime: Any, mock_node: Any + self, aware_datetime: datetime, mock_node: Mock ) -> None: """Test state through multiple sequential executions.""" state = State() @@ -273,15 +282,17 @@ def test_multiple_sequential_executions( class TestStateArbitraryTypes: """Tests for arbitrary type support (run_item).""" - def test_store_node_object(self, state_with_node: Any) -> None: + def test_store_node_object(self, state_with_node: State) -> None: """Test storing a QualibrationNode object in run_item.""" + assert state_with_node.run_item is not None assert state_with_node.run_item.name == "test_node" - assert state_with_node.run_item.snapshot_idx == 42 + assert state_with_node.run_item.snapshot_idx == 42 # type: ignore[union-attr] - def test_store_workflow_object(self, state_with_workflow: Any) -> None: + def test_store_workflow_object(self, state_with_workflow: State) -> None: """Test storing a workflow (QGraph) object in run_item.""" + assert state_with_workflow.run_item is not None assert state_with_workflow.run_item.name == "test_workflow" - assert state_with_workflow.run_item.snapshot_idx == 100 + assert state_with_workflow.run_item.snapshot_idx == 100 # type: ignore[union-attr] def test_run_item_can_be_any_object(self) -> None: """Test that run_item can store any object (arbitrary_types_allowed). @@ -305,28 +316,8 @@ def __init__(self) -> None: class TestStateEdgeCases: """Tests for edge cases and special scenarios.""" - def test_set_last_run_to_none_explicitly( - self, sample_last_run_finished: Any - ) -> None: - """Test explicitly setting last_run to None.""" - state = State(last_run=sample_last_run_finished) - assert state.last_run is not None - - state.last_run = None - assert state.last_run is None - assert state.is_running is False - - def test_set_run_item_to_none_explicitly( - self, state_with_node: Any - ) -> None: - """Test explicitly setting run_item to None.""" - assert state_with_node.run_item is not None - - state_with_node.run_item = None - assert state_with_node.run_item is None - def test_replace_last_run_while_not_running( - self, sample_last_run_finished: Any, sample_last_run_error: Any + self, sample_last_run_finished: LastRun, sample_last_run_error: LastRun ) -> None: """Test replacing last_run when not running.""" state = State(last_run=sample_last_run_finished) @@ -336,14 +327,6 @@ def test_replace_last_run_while_not_running( assert state.last_run.status == RunStatusEnum.ERROR - def test_state_with_none_values(self) -> None: - """Test State initialized with explicit None values.""" - state = State(last_run=None, run_item=None) - - assert state.last_run is None - assert state.run_item is None - assert state.is_running is False - def test_is_running_check_is_safe_with_none(self) -> None: """Test that is_running safely handles None last_run.""" state = State() diff --git a/tests/unit/test_models_common.py b/tests/unit/test_models_common.py index c130260..a5a45ab 100644 --- a/tests/unit/test_models_common.py +++ b/tests/unit/test_models_common.py @@ -7,46 +7,13 @@ from __future__ import annotations -from typing import Any - from qualibrate_runner.core.models.common import RunError, StateUpdate class TestRunError: """Tests for the RunError model.""" - def test_create_with_all_fields(self, sample_traceback: Any) -> None: - """Test creating RunError with all required fields.""" - error = RunError( - error_class="ValueError", - message="Test error message", - traceback=sample_traceback, - ) - - assert error.error_class == "ValueError" - assert error.message == "Test error message" - assert error.traceback == sample_traceback - assert len(error.traceback) == 2 - - def test_create_from_exception(self) -> None: - """Test creating RunError from a real exception.""" - import traceback as tb - - try: - raise ValueError("Test exception") - except ValueError as e: - error = RunError( - error_class=type(e).__name__, - message=str(e), - traceback=tb.format_tb(e.__traceback__), - ) - - assert error.error_class == "ValueError" - assert error.message == "Test exception" - assert len(error.traceback) > 0 - assert any("raise ValueError" in line for line in error.traceback) - - def test_serialization(self, sample_run_error: Any) -> None: + def test_serialization(self, sample_run_error: RunError) -> None: """Test that RunError can be serialized to dict.""" data = sample_run_error.model_dump() @@ -55,7 +22,7 @@ def test_serialization(self, sample_run_error: Any) -> None: assert isinstance(data["traceback"], list) assert len(data["traceback"]) == 2 - def test_deserialization(self, sample_traceback: Any) -> None: + def test_deserialization(self, sample_traceback: list[str]) -> None: """Test that RunError can be deserialized from dict.""" data = { "error_class": "RuntimeError", @@ -69,89 +36,8 @@ def test_deserialization(self, sample_traceback: Any) -> None: assert error.message == "Something went wrong" assert error.traceback == sample_traceback - def test_different_error_classes(self) -> None: - """Test RunError with various exception types.""" - error_types = [ - "ValueError", - "KeyError", - "RuntimeError", - "AttributeError", - "TypeError", - "CustomCalibrationError", - ] - - for error_type in error_types: - error = RunError( - error_class=error_type, - message=f"Test {error_type}", - traceback=[], - ) - assert error.error_class == error_type - - def test_multiline_error_message(self) -> None: - """Test RunError with multiline error message.""" - multiline_message = """First line of error -Second line with details -Third line with more context""" - - error = RunError( - error_class="ValueError", - message=multiline_message, - traceback=[], - ) - - assert error.message == multiline_message - assert "\n" in error.message - - def test_long_traceback(self) -> None: - """Test RunError with a long traceback (many frames).""" - long_traceback = [ - f' File "/path/frame_{i}.py", line {i * 10}, in func_{i}\n' - for i in range(20) - ] - - error = RunError( - error_class="DeepCallStackError", - message="Error from deep call stack", - traceback=long_traceback, - ) - - assert len(error.traceback) == 20 - - def test_create_with_optional_detail_fields( - self, sample_traceback: Any - ) -> None: - """Test creating RunError with optional detail fields.""" - error = RunError( - error_class="ValueError", - message="Invalid configuration", - traceback=sample_traceback, - details_headline="Configuration Error", - details="The 'frequency' parameter must be greater than zero.", - ) - - assert error.error_class == "ValueError" - assert error.details_headline == "Configuration Error" - assert ( - error.details - == "The 'frequency' parameter must be greater than zero." - ) - - def test_create_without_optional_detail_fields( - self, sample_traceback: Any - ) -> None: - """Test that optional detail fields default to None.""" - error = RunError( - error_class="ValueError", - message="Test error", - traceback=sample_traceback, - ) - - assert error.details_headline is None - assert error.details is None - def test_serialization_with_optional_fields( - self, sample_traceback: Any + self, sample_traceback: list[str] ) -> None: """Test serialization includes optional fields when present.""" error = RunError( @@ -169,7 +55,7 @@ def test_serialization_with_optional_fields( assert serialized["details"] == "Check the logs for more information" def test_deserialization_with_optional_fields( - self, sample_traceback: Any + self, sample_traceback: list[str] ) -> None: """Test deserialization with optional fields.""" data = { @@ -185,7 +71,7 @@ def test_deserialization_with_optional_fields( assert error.details == "Test Details" def test_deserialization_without_optional_fields( - self, sample_traceback: Any + self, sample_traceback: list[str] ) -> None: """Test deserialization without optional fields.""" data = { @@ -202,106 +88,6 @@ def test_deserialization_without_optional_fields( class TestStateUpdate: """Tests for the StateUpdate model.""" - def test_create_with_all_fields(self) -> None: - """Test creating StateUpdate with all fields.""" - update = StateUpdate( - key="qubit_0", - attr="resonance_frequency", - old=5.0e9, - new=5.1e9, - updated=True, - ) - - assert update.key == "qubit_0" - assert update.attr == "resonance_frequency" - assert update.old == 5.0e9 - assert update.new == 5.1e9 - assert update.updated is True - - def test_create_with_default_updated_false(self) -> None: - """Test that updated defaults to False.""" - update = StateUpdate( - key="qubit_1", - attr="pi_pulse_amplitude", - old=0.5, - new=0.6, - ) - - assert update.updated is False - - def test_string_attribute(self) -> None: - """Test StateUpdate with string attribute name.""" - update = StateUpdate( - key="resonator_0", - attr="wiring", - old="port_1", - new="port_2", - updated=True, - ) - - assert update.attr == "wiring" - assert isinstance(update.attr, str) - - def test_integer_attribute_for_list_index(self) -> None: - """Test StateUpdate with integer attribute (list index).""" - update = StateUpdate( - key="calibration_array", - attr=3, # Index in list - old=0.1, - new=0.2, - updated=True, - ) - - assert update.attr == 3 - assert isinstance(update.attr, int) - - def test_various_value_types(self) -> None: - """Test StateUpdate with different value types.""" - # Float values - update_float = StateUpdate( - key="qubit_0", attr="frequency", old=5.0e9, new=5.1e9 - ) - assert isinstance(update_float.old, float) - - # Integer values - update_int = StateUpdate( - key="qubit_0", attr="num_gates", old=10, new=15 - ) - assert isinstance(update_int.old, int) - - # String values - update_str = StateUpdate( - key="qubit_0", attr="state", old="|0>", new="|1>" - ) - assert isinstance(update_str.old, str) - - # Boolean values - update_bool = StateUpdate( - key="qubit_0", attr="enabled", old=False, new=True - ) - assert isinstance(update_bool.old, bool) - - # None values - update_none = StateUpdate( - key="qubit_0", attr="optional_param", old=None, new=42 - ) - assert update_none.old is None - - # List values - update_list = StateUpdate( - key="qubit_0", attr="calibration_points", old=[1, 2], new=[1, 2, 3] - ) - assert isinstance(update_list.old, list) - - # Dict values - update_dict = StateUpdate( - key="qubit_0", - attr="config", - old={"a": 1}, - new={"a": 1, "b": 2}, - ) - assert isinstance(update_dict.old, dict) - def test_nested_key_path(self) -> None: """Test StateUpdate with nested key path (dot notation).""" update = StateUpdate( @@ -315,7 +101,7 @@ def test_nested_key_path(self) -> None: assert update.key == "qubit_0.readout.resonator" assert "." in update.key - def test_serialization(self, sample_state_update: Any) -> None: + def test_serialization(self, sample_state_update: StateUpdate) -> None: """Test that StateUpdate can be serialized to dict.""" data = sample_state_update.model_dump() @@ -342,69 +128,3 @@ def test_deserialization(self) -> None: assert update.old == 50e-6 assert update.new == 55e-6 assert update.updated is True - - def test_no_change_update(self) -> None: - """Test StateUpdate where old and new values are the same.""" - update = StateUpdate( - key="qubit_0", - attr="frequency", - old=5.0e9, - new=5.0e9, - updated=False, - ) - - # Values can be the same, it's valid - assert update.old == update.new - assert update.updated is False - - def test_update_to_none(self) -> None: - """Test StateUpdate where new value is None (clearing a value).""" - update = StateUpdate( - key="qubit_0", - attr="optional_correction", - old=0.05, - new=None, - updated=True, - ) - - assert update.old == 0.05 - assert update.new is None - - def test_update_from_none(self) -> None: - """Test StateUpdate where old value is None (setting initial value).""" - update = StateUpdate( - key="qubit_0", - attr="new_parameter", - old=None, - new=0.1, - updated=True, - ) - - assert update.old is None - assert update.new == 0.1 - - def test_complex_number_values(self) -> None: - """Test StateUpdate with complex number values.""" - update = StateUpdate( - key="mixer_0", - attr="iq_imbalance", - old=complex(1.0, 0.1), - new=complex(1.0, 0.05), - updated=True, - ) - - assert isinstance(update.old, complex) - assert isinstance(update.new, complex) - - def test_very_long_key(self) -> None: - """Test StateUpdate with very long nested key path.""" - long_key = ( - "system.rack_1.chassis_2.module_3.channel_4.qubit_5.subsystem_6" - ) - - update = StateUpdate( - key=long_key, attr="parameter", old=0, new=1, updated=True - ) - - assert update.key == long_key - assert update.key.count(".") == 6 diff --git a/tests/unit/test_models_last_run.py b/tests/unit/test_models_last_run.py index 38a3e36..4cadee6 100644 --- a/tests/unit/test_models_last_run.py +++ b/tests/unit/test_models_last_run.py @@ -8,146 +8,16 @@ from __future__ import annotations from datetime import datetime, timedelta, timezone -from typing import Any -from unittest.mock import Mock -from qualibrate_runner.core.models.common import StateUpdate from qualibrate_runner.core.models.enums import RunnableType, RunStatusEnum from qualibrate_runner.core.models.last_run import LastRun -class TestLastRunCreation: - """Tests for creating LastRun instances.""" - - def test_create_running_node(self, aware_datetime: Any) -> None: - """Test creating LastRun for a running node.""" - last_run = LastRun( - status=RunStatusEnum.RUNNING, - started_at=aware_datetime, - name="test_node", - idx=-1, - runnable_type=RunnableType.NODE, - passed_parameters={}, - ) - - assert last_run.status == RunStatusEnum.RUNNING - assert last_run.started_at == aware_datetime - assert last_run.completed_at is None - assert last_run.name == "test_node" - assert last_run.idx == -1 - assert last_run.runnable_type == RunnableType.NODE - assert last_run.passed_parameters == {} - assert last_run.run_result is None - assert last_run.error is None - - def test_create_finished_node( - self, aware_datetime: Any, later_datetime: Any - ) -> None: - """Test creating LastRun for a finished node.""" - # Use model_construct to bypass validation for mock run_result - last_run = LastRun.model_construct( - status=RunStatusEnum.FINISHED, - started_at=aware_datetime, - completed_at=later_datetime, - name="test_node", - idx=42, - runnable_type=RunnableType.NODE, - run_result=Mock(success=True), - passed_parameters={}, - ) - - assert last_run.status == RunStatusEnum.FINISHED - assert last_run.completed_at == later_datetime - assert last_run.idx == 42 - assert last_run.run_result is not None - assert last_run.error is None - - def test_create_error_node( - self, aware_datetime: Any, later_datetime: Any, sample_run_error: Any - ) -> None: - """Test creating LastRun for a failed node.""" - last_run = LastRun( - status=RunStatusEnum.ERROR, - started_at=aware_datetime, - completed_at=later_datetime, - name="test_node", - idx=-1, - runnable_type=RunnableType.NODE, - error=sample_run_error, - passed_parameters={}, - ) - - assert last_run.status == RunStatusEnum.ERROR - assert last_run.error is not None - assert last_run.error.error_class == "ValueError" - assert last_run.run_result is None - - def test_create_running_workflow(self, aware_datetime: Any) -> None: - """Test creating LastRun for a running workflow.""" - last_run = LastRun( - status=RunStatusEnum.RUNNING, - started_at=aware_datetime, - name="test_workflow", - idx=-1, - runnable_type=RunnableType.GRAPH, - passed_parameters={}, - ) - - assert last_run.runnable_type == RunnableType.GRAPH - assert last_run.name == "test_workflow" - - def test_create_with_passed_parameters(self, aware_datetime: Any) -> None: - """Test creating LastRun with passed parameters.""" - params = { - "amplitude": 0.5, - "frequency": 5.0e9, - "num_averages": 1000, - } - - last_run = LastRun( - status=RunStatusEnum.RUNNING, - started_at=aware_datetime, - name="test_node", - idx=-1, - runnable_type=RunnableType.NODE, - passed_parameters=params, - ) - - assert last_run.passed_parameters == params - assert last_run.passed_parameters["amplitude"] == 0.5 - - def test_create_with_state_updates(self, aware_datetime: Any) -> None: - """Test creating LastRun with state updates.""" - state_updates = { - "qubit_0.frequency": StateUpdate( - key="qubit_0", - attr="frequency", - old=5.0e9, - new=5.1e9, - updated=True, - ) - } - - last_run = LastRun( - status=RunStatusEnum.FINISHED, - started_at=aware_datetime, - completed_at=aware_datetime, - name="test_node", - idx=42, - runnable_type=RunnableType.NODE, - state_updates=state_updates, - passed_parameters={}, - ) - - assert len(last_run.state_updates) == 1 - assert "qubit_0.frequency" in last_run.state_updates - - class TestLastRunDuration: """Tests for the run_duration computed field.""" def test_duration_for_completed_run( - self, aware_datetime: Any, later_datetime: Any + self, aware_datetime: datetime, later_datetime: datetime ) -> None: """Test run_duration calculation for completed run.""" # later_datetime is 5.5 seconds after aware_datetime @@ -181,7 +51,9 @@ def test_duration_for_running_execution(self) -> None: # Duration should be positive and small (less than 1 second) assert 0 < last_run.run_duration < 1.0 # type: ignore[operator] - def test_duration_for_very_short_run(self, aware_datetime: Any) -> None: + def test_duration_for_very_short_run( + self, aware_datetime: datetime + ) -> None: """Test duration for very short execution (milliseconds).""" completed = aware_datetime + timedelta(milliseconds=150) @@ -197,7 +69,7 @@ def test_duration_for_very_short_run(self, aware_datetime: Any) -> None: assert last_run.run_duration == 0.15 # type: ignore[comparison-overlap] - def test_duration_for_long_run(self, aware_datetime: Any) -> None: + def test_duration_for_long_run(self, aware_datetime: datetime) -> None: """Test duration for long execution (minutes).""" # 5 minutes and 30 seconds completed = aware_datetime + timedelta(minutes=5, seconds=30) @@ -245,7 +117,9 @@ def test_duration_recalculated_on_access(self) -> None: class TestLastRunSerialization: """Tests for LastRun serialization and deserialization.""" - def test_serialize_running_node(self, sample_last_run_running: Any) -> None: + def test_serialize_running_node( + self, sample_last_run_running: LastRun + ) -> None: """Test serializing a running node to dict.""" data = sample_last_run_running.model_dump() @@ -258,7 +132,7 @@ def test_serialize_running_node(self, sample_last_run_running: Any) -> None: assert data["error"] is None def test_serialize_finished_node( - self, sample_last_run_finished: Any + self, sample_last_run_finished: LastRun ) -> None: """Test serializing a finished node to dict.""" data = sample_last_run_finished.model_dump() @@ -269,7 +143,7 @@ def test_serialize_finished_node( assert "run_result" in data assert "amplitude" in data["passed_parameters"] - def test_serialize_error_node(self, sample_last_run_error: Any) -> None: + def test_serialize_error_node(self, sample_last_run_error: LastRun) -> None: """Test serializing a failed node to dict.""" data = sample_last_run_error.model_dump() @@ -278,7 +152,7 @@ def test_serialize_error_node(self, sample_last_run_error: Any) -> None: assert data["error"]["error_class"] == "ValueError" assert data["error"]["message"] == "Invalid data" - def test_deserialize_from_dict(self, aware_datetime: Any) -> None: + def test_deserialize_from_dict(self, aware_datetime: datetime) -> None: """Test deserializing LastRun from dict.""" data = { "status": RunStatusEnum.RUNNING, @@ -299,135 +173,12 @@ def test_deserialize_from_dict(self, aware_datetime: Any) -> None: assert last_run.name == "test_node" -class TestLastRunStatusTransitions: - """Tests for different status values and transitions.""" - - def test_all_status_values(self, aware_datetime: Any) -> None: - """Test that all status enum values can be used.""" - for status in RunStatusEnum: - last_run = LastRun( - status=status, - started_at=aware_datetime, - completed_at=aware_datetime - if status != RunStatusEnum.RUNNING - else None, - name="test_node", - idx=-1, - runnable_type=RunnableType.NODE, - passed_parameters={}, - ) - assert last_run.status == status - - def test_running_to_finished_transition(self, aware_datetime: Any) -> None: - """Test updating from RUNNING to FINISHED status.""" - # Initial RUNNING state - last_run = LastRun( - status=RunStatusEnum.RUNNING, - started_at=aware_datetime, - name="test_node", - idx=-1, - runnable_type=RunnableType.NODE, - passed_parameters={}, - ) - - # Simulate update to FINISHED (use model_construct for mock run_result) - completed = aware_datetime + timedelta(seconds=5) - updated_run = LastRun.model_construct( - status=RunStatusEnum.FINISHED, - started_at=last_run.started_at, - completed_at=completed, - name=last_run.name, - idx=42, - runnable_type=last_run.runnable_type, - run_result=Mock(success=True), - passed_parameters={}, - ) - - assert updated_run.status == RunStatusEnum.FINISHED - assert updated_run.completed_at is not None - assert updated_run.idx == 42 - - def test_running_to_error_transition( - self, aware_datetime: Any, sample_run_error: Any - ) -> None: - """Test updating from RUNNING to ERROR status.""" - # Initial RUNNING state - last_run = LastRun( - status=RunStatusEnum.RUNNING, - started_at=aware_datetime, - name="test_node", - idx=-1, - runnable_type=RunnableType.NODE, - passed_parameters={}, - ) - - # Simulate update to ERROR - completed = aware_datetime + timedelta(seconds=2) - updated_run = LastRun( - status=RunStatusEnum.ERROR, - started_at=last_run.started_at, - completed_at=completed, - name=last_run.name, - idx=-1, - runnable_type=last_run.runnable_type, - error=sample_run_error, - passed_parameters={}, - ) - - assert updated_run.status == RunStatusEnum.ERROR - assert updated_run.error is not None - assert updated_run.idx == -1 - - class TestLastRunEdgeCases: """Tests for edge cases and special scenarios.""" - def test_idx_negative_one_for_no_snapshot( - self, aware_datetime: Any + def test_node_vs_graph_runnable_types( + self, aware_datetime: datetime ) -> None: - """Test that idx=-1 indicates no snapshot was created.""" - last_run = LastRun( - status=RunStatusEnum.RUNNING, - started_at=aware_datetime, - name="test_node", - idx=-1, - runnable_type=RunnableType.NODE, - passed_parameters={}, - ) - - assert last_run.idx == -1 - - def test_empty_passed_parameters(self, aware_datetime: Any) -> None: - """Test with empty passed_parameters dict.""" - last_run = LastRun( - status=RunStatusEnum.RUNNING, - started_at=aware_datetime, - name="test_node", - idx=-1, - runnable_type=RunnableType.NODE, - passed_parameters={}, - ) - - assert last_run.passed_parameters == {} - assert len(last_run.passed_parameters) == 0 - - def test_empty_state_updates(self, aware_datetime: Any) -> None: - """Test with empty state_updates dict.""" - last_run = LastRun( - status=RunStatusEnum.FINISHED, - started_at=aware_datetime, - completed_at=aware_datetime, - name="test_node", - idx=42, - runnable_type=RunnableType.NODE, - state_updates={}, - passed_parameters={}, - ) - - assert last_run.state_updates == {} - assert len(last_run.state_updates) == 0 - - def test_node_vs_graph_runnable_types(self, aware_datetime: Any) -> None: """Test both NODE and GRAPH runnable types.""" node_run = LastRun( status=RunStatusEnum.RUNNING, @@ -449,66 +200,3 @@ def test_node_vs_graph_runnable_types(self, aware_datetime: Any) -> None: assert node_run.runnable_type == RunnableType.NODE assert graph_run.runnable_type == RunnableType.GRAPH - - def test_very_long_node_name(self, aware_datetime: Any) -> None: - """Test with very long node name.""" - long_name = "very_long_node_name_" * 20 - - last_run = LastRun( - status=RunStatusEnum.RUNNING, - started_at=aware_datetime, - name=long_name, - idx=-1, - runnable_type=RunnableType.NODE, - passed_parameters={}, - ) - - assert last_run.name == long_name - assert len(last_run.name) > 300 - - def test_many_state_updates(self, aware_datetime: Any) -> None: - """Test with many state updates.""" - state_updates = { - f"qubit_{i}.frequency": StateUpdate( - key=f"qubit_{i}", - attr="frequency", - old=5.0e9 + i * 0.1e9, - new=5.1e9 + i * 0.1e9, - updated=True, - ) - for i in range(50) - } - - last_run = LastRun( - status=RunStatusEnum.FINISHED, - started_at=aware_datetime, - completed_at=aware_datetime, - name="test_node", - idx=42, - runnable_type=RunnableType.NODE, - state_updates=state_updates, - passed_parameters={}, - ) - - assert len(last_run.state_updates) == 50 - - def test_complex_passed_parameters(self, aware_datetime: Any) -> None: - """Test with complex nested passed_parameters.""" - params = { - "basic": 42, - "nested": {"a": 1, "b": {"c": 2}}, - "list": [1, 2, 3], - "mixed": [{"x": 1}, {"y": 2}], - } - - last_run = LastRun( - status=RunStatusEnum.RUNNING, - started_at=aware_datetime, - name="test_node", - idx=-1, - runnable_type=RunnableType.NODE, - passed_parameters=params, - ) - - assert last_run.passed_parameters["nested"]["b"]["c"] == 2 - assert len(last_run.passed_parameters["list"]) == 3 diff --git a/tests/unit/test_run_job.py b/tests/unit/test_run_job.py index 04e2ddc..6ff7e32 100644 --- a/tests/unit/test_run_job.py +++ b/tests/unit/test_run_job.py @@ -11,6 +11,8 @@ from fastapi import HTTPException from pydantic import ValidationError +from qualibrate_runner.config.models import State +from qualibrate_runner.core.models.common import StateUpdate from qualibrate_runner.core.models.enums import RunnableType, RunStatusEnum from qualibrate_runner.core.run_job import ( get_active_library_or_error, @@ -24,7 +26,7 @@ class TestValidateInputParameters: """Tests for validate_input_parameters function.""" def test_valid_parameters_pass_validation( - self, sample_parameters_class: Any + self, sample_parameters_class: type[Any] ) -> None: """Test that valid parameters pass validation.""" params = {"amplitude": 0.5, "frequency": 5.0e9, "num_averages": 100} @@ -36,7 +38,7 @@ def test_valid_parameters_pass_validation( assert result.num_averages == 100 # type: ignore[attr-defined] def test_valid_parameters_with_defaults( - self, sample_parameters_class: Any + self, sample_parameters_class: type[Any] ) -> None: """Test that validation works with default values.""" params = {"amplitude": 0.5, "frequency": 5.0e9} @@ -48,7 +50,7 @@ def test_valid_parameters_with_defaults( assert result.num_averages == 100 # type: ignore[attr-defined] # Default value def test_invalid_parameter_raises_http_exception( - self, sample_parameters_class: Any + self, sample_parameters_class: type[Any] ) -> None: """Test that invalid parameters raise HTTPException with 422 status.""" params = {"amplitude": 1.5, "frequency": 5.0e9} # amplitude > 1.0 @@ -60,7 +62,7 @@ def test_invalid_parameter_raises_http_exception( assert exc_info.value.detail is not None def test_missing_required_field_raises_http_exception( - self, sample_parameters_class: Any + self, sample_parameters_class: type[Any] ) -> None: """Test that missing required field raises HTTPException.""" params = {"amplitude": 0.5} # Missing required 'frequency' @@ -73,7 +75,9 @@ def test_missing_required_field_raises_http_exception( errors = exc_info.value.detail assert any("frequency" in str(error).lower() for error in errors) - def test_type_coercion_works(self, sample_parameters_class: Any) -> None: + def test_type_coercion_works( + self, sample_parameters_class: type[Any] + ) -> None: """Test that Pydantic type coercion works as expected.""" params = { "amplitude": "0.5", # String that can be coerced to float @@ -93,7 +97,7 @@ class TestGetActiveLibraryOrError: @patch("qualibrate_runner.core.run_job.QualibrationLibrary") def test_returns_library_when_exists( - self, mock_lib_class: Any, mock_library: Any + self, mock_lib_class: Mock, mock_library: Mock ) -> None: """Test that function returns library when one exists.""" mock_lib_class.get_active_library.return_value = mock_library @@ -105,7 +109,7 @@ def test_returns_library_when_exists( @patch("qualibrate_runner.core.run_job.QualibrationLibrary") def test_raises_exception_when_no_library( - self, mock_lib_class: Any + self, mock_lib_class: Mock ) -> None: """Test that function raises exception when no library exists.""" mock_lib_class.get_active_library.side_effect = RuntimeError( @@ -122,7 +126,7 @@ class TestRunNodeHappyPath: """Tests for run_node function - happy path scenarios.""" def test_sets_run_item_to_node( - self, mock_node: Any, fresh_state: Any + self, mock_node: Mock, fresh_state: State ) -> None: """Test that state.run_item is set to the node.""" mock_node.run = Mock(return_value=None) @@ -132,7 +136,7 @@ def test_sets_run_item_to_node( assert fresh_state.run_item is mock_node def test_creates_last_run_with_running_status( - self, mock_node: Any, fresh_state: Any + self, mock_node: Mock, fresh_state: State ) -> None: """Test that initial LastRun has RUNNING status.""" # Track state during execution @@ -152,18 +156,19 @@ def capture_state(*args: Any, **kwargs: Any) -> None: assert last_run_during_execution.name == "test_node" def test_updates_state_with_finished_status( - self, mock_node: Any, fresh_state: Any + self, mock_node: Mock, fresh_state: State ) -> None: """Test that final state has FINISHED status on success.""" mock_node.run = Mock(return_value=None) run_node(mock_node, {}, fresh_state) + assert fresh_state.last_run is not None assert fresh_state.last_run.status == RunStatusEnum.FINISHED assert fresh_state.last_run.error is None def test_captures_snapshot_idx( - self, mock_node: Any, fresh_state: Any + self, mock_node: Mock, fresh_state: State ) -> None: """Test that snapshot_idx is captured from node.""" mock_node.run = Mock(return_value=None) @@ -171,10 +176,11 @@ def test_captures_snapshot_idx( run_node(mock_node, {}, fresh_state) + assert fresh_state.last_run is not None assert fresh_state.last_run.idx == 42 def test_captures_run_summary( - self, mock_node: Any, fresh_state: Any + self, mock_node: Mock, fresh_state: State ) -> None: """Test that run_summary is captured from node.""" mock_node.run = Mock(return_value=None) @@ -183,10 +189,14 @@ def test_captures_run_summary( run_node(mock_node, {}, fresh_state) # run_result should be None since run_summary is None + assert fresh_state.last_run is not None assert fresh_state.last_run.run_result is None def test_captures_state_updates( - self, mock_node: Any, fresh_state: Any, sample_state_update: Any + self, + mock_node: Mock, + fresh_state: State, + sample_state_update: StateUpdate, ) -> None: """Test that state_updates are captured from node.""" @@ -196,16 +206,18 @@ def test_captures_state_updates( run_node(mock_node, {}, fresh_state) + assert fresh_state.last_run is not None assert fresh_state.last_run.state_updates == state_updates def test_sets_completed_at_timestamp( - self, mock_node: Any, fresh_state: Any + self, mock_node: Mock, fresh_state: State ) -> None: """Test that completed_at is set after execution.""" mock_node.run = Mock(return_value=None) run_node(mock_node, {}, fresh_state) + assert fresh_state.last_run is not None assert fresh_state.last_run.completed_at is not None assert ( fresh_state.last_run.completed_at > fresh_state.last_run.started_at @@ -216,7 +228,7 @@ class TestRunNodeErrorPath: """Tests for run_node function - error scenarios.""" def test_captures_exception_in_state( - self, mock_node: Any, fresh_state: Any + self, mock_node: Mock, fresh_state: State ) -> None: """Test that exceptions are captured in state.last_run.error.""" mock_node.run = Mock(side_effect=ValueError("Test error")) @@ -224,18 +236,23 @@ def test_captures_exception_in_state( with pytest.raises(ValueError, match="Test error"): run_node(mock_node, {}, fresh_state) + assert fresh_state.last_run is not None assert fresh_state.last_run.status == RunStatusEnum.ERROR assert fresh_state.last_run.error is not None assert fresh_state.last_run.error.error_class == "ValueError" assert fresh_state.last_run.error.message == "Test error" - def test_captures_traceback(self, mock_node: Any, fresh_state: Any) -> None: + def test_captures_traceback( + self, mock_node: Mock, fresh_state: State + ) -> None: """Test that full traceback is captured.""" mock_node.run = Mock(side_effect=RuntimeError("Node failed")) with pytest.raises(RuntimeError): run_node(mock_node, {}, fresh_state) + assert fresh_state.last_run is not None + assert fresh_state.last_run.error is not None assert len(fresh_state.last_run.error.traceback) > 0 # Traceback should be a list of strings assert all( @@ -244,7 +261,7 @@ def test_captures_traceback(self, mock_node: Any, fresh_state: Any) -> None: ) def test_re_raises_original_exception( - self, mock_node: Any, fresh_state: Any + self, mock_node: Mock, fresh_state: State ) -> None: """Test that the original exception is re-raised.""" original_error = ValueError("Original error") @@ -257,7 +274,7 @@ def test_re_raises_original_exception( assert exc_info.value is original_error def test_state_updated_even_on_error( - self, mock_node: Any, fresh_state: Any + self, mock_node: Mock, fresh_state: State ) -> None: """Test that state is updated in finally block even on error.""" mock_node.run = Mock(side_effect=RuntimeError("Error")) @@ -266,12 +283,13 @@ def test_state_updated_even_on_error( run_node(mock_node, {}, fresh_state) # State should still be updated with error info + assert fresh_state.last_run is not None assert fresh_state.last_run.status == RunStatusEnum.ERROR assert fresh_state.last_run.completed_at is not None assert fresh_state.last_run.name == "test_node" def test_snapshot_idx_negative_one_on_error( - self, mock_node: Any, fresh_state: Any + self, mock_node: Mock, fresh_state: State ) -> None: """Test that snapshot_idx remains -1 on error.""" mock_node.run = Mock(side_effect=ValueError("Error")) @@ -280,6 +298,7 @@ def test_snapshot_idx_negative_one_on_error( with pytest.raises(ValueError): run_node(mock_node, {}, fresh_state) + assert fresh_state.last_run is not None assert fresh_state.last_run.idx == -1 @@ -289,10 +308,10 @@ class TestRunWorkflowHappyPath: @patch("qualibrate_runner.core.run_job.get_active_library_or_error") def test_retrieves_fresh_workflow_from_library( self, - mock_get_library: Any, - mock_library: Any, - mock_workflow: Any, - fresh_state: Any, + mock_get_library: Mock, + mock_library: Mock, + mock_workflow: Mock, + fresh_state: State, ) -> None: """Test that workflow is retrieved fresh from library.""" mock_get_library.return_value = mock_library @@ -323,10 +342,10 @@ def test_retrieves_fresh_workflow_from_library( @patch("qualibrate_runner.core.run_job.get_active_library_or_error") def test_creates_last_run_with_graph_type( self, - mock_get_library: Any, - mock_library: Any, - mock_workflow: Any, - fresh_state: Any, + mock_get_library: Mock, + mock_library: Mock, + mock_workflow: Mock, + fresh_state: State, ) -> None: """Test that LastRun is created with GRAPH runnable_type.""" mock_get_library.return_value = mock_library @@ -355,10 +374,10 @@ def capture_state(*args: Any, **kwargs: Any) -> None: @patch("qualibrate_runner.core.run_job.get_active_library_or_error") def test_validates_parameters_with_full_parameters_class( self, - mock_get_library: Any, - mock_library: Any, - mock_workflow: Any, - fresh_state: Any, + mock_get_library: Mock, + mock_library: Mock, + mock_workflow: Mock, + fresh_state: State, ) -> None: """Test that parameters are validated using full_parameters_class.""" mock_get_library.return_value = mock_library @@ -382,10 +401,10 @@ def test_validates_parameters_with_full_parameters_class( @patch("qualibrate_runner.core.run_job.get_active_library_or_error") def test_splits_parameters_into_nodes_and_params( self, - mock_get_library: Any, - mock_library: Any, - mock_workflow: Any, - fresh_state: Any, + mock_get_library: Mock, + mock_library: Mock, + mock_workflow: Mock, + fresh_state: State, ) -> None: """Test that parameters are split into nodes and parameters.""" mock_get_library.return_value = mock_library @@ -414,10 +433,10 @@ def test_splits_parameters_into_nodes_and_params( @patch("qualibrate_runner.core.run_job.get_active_library_or_error") def test_updates_state_with_finished_status( self, - mock_get_library: Any, - mock_library: Any, - mock_workflow: Any, - fresh_state: Any, + mock_get_library: Mock, + mock_library: Mock, + mock_workflow: Mock, + fresh_state: State, ) -> None: """Test that final state has FINISHED status on success.""" mock_get_library.return_value = mock_library @@ -431,39 +450,17 @@ def test_updates_state_with_finished_status( run_workflow(mock_workflow, {}, fresh_state) + assert fresh_state.last_run is not None assert fresh_state.last_run.status == RunStatusEnum.FINISHED assert fresh_state.last_run.error is None - @patch("qualibrate_runner.core.run_job.get_active_library_or_error") - def test_captures_workflow_snapshot_idx( - self, - mock_get_library: Any, - mock_library: Any, - mock_workflow: Any, - fresh_state: Any, - ) -> None: - """Test that snapshot_idx is captured from workflow.""" - mock_get_library.return_value = mock_library - - # Setup workflow - mock_workflow.run = Mock(return_value=None) - mock_workflow.snapshot_idx = 100 - mock_params = Mock() - mock_params.nodes.model_dump.return_value = {} - mock_params.parameters.model_dump.return_value = {} - mock_workflow.full_parameters_class.return_value = mock_params - - run_workflow(mock_workflow, {}, fresh_state) - - assert fresh_state.last_run.idx == 100 - class TestRunWorkflowErrorPath: """Tests for run_workflow function - error scenarios.""" @patch("qualibrate_runner.core.run_job.get_active_library_or_error") def test_library_not_found_raises_exception( - self, mock_get_library: Any, mock_workflow: Any, fresh_state: Any + self, mock_get_library: Mock, mock_workflow: Mock, fresh_state: State ) -> None: """Test that missing library raises exception.""" mock_get_library.side_effect = RuntimeError("No active library") @@ -471,15 +468,16 @@ def test_library_not_found_raises_exception( with pytest.raises(RuntimeError, match="No active library"): run_workflow(mock_workflow, {}, fresh_state) + assert fresh_state.last_run is not None assert fresh_state.last_run.status == RunStatusEnum.ERROR @patch("qualibrate_runner.core.run_job.get_active_library_or_error") def test_workflow_not_in_library_raises_exception( self, - mock_get_library: Any, - mock_library: Any, - mock_workflow: Any, - fresh_state: Any, + mock_get_library: Mock, + mock_library: Mock, + mock_workflow: Mock, + fresh_state: State, ) -> None: """Test that missing workflow in library raises exception.""" mock_get_library.return_value = mock_library @@ -488,15 +486,16 @@ def test_workflow_not_in_library_raises_exception( with pytest.raises(KeyError): run_workflow(mock_workflow, {}, fresh_state) + assert fresh_state.last_run is not None assert fresh_state.last_run.status == RunStatusEnum.ERROR @patch("qualibrate_runner.core.run_job.get_active_library_or_error") def test_parameter_validation_errors_raise_exception( self, - mock_get_library: Any, - mock_library: Any, - mock_workflow: Any, - fresh_state: Any, + mock_get_library: Mock, + mock_library: Mock, + mock_workflow: Mock, + fresh_state: State, ) -> None: """Test that parameter validation errors are raised.""" mock_get_library.return_value = mock_library @@ -518,15 +517,16 @@ def test_parameter_validation_errors_raise_exception( with pytest.raises(ValidationError): run_workflow(mock_workflow, {}, fresh_state) + assert fresh_state.last_run is not None assert fresh_state.last_run.status == RunStatusEnum.ERROR @patch("qualibrate_runner.core.run_job.get_active_library_or_error") def test_runtime_errors_captured_and_reraised( self, - mock_get_library: Any, - mock_library: Any, - mock_workflow: Any, - fresh_state: Any, + mock_get_library: Mock, + mock_library: Mock, + mock_workflow: Mock, + fresh_state: State, ) -> None: """Test that runtime errors during workflow.run are captured.""" mock_get_library.return_value = mock_library @@ -541,16 +541,18 @@ def test_runtime_errors_captured_and_reraised( with pytest.raises(RuntimeError, match="Workflow failed"): run_workflow(mock_workflow, {}, fresh_state) + assert fresh_state.last_run is not None assert fresh_state.last_run.status == RunStatusEnum.ERROR + assert fresh_state.last_run.error is not None assert fresh_state.last_run.error.error_class == "RuntimeError" @patch("qualibrate_runner.core.run_job.get_active_library_or_error") def test_state_updated_even_on_error( self, - mock_get_library: Any, - mock_library: Any, - mock_workflow: Any, - fresh_state: Any, + mock_get_library: Mock, + mock_library: Mock, + mock_workflow: Mock, + fresh_state: State, ) -> None: """Test that state is updated in finally block even on error.""" mock_get_library.return_value = mock_library @@ -566,5 +568,6 @@ def test_state_updated_even_on_error( run_workflow(mock_workflow, {}, fresh_state) # State should still be updated + assert fresh_state.last_run is not None assert fresh_state.last_run.status == RunStatusEnum.ERROR assert fresh_state.last_run.completed_at is not None From 4bbbb4f2a022f9f3ecd0b509b439c134a309c7a3 Mon Sep 17 00:00:00 2001 From: Jonatan Kutchinsky Date: Tue, 23 Dec 2025 22:26:08 +0100 Subject: [PATCH 25/26] Removing dependency on qualibration-libs, replacing it with a simplified test helper. --- pyproject.toml | 3 +- tests/fixtures/test_helpers.py | 63 +++++++++++++++++++ .../fixtures/test_nodes/node_with_actions.py | 31 ++++----- 3 files changed, 76 insertions(+), 21 deletions(-) create mode 100644 tests/fixtures/test_helpers.py diff --git a/pyproject.toml b/pyproject.toml index 3eb4af8..9037005 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,6 @@ pytest-mock = "^3.15.1" freezegun = "^1.2.0" xarray = "^2024.1.0" quam-builder = {git = "https://github.com/qua-platform/quam-builder.git"} -#qualibration-libs = {path = "../qualibration-libs", develop = true} #[tool.poetry.group.qm-dev.dependencies] #qualibrate-config = {path = "../qualibrate-config", develop = true} @@ -47,7 +46,7 @@ warn_return_any = true plugins = ['pydantic.mypy'] [[tool.mypy.overrides]] -module = ["jsonpatch", "jsonpointer", "qualibration_libs.*", "qualang_tools.*"] +module = ["jsonpatch", "jsonpointer", "qualang_tools.*"] ignore_missing_imports = true [tool.ruff] diff --git a/tests/fixtures/test_helpers.py b/tests/fixtures/test_helpers.py new file mode 100644 index 0000000..d5620ed --- /dev/null +++ b/tests/fixtures/test_helpers.py @@ -0,0 +1,63 @@ +""" +Test helpers for qualibrate-runner tests. + +Minimal implementations extracted from qualibration-libs to avoid dependency. +""" + +import time +from collections.abc import Iterator, Sequence +from typing import Any + + +def format_available_items( + items: Sequence[Any] | dict[Any, Any], + max_items: int = 10, + item_type: str = "keys", +) -> str: + """Format a list of available items for error messages.""" + item_keys = list(items.keys()) if isinstance(items, dict) else list(items) + if item_keys: + items_str = "'" + "', '".join( + str(item) for item in item_keys[:max_items] + ) + items_str += "', ..." if len(item_keys) > max_items else "'." + else: + items_str = "None." + + return f"Available {item_type}: {items_str}" + + +class XarrayDataFetcher: + """ + Minimal XarrayDataFetcher for testing error handling. + + Simplified version that provides just enough functionality to test + deep error handling when KeyError is raised from __getitem__. + """ + + def __init__( + self, + job: Any, + axes: dict[str, Any] | None = None, + ): + """Initialize with job and axes.""" + self.job = job + self.axes = axes + self.t_start: float | None = None + self.data: dict[str, Any] = {"dataset": None} + + def __getitem__(self, key: str) -> Any: + """Get item from data dict, raising KeyError with helpful message.""" + try: + return self.data[key] + except KeyError as e: + keys_list = format_available_items(self.data, item_type="keys") + raise KeyError( + f"Data key '{key}' not found in XarrayDataFetcher. {keys_list}" + ) from e + + def __iter__(self) -> Iterator[Any]: + """Make iterable - yields once for testing.""" + if self.t_start is None: + self.t_start = time.time() + yield self.data["dataset"] diff --git a/tests/fixtures/test_nodes/node_with_actions.py b/tests/fixtures/test_nodes/node_with_actions.py index 38991e8..3540675 100644 --- a/tests/fixtures/test_nodes/node_with_actions.py +++ b/tests/fixtures/test_nodes/node_with_actions.py @@ -9,7 +9,7 @@ """ from typing import Any -from unittest.mock import Mock, patch +from unittest.mock import Mock import numpy as np import xarray as xr @@ -17,7 +17,8 @@ from qualang_tools.results import progress_counter from qualang_tools.units import unit from qualibrate import NodeParameters, QualibrationNode -from qualibration_libs.data import XarrayDataFetcher + +from tests.fixtures.test_helpers import XarrayDataFetcher class Parameters(NodeParameters): @@ -93,11 +94,6 @@ def execute_qua_program( mock_job = Mock() mock_job.result_handles.keys.return_value = ["I"] - # Mock fetching_tool to return a result with is_processing method - mock_result = Mock() - mock_result.is_processing.return_value = False - mock_result.fetch_all.return_value = {"I": Mock()} - # Code supposed to display the progress bar (won't work) u = unit(coerce_to_integer=True) span = node.parameters.frequency_span_in_mhz * u.MHz @@ -110,18 +106,15 @@ def execute_qua_program( ), } - with patch( - "qualibration_libs.data.fetcher.fetching_tool", return_value=mock_result - ): - data_fetcher = XarrayDataFetcher(mock_job, sweep_axes) - for _dataset in data_fetcher: - progress_counter( - data_fetcher[ - "nonexistent_key" - ], # This will raise KeyError from inside XarrayDataFetcher - node.parameters.num_shots, - start_time=data_fetcher.t_start, - ) + data_fetcher = XarrayDataFetcher(mock_job, sweep_axes) + for _dataset in data_fetcher: + progress_counter( + data_fetcher[ + "nonexistent_key" + ], # This will raise KeyError from inside XarrayDataFetcher + node.parameters.num_shots, + start_time=data_fetcher.t_start, + ) node.log(mock_job.execution_report()) return {"ds_raw": _dataset} From e406daa3ddfbe02cad6c709169c2e9c8fde10d65 Mon Sep 17 00:00:00 2001 From: Jonatan Kutchinsky Date: Tue, 6 Jan 2026 10:31:48 +0100 Subject: [PATCH 26/26] Did poetry lock (should fix lint errors) --- poetry.lock | 502 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 399 insertions(+), 103 deletions(-) diff --git a/poetry.lock b/poetry.lock index e37b5fe..69d60bd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -6,7 +6,7 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] 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"}, @@ -18,7 +18,7 @@ version = "4.11.0" description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc"}, {file = "anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4"}, @@ -54,7 +54,7 @@ version = "1.2.0" description = "Backport of CPython tarfile module" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] markers = "python_version < \"3.12\"" files = [ {file = "backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34"}, @@ -71,7 +71,7 @@ version = "2.0.0b7" description = "A better Protobuf / gRPC generator & library" optional = false python-versions = "<4.0,>=3.7" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "betterproto-2.0.0b7-py3-none-any.whl", hash = "sha256:401ab8055e2f814e77b9c88a74d0e1ae3d1e8a969cced6aeb1b59f71ad63fbd2"}, {file = "betterproto-2.0.0b7.tar.gz", hash = "sha256:1b1458ca5278d519bcd62556a4c236f998a91d503f0f71c67b0b954747052af2"}, @@ -140,7 +140,7 @@ version = "1.3.0" description = "A simple, correct Python build frontend" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "build-1.3.0-py3-none-any.whl", hash = "sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4"}, {file = "build-1.3.0.tar.gz", hash = "sha256:698edd0ea270bde950f53aed21f3a0135672206f3911e0176261a31e0e07b397"}, @@ -163,7 +163,7 @@ version = "0.14.3" description = "httplib2 caching for requests" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "cachecontrol-0.14.3-py3-none-any.whl", hash = "sha256:b35e44a3113f17d2a31c1e6b27b9de6d4405f84ae51baa8c1d3cc5b633010cae"}, {file = "cachecontrol-0.14.3.tar.gz", hash = "sha256:73e7efec4b06b20d9267b441c1f733664f989fb8688391b670ca812d70795d11"}, @@ -185,7 +185,7 @@ version = "2025.8.3" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, @@ -197,8 +197,7 @@ version = "2.0.0" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] -markers = "(sys_platform == \"linux\" or sys_platform == \"darwin\") and (platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\")" +groups = ["main", "dev", "test"] files = [ {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, @@ -285,6 +284,7 @@ files = [ {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"}, {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"}, ] +markers = {main = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and (sys_platform == \"linux\" or sys_platform == \"darwin\")", dev = "(sys_platform == \"linux\" or sys_platform == \"darwin\") and (platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\")", test = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and (sys_platform == \"linux\" or sys_platform == \"darwin\")"} [package.dependencies] pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} @@ -295,7 +295,7 @@ version = "3.4.3" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, @@ -384,7 +384,7 @@ version = "2.1.0" description = "Cleo allows you to create beautiful and testable command-line interfaces." optional = false python-versions = ">=3.7,<4.0" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "cleo-2.1.0-py3-none-any.whl", hash = "sha256:4a31bd4dd45695a64ee3c4758f583f134267c2bc518d8ae9a29cf237d009b07e"}, {file = "cleo-2.1.0.tar.gz", hash = "sha256:0b2c880b5d13660a7ea651001fb4acb527696c01f15c9ee650f377aa543fd523"}, @@ -400,7 +400,7 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -415,12 +415,12 @@ 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" -groups = ["main", "dev"] -markers = "os_name == \"nt\" or platform_system == \"Windows\"" +groups = ["main", "dev", "test"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "platform_system == \"Windows\" or os_name == \"nt\"", dev = "os_name == \"nt\" or platform_system == \"Windows\"", test = "platform_system == \"Windows\" or os_name == \"nt\" or sys_platform == \"win32\""} [[package]] name = "contourpy" @@ -428,7 +428,7 @@ version = "1.3.2" description = "Python library for calculating contours of 2D quadrilateral grids" optional = false python-versions = ">=3.10" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934"}, {file = "contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989"}, @@ -505,7 +505,7 @@ version = "0.4.1" description = "Manage Python errors with ease" optional = false python-versions = ">=3.7,<4.0" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "crashtest-0.4.1-py3-none-any.whl", hash = "sha256:8d23eac5fa660409f57472e3851dab7ac18aba459a8d19cbbba86d3d5aecd2a5"}, {file = "crashtest-0.4.1.tar.gz", hash = "sha256:80d7b1f316ebfbd429f648076d6275c877ba30ba48979de4191714a75266f0ce"}, @@ -517,7 +517,7 @@ version = "46.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = "!=3.9.0,!=3.9.1,>=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] markers = "sys_platform == \"linux\"" files = [ {file = "cryptography-46.0.1-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:1cd6d50c1a8b79af1a6f703709d8973845f677c8e97b1268f5ff323d38ce8475"}, @@ -596,7 +596,7 @@ version = "0.12.1" description = "Composable style cycles" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, @@ -612,7 +612,7 @@ version = "2.43.0" description = "Collection of all Datadog Public endpoints" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "datadog_api_client-2.43.0-py3-none-any.whl", hash = "sha256:1d038b545263ad0b665f46d9d36dc0f0ed491770d9dcd68380be4685a9ae6cc3"}, {file = "datadog_api_client-2.43.0.tar.gz", hash = "sha256:f3aec5db9bcceb39f6315e505eea908da7133a19f8475ef996ef8be8e3c84855"}, @@ -668,7 +668,7 @@ version = "4.48.2" description = "Dependency injection framework for Python" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "dependency_injector-4.48.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:54d0178be10f17b768afb5c0ed1c5c565abaa2d097b2bc5a529a31c580613df2"}, {file = "dependency_injector-4.48.2-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:12a15979fd534b728b3061c8aa52fd55adb77574758817daae9df8a1c2eb830b"}, @@ -718,7 +718,7 @@ version = "0.4.0" description = "Distribution utilities" optional = false python-versions = "*" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, @@ -730,7 +730,7 @@ version = "0.22.8" description = "Python Git Library" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "dulwich-0.22.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:546176d18b8cc0a492b0f23f07411e38686024cffa7e9d097ae20512a2e57127"}, {file = "dulwich-0.22.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d2434dd72b2ae09b653c9cfe6764a03c25cfbd99fbbb7c426f0478f6fb1100f"}, @@ -797,7 +797,7 @@ version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] markers = "python_version == \"3.10\"" files = [ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, @@ -838,7 +838,7 @@ version = "2.21.2" description = "Fastest Python implementation of JSON schema" optional = false python-versions = "*" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463"}, {file = "fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de"}, @@ -853,7 +853,7 @@ version = "3.19.1" description = "A platform independent file lock." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d"}, {file = "filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58"}, @@ -865,7 +865,7 @@ version = "0.6.3" description = "A utility to find python versions on your system" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "findpython-0.6.3-py3-none-any.whl", hash = "sha256:a85bb589b559cdf1b87227cc233736eb7cad894b9e68021ee498850611939ebc"}, {file = "findpython-0.6.3.tar.gz", hash = "sha256:5863ea55556d8aadc693481a14ac4f3624952719efc1c5591abb0b4a9e965c94"}, @@ -880,7 +880,7 @@ version = "4.60.1" description = "Tools to manipulate font files" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "fonttools-4.60.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9a52f254ce051e196b8fe2af4634c2d2f02c981756c6464dc192f1b6050b4e28"}, {file = "fonttools-4.60.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c7420a2696a44650120cdd269a5d2e56a477e2bfa9d95e86229059beb1c19e15"}, @@ -955,6 +955,21 @@ type1 = ["xattr ; sys_platform == \"darwin\""] unicode = ["unicodedata2 (>=15.1.0) ; python_version <= \"3.12\""] woff = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "zopfli (>=0.1.4)"] +[[package]] +name = "freezegun" +version = "1.5.5" +description = "Let your Python tests travel through time" +optional = false +python-versions = ">=3.8" +groups = ["test"] +files = [ + {file = "freezegun-1.5.5-py3-none-any.whl", hash = "sha256:cd557f4a75cf074e84bc374249b9dd491eaeacd61376b9eb3c423282211619d2"}, + {file = "freezegun-1.5.5.tar.gz", hash = "sha256:ac7742a6cc6c25a2c35e9292dfd554b897b517d2dec26891a2e8debf205cb94a"}, +] + +[package.dependencies] +python-dateutil = ">=2.7" + [[package]] name = "genson" version = "1.3.0" @@ -973,7 +988,7 @@ version = "1.75.1" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "grpcio-1.75.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:1712b5890b22547dd29f3215c5788d8fc759ce6dd0b85a6ba6e2731f2d04c088"}, {file = "grpcio-1.75.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8d04e101bba4b55cea9954e4aa71c24153ba6182481b487ff376da28d4ba46cf"}, @@ -1050,7 +1065,7 @@ version = "0.4.5" description = "Pure-Python gRPC implementation for asyncio" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "grpclib-0.4.5.tar.gz", hash = "sha256:bf83ed55aca59497e168761d9555056efc54a8f865316c3b39becd007e9f9a73"}, ] @@ -1068,7 +1083,7 @@ version = "0.16.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, @@ -1080,7 +1095,7 @@ version = "4.3.0" description = "Pure-Python HTTP/2 protocol implementation" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd"}, {file = "h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1"}, @@ -1096,7 +1111,7 @@ version = "4.1.0" description = "Pure-Python HPACK header encoding" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496"}, {file = "hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca"}, @@ -1108,7 +1123,7 @@ version = "1.0.9" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, @@ -1130,7 +1145,7 @@ version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -1156,7 +1171,7 @@ version = "6.1.0" description = "Pure-Python HTTP/2 framing" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5"}, {file = "hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08"}, @@ -1168,7 +1183,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -1183,7 +1198,7 @@ version = "8.7.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] markers = "python_version < \"3.12\"" files = [ {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, @@ -1226,13 +1241,25 @@ enabler = ["pytest-enabler (>=2.2)"] test = ["pygments", "pytest (>=6,!=8.1.*)"] type = ["pytest-mypy"] +[[package]] +name = "iniconfig" +version = "2.3.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.10" +groups = ["test"] +files = [ + {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, + {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, +] + [[package]] name = "installer" version = "0.7.0" description = "A library for installing Python wheels." optional = false python-versions = ">=3.7" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "installer-0.7.0-py3-none-any.whl", hash = "sha256:05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53"}, {file = "installer-0.7.0.tar.gz", hash = "sha256:a26d3e3116289bb08216e0d0f7d925fcef0b0194eedfa0c944bcaaa106c4b631"}, @@ -1260,7 +1287,7 @@ version = "3.4.0" description = "Utility functions for Python class constructs" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790"}, {file = "jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd"}, @@ -1279,7 +1306,7 @@ version = "6.0.1" description = "Useful decorators and context managers" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "jaraco.context-6.0.1-py3-none-any.whl", hash = "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4"}, {file = "jaraco_context-6.0.1.tar.gz", hash = "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3"}, @@ -1298,7 +1325,7 @@ version = "4.3.0" description = "Functools like those found in stdlib" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "jaraco_functools-4.3.0-py3-none-any.whl", hash = "sha256:227ff8ed6f7b8f62c56deff101545fa7543cf2c8e7b82a7c2116e672f29c26e8"}, {file = "jaraco_functools-4.3.0.tar.gz", hash = "sha256:cfd13ad0dd2c47a3600b439ef72d8615d482cedcff1632930d6f28924d92f294"}, @@ -1321,7 +1348,7 @@ version = "0.9.0" description = "Low-level, pure Python DBus protocol wrapper." optional = false python-versions = ">=3.7" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] markers = "sys_platform == \"linux\"" files = [ {file = "jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683"}, @@ -1356,7 +1383,7 @@ version = "1.5.2" description = "Lightweight pipelining with Python functions" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241"}, {file = "joblib-1.5.2.tar.gz", hash = "sha256:3faa5c39054b2f03ca547da9b2f52fde67c06240c31853f306aea97f13647b55"}, @@ -1368,7 +1395,7 @@ version = "1.33" description = "Apply JSON-Patches (RFC 6902)" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, @@ -1383,7 +1410,7 @@ version = "3.0.0" description = "Identify specific nodes in a JSON document (RFC 6901)" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, @@ -1395,7 +1422,7 @@ version = "25.6.0" description = "Store and access your passwords safely." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd"}, {file = "keyring-25.6.0.tar.gz", hash = "sha256:0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66"}, @@ -1425,7 +1452,7 @@ version = "1.4.9" description = "A fast implementation of the Cassowary constraint solver" optional = false python-versions = ">=3.10" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b4b4d74bda2b8ebf4da5bd42af11d02d04428b2c32846e4c2c93219df8a7987b"}, {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fb3b8132019ea572f4611d770991000d7f58127560c4889729248eb5852a102f"}, @@ -1635,7 +1662,7 @@ version = "3.23.3" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "marshmallow-3.23.3-py3-none-any.whl", hash = "sha256:20c0f8c613f68bcb45b2a0d3282e2f172575560170bf220d67aafb42717910e4"}, {file = "marshmallow-3.23.3.tar.gz", hash = "sha256:d586c8685ebdb80bf754e1f96e3f305aaf30951f1fc69175b977453633467e76"}, @@ -1655,7 +1682,7 @@ version = "5.11" description = "An unofficial extension to Marshmallow to allow for polymorphic fields" optional = false python-versions = ">=3.5" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "marshmallow-polyfield-5.11.tar.gz", hash = "sha256:8075a9cc490da4af58b902b4a40a99882dd031adb7aaa96abd147a4fcd53415f"}, ] @@ -1669,7 +1696,7 @@ version = "3.10.6" description = "Python plotting package" optional = false python-versions = ">=3.10" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "matplotlib-3.10.6-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:bc7316c306d97463a9866b89d5cc217824e799fa0de346c8f68f4f3d27c8693d"}, {file = "matplotlib-3.10.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d00932b0d160ef03f59f9c0e16d1e3ac89646f7785165ce6ad40c842db16cc2e"}, @@ -1748,7 +1775,7 @@ version = "10.8.0" description = "More routines for operating on iterables, beyond itertools" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b"}, {file = "more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd"}, @@ -1760,7 +1787,7 @@ version = "1.1.1" description = "MessagePack serializer" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "msgpack-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:353b6fc0c36fde68b661a12949d7d49f8f51ff5fa019c1e47c87c4ff34b080ed"}, {file = "msgpack-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:79c408fcf76a958491b4e3b103d1c417044544b68e96d06432a189b43d1215c8"}, @@ -1829,7 +1856,7 @@ version = "6.6.4" description = "multidict implementation" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "multidict-6.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b8aa6f0bd8125ddd04a6593437bad6a7e70f300ff4180a531654aa2ab3f6d58f"}, {file = "multidict-6.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9e5853bbd7264baca42ffc53391b490d65fe62849bf2c690fa3f6273dbcd0cb"}, @@ -2045,7 +2072,7 @@ version = "1.26.4" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, @@ -2091,12 +2118,112 @@ version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, ] +[[package]] +name = "pandas" +version = "2.3.3" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.9" +groups = ["test"] +files = [ + {file = "pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c"}, + {file = "pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a"}, + {file = "pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1"}, + {file = "pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838"}, + {file = "pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250"}, + {file = "pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4"}, + {file = "pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826"}, + {file = "pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523"}, + {file = "pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45"}, + {file = "pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66"}, + {file = "pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b"}, + {file = "pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791"}, + {file = "pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151"}, + {file = "pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c"}, + {file = "pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53"}, + {file = "pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35"}, + {file = "pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908"}, + {file = "pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89"}, + {file = "pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98"}, + {file = "pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084"}, + {file = "pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b"}, + {file = "pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713"}, + {file = "pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8"}, + {file = "pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d"}, + {file = "pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac"}, + {file = "pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c"}, + {file = "pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493"}, + {file = "pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee"}, + {file = "pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5"}, + {file = "pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21"}, + {file = "pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78"}, + {file = "pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110"}, + {file = "pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86"}, + {file = "pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc"}, + {file = "pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0"}, + {file = "pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593"}, + {file = "pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c"}, + {file = "pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b"}, + {file = "pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6"}, + {file = "pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3"}, + {file = "pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5"}, + {file = "pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec"}, + {file = "pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7"}, + {file = "pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450"}, + {file = "pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5"}, + {file = "pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788"}, + {file = "pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87"}, + {file = "pandas-2.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c503ba5216814e295f40711470446bc3fd00f0faea8a086cbc688808e26f92a2"}, + {file = "pandas-2.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a637c5cdfa04b6d6e2ecedcb81fc52ffb0fd78ce2ebccc9ea964df9f658de8c8"}, + {file = "pandas-2.3.3-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:854d00d556406bffe66a4c0802f334c9ad5a96b4f1f868adf036a21b11ef13ff"}, + {file = "pandas-2.3.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf1f8a81d04ca90e32a0aceb819d34dbd378a98bf923b6398b9a3ec0bf44de29"}, + {file = "pandas-2.3.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:23ebd657a4d38268c7dfbdf089fbc31ea709d82e4923c5ffd4fbd5747133ce73"}, + {file = "pandas-2.3.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5554c929ccc317d41a5e3d1234f3be588248e61f08a74dd17c9eabb535777dc9"}, + {file = "pandas-2.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:d3e28b3e83862ccf4d85ff19cf8c20b2ae7e503881711ff2d534dc8f761131aa"}, + {file = "pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b"}, +] + +[package.dependencies] +numpy = [ + {version = ">=1.22.4", markers = "python_version < \"3.11\""}, + {version = ">=1.23.2", markers = "python_version == \"3.11\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, +] +python-dateutil = ">=2.8.2" +pytz = ">=2020.1" +tzdata = ">=2022.7" + +[package.extras] +all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] +aws = ["s3fs (>=2022.11.0)"] +clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] +compression = ["zstandard (>=0.19.0)"] +computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] +consortium-standard = ["dataframe-api-compat (>=0.1.7)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] +feather = ["pyarrow (>=10.0.1)"] +fss = ["fsspec (>=2022.11.0)"] +gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] +hdf5 = ["tables (>=3.8.0)"] +html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] +mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] +parquet = ["pyarrow (>=10.0.1)"] +performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] +plot = ["matplotlib (>=3.6.3)"] +postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] +pyarrow = ["pyarrow (>=10.0.1)"] +spss = ["pyreadstat (>=1.2.0)"] +sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.9.2)"] + [[package]] name = "pastel" version = "0.2.1" @@ -2127,7 +2254,7 @@ version = "2025.9.18" description = "Installer for Python Build Standalone" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "pbs_installer-2025.9.18-py3-none-any.whl", hash = "sha256:8ef55d7675698747505c237015d14c81759bd66a0d4c8b20cec9a2dc96e8434c"}, {file = "pbs_installer-2025.9.18.tar.gz", hash = "sha256:c0a51a7c1e015723bd8396f02e15b5876e439f74b0f45bbac436b189f903219f"}, @@ -2148,7 +2275,7 @@ version = "11.3.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860"}, {file = "pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"}, @@ -2273,7 +2400,7 @@ version = "1.12.1.2" description = "Query metadata from sdists / bdists / installed packages." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "pkginfo-1.12.1.2-py3-none-any.whl", hash = "sha256:c783ac885519cab2c34927ccfa6bf64b5a704d7c69afaea583dd9b7afe969343"}, {file = "pkginfo-1.12.1.2.tar.gz", hash = "sha256:5cd957824ac36f140260964eba3c6be6442a8359b8c48f4adf90210f33a04b7b"}, @@ -2288,7 +2415,7 @@ version = "4.4.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85"}, {file = "platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"}, @@ -2305,7 +2432,7 @@ version = "5.24.1" description = "An open-source, interactive data visualization library for Python" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "plotly-5.24.1-py3-none-any.whl", hash = "sha256:f67073a1e637eb0dc3e46324d9d51e2fe76e9727c892dde64ddf1e1b51f29089"}, {file = "plotly-5.24.1.tar.gz", hash = "sha256:dbc8ac8339d248a4bcc36e08a5659bacfe1b079390b8953533f4eb22169b4bae"}, @@ -2315,6 +2442,22 @@ files = [ packaging = "*" tenacity = ">=6.2.0" +[[package]] +name = "pluggy" +version = "1.6.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.9" +groups = ["test"] +files = [ + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["coverage", "pytest", "pytest-benchmark"] + [[package]] name = "poethepoet" version = "0.37.0" @@ -2341,7 +2484,7 @@ version = "2.1.3" description = "Python dependency management and packaging made easy." optional = false python-versions = "<4.0,>=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "poetry-2.1.3-py3-none-any.whl", hash = "sha256:7054d3f97ccce7f31961ead16250407c4577bfe57e2037a190ae2913fc40a20c"}, {file = "poetry-2.1.3.tar.gz", hash = "sha256:f2c9bd6790b19475976d88ea4553bcc3533c0dc73f740edc4fffe9e2add50594"}, @@ -2377,7 +2520,7 @@ version = "2.1.3" description = "Poetry PEP 517 Build Backend" optional = false python-versions = "<4.0,>=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "poetry_core-2.1.3-py3-none-any.whl", hash = "sha256:2c704f05016698a54ca1d327f46ce2426d72eaca6ff614132c8477c292266771"}, {file = "poetry_core-2.1.3.tar.gz", hash = "sha256:0522a015477ed622c89aad56a477a57813cace0c8e7ff2a2906b7ef4a2e296a4"}, @@ -2389,7 +2532,7 @@ version = "6.32.1" description = "" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "protobuf-6.32.1-cp310-abi3-win32.whl", hash = "sha256:a8a32a84bc9f2aad712041b8b366190f71dde248926da517bde9e832e4412085"}, {file = "protobuf-6.32.1-cp310-abi3-win_amd64.whl", hash = "sha256:b00a7d8c25fa471f16bc8153d0e53d6c9e827f0953f3c09aaa4331c718cae5e1"}, @@ -2408,12 +2551,12 @@ version = "2.23" description = "C parser in Python" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] -markers = "(sys_platform == \"linux\" or sys_platform == \"darwin\") and (platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and implementation_name != \"PyPy\"" +groups = ["main", "dev", "test"] files = [ {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"}, {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}, ] +markers = {main = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and implementation_name != \"PyPy\" and (sys_platform == \"linux\" or sys_platform == \"darwin\")", dev = "(sys_platform == \"linux\" or sys_platform == \"darwin\") and (platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and implementation_name != \"PyPy\"", test = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and implementation_name != \"PyPy\" and (sys_platform == \"linux\" or sys_platform == \"darwin\")"} [[package]] name = "pydantic" @@ -2421,7 +2564,7 @@ version = "2.11.9" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "pydantic-2.11.9-py3-none-any.whl", hash = "sha256:c42dd626f5cfc1c6950ce6205ea58c93efa406da65f479dcb4029d5934857da2"}, {file = "pydantic-2.11.9.tar.gz", hash = "sha256:6b8ffda597a14812a7975c90b82a8a2e777d9257aba3453f973acd3c032a18e2"}, @@ -2443,7 +2586,7 @@ version = "2.33.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, @@ -2555,7 +2698,7 @@ version = "2.11.0" description = "Settings management using Pydantic" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c"}, {file = "pydantic_settings-2.11.0.tar.gz", hash = "sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180"}, @@ -2573,13 +2716,28 @@ gcp-secret-manager = ["google-cloud-secret-manager (>=2.23.1)"] toml = ["tomli (>=2.0.1)"] yaml = ["pyyaml (>=6.0.1)"] +[[package]] +name = "pygments" +version = "2.19.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +groups = ["test"] +files = [ + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + [[package]] name = "pyparsing" version = "3.2.5" description = "pyparsing - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e"}, {file = "pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6"}, @@ -2594,19 +2752,61 @@ version = "1.2.0" description = "Wrappers to call pyproject.toml-based build backend hooks." optional = false python-versions = ">=3.7" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913"}, {file = "pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8"}, ] +[[package]] +name = "pytest" +version = "8.4.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.9" +groups = ["test"] +files = [ + {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, + {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, +] + +[package.dependencies] +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} +iniconfig = ">=1" +packaging = ">=20" +pluggy = ">=1.5,<2" +pygments = ">=2.7.2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-mock" +version = "3.15.1" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.9" +groups = ["test"] +files = [ + {file = "pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d"}, + {file = "pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f"}, +] + +[package.dependencies] +pytest = ">=6.2.5" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + [[package]] name = "python-dateutil" version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -2621,7 +2821,7 @@ version = "1.1.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc"}, {file = "python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab"}, @@ -2660,13 +2860,25 @@ files = [ [package.extras] dev = ["black", "build", "mypy", "pytest", "pytest-cov", "setuptools", "tox", "twine", "wheel"] +[[package]] +name = "pytz" +version = "2025.2" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +groups = ["test"] +files = [ + {file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"}, + {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, +] + [[package]] name = "pywin32-ctypes" version = "0.2.3" description = "A (partial) reimplementation of pywin32 using ctypes/cffi" optional = false python-versions = ">=3.6" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] markers = "sys_platform == \"win32\"" files = [ {file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"}, @@ -2762,7 +2974,7 @@ version = "2.1.6" description = "SDK to control an Octave with QUA" optional = false python-versions = "<3.13,>=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "qm_octave-2.1.6-py3-none-any.whl", hash = "sha256:2e3df8085330ecf0eb1cc3dd661ebec7b3505959d0fabb3a2dc6175ec7c7cba3"}, {file = "qm_octave-2.1.6.tar.gz", hash = "sha256:8a16e6a8c76b060bc2856020a3f54f2e6d936ffc24e2b7963376ce990a8110be"}, @@ -2780,7 +2992,7 @@ version = "1.2.3" description = "QUA language SDK to control a Quantum Computer" optional = false python-versions = "<3.13,>=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "qm_qua-1.2.3-py3-none-any.whl", hash = "sha256:d719aa0f6ca8dc02e3f9941103942d2f260390ba1527857e08e164cd5e1e1df1"}, {file = "qm_qua-1.2.3.tar.gz", hash = "sha256:529cbb443c04f895cd9b2b663ac4617cb221dbdd490220d1de955a2a9bf1c9c5"}, @@ -2812,7 +3024,7 @@ version = "0.20.0" description = "The qualang_tools package includes various tools related to QUA programs in Python" optional = false python-versions = "<3.13,>=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "qualang_tools-0.20.0-py3-none-any.whl", hash = "sha256:0dc71d2c00eaa98da86b70e082cf0c59d9d61b1494d74bc5aee6b07b8f1bff9c"}, {file = "qualang_tools-0.20.0.tar.gz", hash = "sha256:23de4e0dc81387e45803afdf58c51f1a9c6f8b6ea650becda7d3b31904b0d342"}, @@ -2838,7 +3050,7 @@ version = "0.1.8" description = "Configuration for Qualibrate" optional = false python-versions = "<4.0,>=3.10" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "qualibrate_config-0.1.8-py3-none-any.whl", hash = "sha256:58969295ada9253540e18208387acad9097a4883051dd8ed72887e89b4f6d37f"}, {file = "qualibrate_config-0.1.8.tar.gz", hash = "sha256:7deccb0d160a0f1863ca43c87c5712d7a7e40a4c89fa03a8ed06d2ff552a1176"}, @@ -2880,13 +3092,59 @@ url = "https://github.com/qua-platform/qualibrate-core.git" reference = "HEAD" resolved_reference = "e02b120503bdeccf0579ef406ece2c2cc48f36d0" +[[package]] +name = "quam" +version = "0.4.2" +description = "Quantum Abstract Machine (QUAM) facilitates development of abstraction layers in experiments." +optional = false +python-versions = "<3.13,>=3.9" +groups = ["main", "test"] +files = [ + {file = "quam-0.4.2-py3-none-any.whl", hash = "sha256:4651c97f3a0f601f02e7d4d980973bc9eeab47af8954a1a776f4ba6a6ca601f8"}, + {file = "quam-0.4.2.tar.gz", hash = "sha256:0804833adc1813b41d78b14183ddec5135bc97d013fce2744041712e69e668a9"}, +] + +[package.dependencies] +numpy = ">=1.21.2" +qm-qua = ">=1.2.2" +qualang-tools = ">=0.15.0" +qualibrate-config = ">=0.1.6" +typeguard = ">=4.1.0" + +[package.extras] +build = ["build (>=1.2.1)", "setuptools (>=71)", "setuptools-scm (>=8.1.0)"] +dev = ["black (>=23.7.0)", "flake8 (>=5.0.1)", "ipykernel (>=6.24.0)", "pyproject-flake8 (>=5.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=4.1.0)", "pytest-mock (>=3.6.1)"] +docs = ["mkdocs-gen-files", "mkdocs-jupyter", "mkdocstrings[python] (>=0.18)"] + +[[package]] +name = "quam-builder" +version = "0.2.1" +description = "A Python tool designed to programmatically construct QUAM (Quantum Abstract Machine) configurations for the Quantum Orchestration Platform (QOP)." +optional = false +python-versions = ">=3.9,<3.13" +groups = ["test"] +files = [] +develop = false + +[package.dependencies] +qm-qua = ">=1.2.2" +qualang-tools = ">=0.19.0" +quam = ">=0.4.0" +xarray = ">=2024.7.0" + +[package.source] +type = "git" +url = "https://github.com/qua-platform/quam-builder.git" +reference = "HEAD" +resolved_reference = "e3683ff182bda5c5f339af088993b907355c2466" + [[package]] name = "rapidfuzz" version = "3.14.1" description = "rapid fuzzy string matching" optional = false python-versions = ">=3.10" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "rapidfuzz-3.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:489440e4b5eea0d150a31076eb183bed0ec84f934df206c72ae4fc3424501758"}, {file = "rapidfuzz-3.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eff22cc938c3f74d194df03790a6c3325d213b28cf65cdefd6fdeae759b745d5"}, @@ -2991,7 +3249,7 @@ version = "2.32.5" description = "Python HTTP for Humans." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, @@ -3013,7 +3271,7 @@ version = "1.0.0" description = "A utility belt for advanced users of python-requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, @@ -3057,7 +3315,7 @@ version = "1.7.2" description = "A set of python modules for machine learning and data mining" optional = false python-versions = ">=3.10" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "scikit_learn-1.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b33579c10a3081d076ab403df4a4190da4f4432d443521674637677dc91e61f"}, {file = "scikit_learn-1.7.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:36749fb62b3d961b1ce4fedf08fa57a1986cd409eff2d783bca5d4b9b5fce51c"}, @@ -3113,7 +3371,7 @@ version = "1.15.3" description = "Fundamental algorithms for scientific computing in Python" optional = false python-versions = ">=3.10" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c"}, {file = "scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253"}, @@ -3177,7 +3435,7 @@ version = "3.4.0" description = "Python bindings to FreeDesktop.org Secret Service API" optional = false python-versions = ">=3.10" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] markers = "sys_platform == \"linux\"" files = [ {file = "secretstorage-3.4.0-py3-none-any.whl", hash = "sha256:0e3b6265c2c63509fb7415717607e4b2c9ab767b7f344a57473b779ca13bd02e"}, @@ -3194,7 +3452,7 @@ version = "1.5.4" description = "Tool to Detect Surrounding Shell" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, @@ -3206,7 +3464,7 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -3218,7 +3476,7 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -3249,7 +3507,7 @@ version = "9.1.2" description = "Retry code until it succeeds" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138"}, {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"}, @@ -3265,7 +3523,7 @@ version = "3.6.0" description = "threadpoolctl" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb"}, {file = "threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e"}, @@ -3277,7 +3535,7 @@ version = "4.8.2" description = "TinyDB is a tiny, document oriented database optimized for your happiness :)" optional = false python-versions = "<4.0,>=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "tinydb-4.8.2-py3-none-any.whl", hash = "sha256:f97030ee5cbc91eeadd1d7af07ab0e48ceb04aa63d4a983adbaca4cba16e86c3"}, {file = "tinydb-4.8.2.tar.gz", hash = "sha256:f7dfc39b8d7fda7a1ca62a8dbb449ffd340a117c1206b68c50b1a481fb95181d"}, @@ -3289,8 +3547,7 @@ version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] -markers = "python_version <= \"3.11\"" +groups = ["main", "dev", "test"] files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -3325,6 +3582,7 @@ files = [ {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] +markers = {main = "python_version < \"3.12\"", dev = "python_version < \"3.12\"", test = "python_version == \"3.10\""} [[package]] name = "tomli-w" @@ -3332,7 +3590,7 @@ version = "1.2.0" description = "A lil' TOML writer" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90"}, {file = "tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021"}, @@ -3344,7 +3602,7 @@ version = "0.13.3" description = "Style preserving TOML library" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0"}, {file = "tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1"}, @@ -3356,7 +3614,7 @@ version = "2025.9.11.17" description = "Canonical source for classifiers on PyPI (pypi.org)." optional = false python-versions = "*" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "trove_classifiers-2025.9.11.17-py3-none-any.whl", hash = "sha256:5d392f2d244deb1866556457d6f3516792124a23d1c3a463a2e8668a5d1c15dd"}, {file = "trove_classifiers-2025.9.11.17.tar.gz", hash = "sha256:931ca9841a5e9c9408bc2ae67b50d28acf85bef56219b56860876dd1f2d024dd"}, @@ -3368,7 +3626,7 @@ version = "4.4.4" description = "Run-time type checker for Python" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "typeguard-4.4.4-py3-none-any.whl", hash = "sha256:b5f562281b6bfa1f5492470464730ef001646128b180769880468bd84b68b09e"}, {file = "typeguard-4.4.4.tar.gz", hash = "sha256:3a7fd2dffb705d4d0efaed4306a704c89b9dee850b688f060a8b1615a79e5f74"}, @@ -3398,7 +3656,7 @@ version = "4.15.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, @@ -3410,7 +3668,7 @@ version = "0.4.1" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"}, {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"}, @@ -3419,13 +3677,25 @@ files = [ [package.dependencies] typing-extensions = ">=4.12.0" +[[package]] +name = "tzdata" +version = "2025.3" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +groups = ["test"] +files = [ + {file = "tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1"}, + {file = "tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7"}, +] + [[package]] name = "urllib3" version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, @@ -3463,7 +3733,7 @@ version = "20.34.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "virtualenv-20.34.0-py3-none-any.whl", hash = "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026"}, {file = "virtualenv-20.34.0.tar.gz", hash = "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a"}, @@ -3558,13 +3828,39 @@ files = [ {file = "websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee"}, ] +[[package]] +name = "xarray" +version = "2024.11.0" +description = "N-D labeled arrays and datasets in Python" +optional = false +python-versions = ">=3.10" +groups = ["test"] +files = [ + {file = "xarray-2024.11.0-py3-none-any.whl", hash = "sha256:6ee94f63ddcbdd0cf3909d1177f78cdac756640279c0e32ae36819a89cdaba37"}, + {file = "xarray-2024.11.0.tar.gz", hash = "sha256:1ccace44573ddb862e210ad3ec204210654d2c750bec11bbe7d842dfc298591f"}, +] + +[package.dependencies] +numpy = ">=1.24" +packaging = ">=23.2" +pandas = ">=2.1" + +[package.extras] +accel = ["bottleneck", "flox", "numba (>=0.54)", "numbagg", "opt_einsum", "scipy"] +complete = ["xarray[accel,etc,io,parallel,viz]"] +dev = ["hypothesis", "jinja2", "mypy", "pre-commit", "pytest", "pytest-cov", "pytest-env", "pytest-timeout", "pytest-xdist", "ruff", "sphinx", "sphinx_autosummary_accessors", "xarray[complete]"] +etc = ["sparse"] +io = ["cftime", "fsspec", "h5netcdf", "netCDF4", "pooch", "pydap ; python_version < \"3.10\"", "scipy", "zarr"] +parallel = ["dask[complete]"] +viz = ["cartopy", "matplotlib", "nc-time-axis", "seaborn"] + [[package]] name = "xattr" version = "1.2.0" description = "Python wrapper for extended filesystem attributes" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] markers = "sys_platform == \"darwin\"" files = [ {file = "xattr-1.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3df4d8d91e2996c3c72a390ec82e8544acdcb6c7df67b954f1736ff37ea4293e"}, @@ -3651,7 +3947,7 @@ version = "3.23.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] markers = "python_version < \"3.12\"" files = [ {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, @@ -3672,7 +3968,7 @@ version = "0.25.0" description = "Zstandard bindings for Python" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "test"] files = [ {file = "zstandard-0.25.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e59fdc271772f6686e01e1b3b74537259800f57e24280be3f29c8a0deb1904dd"}, {file = "zstandard-0.25.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4d441506e9b372386a5271c64125f72d5df6d2a8e8a2a45a0ae09b03cb781ef7"}, @@ -3781,4 +4077,4 @@ cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and pyt [metadata] lock-version = "2.1" python-versions = "^3.10,<3.13" -content-hash = "44963590f85aa49537e1cb5e580f69ecaa3a61ebfcc0aa415e1a9df00652774f" +content-hash = "adbe3e17c43b3b31c0a98121cccef55fafef3e3044b7ee16e4e8dc53677151e0"