From e0fe7e211360106e947d5ec0ae1d6d52b04213fa Mon Sep 17 00:00:00 2001 From: Karanjot Singh <99573351+Karanjot786@users.noreply.github.com> Date: Fri, 16 Aug 2024 10:25:04 +0530 Subject: [PATCH 01/10] feat: add WRROC models, validators & unit tests (#19) --- .github/workflows/ci.yml | 6 +- crategen/models.py | 279 ++++++++++++++++++++++++++++++++ crategen/validators.py | 109 +++++++++++++ tests/unit/test_wrroc_models.py | 235 +++++++++++++++++++++++++++ 4 files changed, 626 insertions(+), 3 deletions(-) create mode 100644 crategen/models.py create mode 100644 crategen/validators.py create mode 100644 tests/unit/test_wrroc_models.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fa999e2..0d1b757 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,6 +39,6 @@ jobs: run: | poetry add pytest pytest-cov pytest-mock - # - name: Run tests - # run: | - # poetry run pytest --cov=crategen + - name: Run tests + run: | + poetry run pytest --cov=crategen diff --git a/crategen/models.py b/crategen/models.py new file mode 100644 index 0000000..35ad1c2 --- /dev/null +++ b/crategen/models.py @@ -0,0 +1,279 @@ +from pydantic import BaseModel +from typing import Optional + + +class Executor(BaseModel): + """ + A model representing an executor in the Task Execution Service (TES). + + Attributes: + image (str): The Docker image to be used. + command (list[str]): The command to be executed. + """ + image: str + command: list[str] + + +class TESInputs(BaseModel): + """ + A model representing input files in TES. + + Attributes: + url (str): The URL of the input file. + path (str): The path where the input file should be placed. + """ + url: str + path: str + + +class TESOutputs(BaseModel): + """ + A model representing output files in TES. + + Attributes: + url (str): The URL of the output file. + path (str): The path where the output file is stored. + """ + url: str + path: str + + +class TESLogs(BaseModel): + """ + A model representing logs in TES. + + Attributes: + end_time (Optional[str]): The time the task ended. + """ + end_time: Optional[str] = None + + +class TESData(BaseModel): + """ + A model representing a TES task. + + Attributes: + id (str): The unique identifier for the TES task. + name (str): The name of the TES task. + description (Optional[str]): A brief description of the TES task. + executors (list[Executor]): The executors associated with the TES task. + inputs (list[TESInputs]): The inputs to the TES task. + outputs (list[TESOutputs]): The outputs of the TES task. + creation_time (str): The time the task was created. + logs (list[TESLogs]): Logs associated with the TES task. + """ + id: str + name: str + description: Optional[str] = "" + executors: list[Executor] + inputs: list[TESInputs] + outputs: list[TESOutputs] + creation_time: str + logs: list[TESLogs] + + class Config: + extra = "forbid" + + +class WESRunLog(BaseModel): + """ + A model representing a run log in the Workflow Execution Service (WES). + + Attributes: + name (Optional[str]): The name of the run. + start_time (Optional[str]): The start time of the run. + end_time (Optional[str]): The end time of the run. + cmd (Optional[list[str]]): The command executed in the run. + stdout (Optional[str]): The path to the stdout log. + stderr (Optional[str]): The path to the stderr log. + exit_code (Optional[int]): The exit code of the run. + """ + name: Optional[str] = None + start_time: Optional[str] = None + end_time: Optional[str] = None + cmd: Optional[list[str]] = None + stdout: Optional[str] = None + stderr: Optional[str] = None + exit_code: Optional[int] = None + + +class WESOutputs(BaseModel): + """ + A model representing output files in WES. + + Attributes: + location (str): The URL of the output file. + name (str): The name of the output file. + """ + location: str + name: str + + +class WESRequest(BaseModel): + """ + A model representing a workflow request in WES. + + Attributes: + workflow_params (dict[str, str]): The parameters for the workflow. + workflow_type (str): The type of the workflow (e.g., CWL). + workflow_type_version (str): The version of the workflow type. + tags (Optional[dict[str, str]]): Additional tags associated with the workflow. + """ + workflow_params: dict[str, str] + workflow_type: str + workflow_type_version: str + tags: Optional[dict[str, str]] = None + + +class WESData(BaseModel): + """ + A model representing a WES run. + + Attributes: + run_id (str): The unique identifier for the WES run. + request (WESRequest): The request associated with the WES run. + state (str): The state of the WES run. + run_log (WESRunLog): The log of the WES run. + task_logs (Optional[list[WESRunLog]]): The logs of individual tasks within the run. + outputs (list[WESOutputs]): The outputs of the WES run. + """ + run_id: str + request: WESRequest + state: str + run_log: WESRunLog + task_logs: Optional[list[WESRunLog]] = None + outputs: list[WESOutputs] + + class Config: + extra = "forbid" + + +class WRROCInputs(BaseModel): + """ + A model representing inputs in WRROC. + + Attributes: + id (str): The unique identifier for the input. + name (str): The name of the input. + """ + id: str + name: str + + +class WRROCOutputs(BaseModel): + """ + A model representing outputs in WRROC. + + Attributes: + id (str): The unique identifier for the output. + name (str): The name of the output. + """ + id: str + name: str + + +class WRROCDataBase(BaseModel): + """ + A base model representing common fields for WRROC entities. + + Attributes: + id (str): The unique identifier for the WRROC entity. + name (str): The name of the WRROC entity. + description (Optional[str]): A brief description of the WRROC entity. + instrument (Optional[str]): The instrument used in the WRROC entity. + object (list[WRROCInputs]): A list of input objects related to the WRROC entity. + result (list[WRROCOutputs]): A list of output results related to the WRROC entity. + startTime (Optional[str]): The start time of the WRROC entity. + endTime (Optional[str]): The end time of the WRROC entity. + """ + id: str + name: str + description: Optional[str] = "" + instrument: Optional[str] = None + object: list[WRROCInputs] + result: list[WRROCOutputs] + startTime: Optional[str] = None + endTime: Optional[str] = None + + class Config: + extra = "forbid" + + +class WRROCData(WRROCDataBase): + """ + A model representing a WRROC entity, inheriting from WRROCDataBase. + """ + pass + + +class WRROCDataTES(WRROCDataBase): + """ + A model representing WRROC data specifically for TES conversion. + + This model inherits from WRROCDataBase and includes all the necessary fields required for TES conversion. + """ + pass + + +class WRROCDataWES(WRROCDataBase): + """ + A model representing WRROC data specifically for WES conversion. + + This model inherits from WRROCDataBase and includes additional fields required for WES conversion. + """ + status: str + + +class WRROCProcess(BaseModel): + """ + A model representing the WRROC Process Run profile. + + Attributes: + id (str): The unique identifier for the WRROC entity. + name (str): The name of the WRROC entity. + description (Optional[str]): A brief description of the WRROC entity. + startTime (Optional[str]): The start time of the process. + endTime (Optional[str]): The end time of the process. + object (Optional[list[dict[str, str]]]): A list of input objects related to the process. + """ + id: str + name: str + description: Optional[str] = "" + startTime: Optional[str] = None + endTime: Optional[str] = None + object: Optional[list[dict[str, str]]] = None + + class Config: + extra = "forbid" + + +class WRROCWorkflow(WRROCProcess): + """ + A model representing the WRROC Workflow Run profile, inheriting from WRROCProcess. + + Attributes: + workflowType (Optional[str]): The type of the workflow. + workflowVersion (Optional[str]): The version of the workflow. + result (Optional[list[dict[str, str]]]): A list of output results related to the workflow. + """ + workflowType: Optional[str] = None + workflowVersion: Optional[str] = None + result: Optional[list[dict[str, str]]] = None + + class Config: + extra = "forbid" + + +class WRROCProvenance(WRROCWorkflow): + """ + A model representing the WRROC Provenance Run profile, inheriting from WRROCWorkflow. + + Attributes: + provenanceData (Optional[str]): Data related to the provenance of the workflow. + agents (Optional[list[dict[str, str]]]): A list of agents involved in the workflow. + """ + provenanceData: Optional[str] = None + agents: Optional[list[dict[str, str]]] = None + + class Config: + extra = "forbid" diff --git a/crategen/validators.py b/crategen/validators.py new file mode 100644 index 0000000..af19e8d --- /dev/null +++ b/crategen/validators.py @@ -0,0 +1,109 @@ +from pydantic import ValidationError +from typing import Union +from .models import WRROCProcess, WRROCWorkflow, WRROCProvenance +from urllib.parse import urlparse + +def validate_wrroc(data: dict) -> Union[WRROCProvenance, WRROCWorkflow, WRROCProcess]: + """ + Validate that the input data is a valid WRROC entity and determine which profile it adheres to. + + This function attempts to validate the input data against the WRROCProvenance model first. + If that validation fails, it attempts validation against the WRROCWorkflow model. + If that also fails, it finally attempts validation against the WRROCProcess model. + + Args: + data (dict): The input data to validate. + + Returns: + Union[WRROCProvenance, WRROCWorkflow, WRROCProcess]: The validated WRROC data, indicating the highest profile the data adheres to. + + Raises: + ValueError: If the data does not adhere to any of the WRROC profiles. + """ + # Convert '@id' to 'id' for validation purposes + if '@id' in data: + data['id'] = data.pop('@id') + + errors = [] + + try: + return WRROCProvenance(**data) + except ValidationError as e: + errors.extend(e.errors()) + + try: + return WRROCWorkflow(**data) + except ValidationError as e: + errors.extend(e.errors()) + + try: + return WRROCProcess(**data) + except ValidationError as e: + errors.extend(e.errors()) + raise ValueError(f"Invalid WRROC data: {errors}") + +def validate_wrroc_tes(data: dict) -> WRROCProcess: + """ + Validate that the input data contains the fields required for WRROC to TES conversion. + + This function first validates that the data is a valid WRROC entity by calling `validate_wrroc`. + Then it checks that the data contains all necessary fields for TES conversion. + + Args: + data (dict): The input data to validate. + + Returns: + WRROCProcess: The validated WRROC data that is suitable for TES conversion. + + Raises: + ValueError: If the data is not valid WRROC data or does not contain the necessary fields for TES conversion. + """ + validated_data = validate_wrroc(data) + required_fields = ["id", "name", "object", "result"] + + missing_fields = [field for field in required_fields if getattr(validated_data, field) is None] + + if missing_fields: + raise ValueError(f"Missing required field(s) for TES conversion: {', '.join(missing_fields)}") + + return validated_data + +def validate_wrroc_wes(data: dict) -> WRROCWorkflow: + """ + Validate that the input data contains the fields required for WRROC to WES conversion. + + This function first validates that the data is a valid WRROCWorkflow entity by calling `validate_wrroc`. + Then it checks that the data contains all necessary fields for WES conversion. + + Args: + data (dict): The input data to validate. + + Returns: + WRROCWorkflow: The validated WRROC data that is suitable for WES conversion. + + Raises: + ValueError: If the data is not valid WRROC data or does not contain the necessary fields for WES conversion. + """ + validated_data = validate_wrroc(data) + + if not isinstance(validated_data, WRROCWorkflow): + raise ValueError("The validated data is not a WRROCWorkflow entity.") + + required_fields = ["id", "name", "workflowType", "workflowVersion", "result"] + + missing_fields = [field for field in required_fields if getattr(validated_data, field) is None] + + if missing_fields: + raise ValueError(f"Missing required field(s) for WES conversion: {', '.join(missing_fields)}") + + # Validate URLs in the result field, only if result is not None + if validated_data.result is not None: + for result in validated_data.result: + url = result['id'] + parsed_url = urlparse(url) + if not all([parsed_url.scheme, parsed_url.netloc]): + raise ValueError(f"Invalid URL in result: {url}") + + return validated_data + + diff --git a/tests/unit/test_wrroc_models.py b/tests/unit/test_wrroc_models.py new file mode 100644 index 0000000..af02c66 --- /dev/null +++ b/tests/unit/test_wrroc_models.py @@ -0,0 +1,235 @@ +import unittest +from pydantic import ValidationError + +from crategen.models import WRROCProcess, WRROCWorkflow, WRROCProvenance +from crategen.validators import validate_wrroc, validate_wrroc_tes, validate_wrroc_wes + +class TestWRROCModels(unittest.TestCase): + """ + Unit tests for the WRROC models to ensure they work as expected. + """ + + def test_wrroc_process_model(self): + """ + Test that the WRROCProcess model correctly validates data. + """ + data = { + "id": "process-id", + "name": "Test Process", + "description": "A simple process", + "startTime": "2024-07-10T14:30:00Z", + "endTime": "2024-07-10T15:30:00Z", + "object": [{"id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/README.md", "name": "Input 1"}] + } + model = WRROCProcess(**data) + self.assertEqual(model.id, "process-id") + self.assertEqual(model.name, "Test Process") + + def test_wrroc_process_empty_object_list(self): + """ + Test that the WRROCProcess model handles empty object lists correctly. + """ + data = { + "id": "process-id", + "name": "Test Process", + "object": [] + } + model = WRROCProcess(**data) + self.assertEqual(model.object, []) + + def test_wrroc_workflow_model(self): + """ + Test that the WRROCWorkflow model correctly validates data and includes additional workflow fields. + """ + data = { + "id": "workflow-id", + "name": "Test Workflow", + "workflowType": "CWL", + "workflowVersion": "v1.0", + "result": [{"id": "https://github.com/elixir-cloud-aai/CrateGen/blob/main/LICENSE", "name": "Output 1"}] + } + model = WRROCWorkflow(**data) + self.assertEqual(model.workflowType, "CWL") + self.assertEqual(model.result[0]['name'], "Output 1") + + def test_wrroc_workflow_missing_optional_fields(self): + """ + Test that the WRROCWorkflow model handles missing optional fields correctly. + """ + data = { + "id": "workflow-id", + "name": "Test Workflow" + } + model = WRROCWorkflow(**data) + self.assertIsNone(model.workflowType) + self.assertIsNone(model.workflowVersion) + + def test_wrroc_provenance_model(self): + """ + Test that the WRROCProvenance model correctly validates data and includes additional provenance fields. + """ + data = { + "id": "provenance-id", + "name": "Test Provenance", + "provenanceData": "Provenance information", + "agents": [{"id": "agent1", "name": "Agent 1"}] + } + model = WRROCProvenance(**data) + self.assertEqual(model.provenanceData, "Provenance information") + self.assertEqual(model.agents[0]['name'], "Agent 1") + + def test_wrroc_provenance_empty_agents_list(self): + """ + Test that the WRROCProvenance model handles empty agents lists correctly. + """ + data = { + "id": "provenance-id", + "name": "Test Provenance", + "agents": [] + } + model = WRROCProvenance(**data) + self.assertEqual(model.agents, []) + + def test_wrroc_process_invalid_data(self): + """ + Test that the WRROCProcess model raises a ValidationError with invalid data. + """ + data = { + "id": 123, # id should be a string + "name": None # name should be a string + } + with self.assertRaises(ValidationError): + WRROCProcess(**data) + +class TestWRROCValidators(unittest.TestCase): + """ + Unit tests for the WRROC validators to ensure they work as expected. + """ + + def test_validate_wrroc_process(self): + """ + Test that validate_wrroc correctly identifies a WRROCProcess entity. + """ + data = { + "id": "process-id", + "name": "Test Process" + } + model = validate_wrroc(data) + self.assertIsInstance(model, WRROCProcess) + + def test_validate_wrroc_workflow(self): + """ + Test that validate_wrroc correctly identifies a WRROCWorkflow entity. + """ + data = { + "id": "workflow-id", + "name": "Test Workflow", + "workflowType": "CWL", + "workflowVersion": "v1.0" + } + model = validate_wrroc(data) + self.assertIsInstance(model, WRROCWorkflow) + + def test_validate_wrroc_provenance(self): + """ + Test that validate_wrroc correctly identifies a WRROCProvenance entity. + """ + data = { + "id": "provenance-id", + "name": "Test Provenance", + "provenanceData": "Provenance information" + } + model = validate_wrroc(data) + self.assertIsInstance(model, WRROCProvenance) + + def test_validate_wrroc_invalid(self): + """ + Test that validate_wrroc raises a ValueError for invalid WRROC data. + """ + data = { + "unknown_field": "unexpected" + } + with self.assertRaises(ValueError): + validate_wrroc(data) + + def test_validate_wrroc_tes(self): + """ + Test that validate_wrroc_tes correctly validates a WRROC entity for TES conversion. + """ + data = { + "id": "process-id", + "name": "Test Process", + "object": [{"id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/README.md", "name": "Input 1"}], + "result": [{"id": "https://github.com/elixir-cloud-aai/CrateGen/blob/main/LICENSE", "name": "Output 1"}] + } + model = validate_wrroc_tes(data) + self.assertEqual(model.id, "process-id") + self.assertEqual(model.name, "Test Process") + + def test_validate_wrroc_tes_empty_object_list(self): + """ + Test that validate_wrroc_tes correctly validates a WRROC entity with an empty object list for TES conversion. + """ + data = { + "id": "process-id", + "name": "Test Process", + "object": [], + "result": [{"id": "https://github.com/elixir-cloud-aai/CrateGen/blob/main/LICENSE", "name": "Output 1"}] + } + model = validate_wrroc_tes(data) + self.assertEqual(model.object, []) + + def test_validate_wrroc_tes_missing_fields(self): + """ + Test that validate_wrroc_tes raises a ValueError if required fields for TES conversion are missing. + """ + data = { + "id": "process-id", + "name": "Test Process" + } + with self.assertRaises(ValueError): + validate_wrroc_tes(data) + + def test_validate_wrroc_wes(self): + """ + Test that validate_wrroc_wes correctly validates a WRROC entity for WES conversion. + """ + data = { + "id": "workflow-id", + "name": "Test Workflow", + "workflowType": "CWL", + "workflowVersion": "v1.0", + "result": [{"id": "https://github.com/elixir-cloud-aai/CrateGen/blob/main/LICENSE", "name": "Output 1"}] + } + model = validate_wrroc_wes(data) + self.assertEqual(model.workflowType, "CWL") + self.assertEqual(model.workflowVersion, "v1.0") + + def test_validate_wrroc_wes_invalid_url(self): + """ + Test that validate_wrroc_wes raises a ValueError if a result URL is invalid. + """ + data = { + "id": "workflow-id", + "name": "Test Workflow", + "workflowType": "CWL", + "workflowVersion": "v1.0", + "result": [{"id": "invalid_url", "name": "Output 1"}] + } + with self.assertRaises(ValueError): + validate_wrroc_wes(data) + + + def test_validate_wrroc_wes_missing_fields(self): + """ + Test that validate_wrroc_wes raises a ValueError if required fields for WES conversion are missing. + """ + data = { + "id": "workflow-id", + "name": "Test Workflow" + } + with self.assertRaises(ValueError): + validate_wrroc_wes(data) + +if __name__ == "__main__": + unittest.main() From 92f4814516497b2beabbaff294d4913dc285710b Mon Sep 17 00:00:00 2001 From: karanjot786 Date: Mon, 19 Aug 2024 17:43:10 +0530 Subject: [PATCH 02/10] feat: add WRROC models, validators & unit tests --- crategen/converters/tes_converter.py | 64 +++++++----- crategen/converters/wes_converter.py | 50 +++++---- crategen/models.py | 146 +++++++++++++++++++++------ 3 files changed, 186 insertions(+), 74 deletions(-) diff --git a/crategen/converters/tes_converter.py b/crategen/converters/tes_converter.py index 99e176d..ec2eb3b 100644 --- a/crategen/converters/tes_converter.py +++ b/crategen/converters/tes_converter.py @@ -1,42 +1,56 @@ from .abstract_converter import AbstractConverter from .utils import convert_to_iso8601 +from ..models import TESData, WRROCDataTES +from pydantic import ValidationError class TESConverter(AbstractConverter): def convert_to_wrroc(self, tes_data): - # Validate and extract data with defaults - id = tes_data.get("id", "") - name = tes_data.get("name", "") - description = tes_data.get("description", "") - executors = tes_data.get("executors", [{}]) - inputs = tes_data.get("inputs", []) - outputs = tes_data.get("outputs", []) - creation_time = tes_data.get("creation_time", "") - end_time = tes_data.get("logs", [{}])[0].get("end_time", "") # Corrected to fetch from logs + # Validate TES data + try: + validated_tes_data = TESData(**tes_data) + except ValidationError as e: + raise ValueError(f"Invalid TES data: {e}") + + # Extract validated data + id = validated_tes_data.id + name = validated_tes_data.name + description = validated_tes_data.description + executors = validated_tes_data.executors + inputs = validated_tes_data.inputs + outputs = validated_tes_data.outputs + creation_time = validated_tes_data.creation_time + end_time = validated_tes_data.logs[0].end_time if validated_tes_data.logs else "" # Convert to WRROC wrroc_data = { "@id": id, "name": name, "description": description, - "instrument": executors[0].get("image", None) if executors else None, - "object": [{"@id": input.get("url", ""), "name": input.get("path", "")} for input in inputs], - "result": [{"@id": output.get("url", ""), "name": output.get("path", "")} for output in outputs], + "instrument": executors[0].image if executors else None, + "object": [{"@id": input.url, "name": input.path} for input in inputs], + "result": [{"@id": output.url, "name": output.path} for output in outputs], "startTime": convert_to_iso8601(creation_time), "endTime": convert_to_iso8601(end_time), } return wrroc_data - def convert_from_wrroc(self, wrroc_data): - # Validate and extract data with defaults - id = wrroc_data.get("@id", "") - name = wrroc_data.get("name", "") - description = wrroc_data.get("description", "") - instrument = wrroc_data.get("instrument", "") - object_data = wrroc_data.get("object", []) - result_data = wrroc_data.get("result", []) - start_time = wrroc_data.get("startTime", "") - end_time = wrroc_data.get("endTime", "") + def convert_from_wrroc(self, data): + # Validate WRROC data + try: + validated_data = WRROCDataTES(**data) + except ValidationError as e: + raise ValueError(f"Invalid WRROC data for TES conversion: {e}") + + # Extract validated data + id = validated_data.id + name = validated_data.name + description = validated_data.description + instrument = validated_data.instrument + object_data = validated_data.object + result_data = validated_data.result + start_time = validated_data.startTime + end_time = validated_data.endTime # Convert from WRROC to TES tes_data = { @@ -44,9 +58,9 @@ def convert_from_wrroc(self, wrroc_data): "name": name, "description": description, "executors": [{"image": instrument}], - "inputs": [{"url": obj.get("@id", ""), "path": obj.get("name", "")} for obj in object_data], - "outputs": [{"url": res.get("@id", ""), "path": res.get("name", "")} for res in result_data], + "inputs": [{"url": obj.id, "path": obj.name} for obj in object_data], + "outputs": [{"url": res.id, "path": res.name} for res in result_data], "creation_time": start_time, - "logs": [{"end_time": end_time}], # Added to logs + "logs": [{"end_time": end_time}], } return tes_data diff --git a/crategen/converters/wes_converter.py b/crategen/converters/wes_converter.py index 154b17d..edd438b 100644 --- a/crategen/converters/wes_converter.py +++ b/crategen/converters/wes_converter.py @@ -1,16 +1,24 @@ from .abstract_converter import AbstractConverter from .utils import convert_to_iso8601 +from ..models import WESData, WRROCDataWES +from pydantic import ValidationError class WESConverter(AbstractConverter): def convert_to_wrroc(self, wes_data): - # Validate and extract data with defaults - run_id = wes_data.get("run_id", "") - name = wes_data.get("run_log", {}).get("name", "") - state = wes_data.get("state", "") - start_time = wes_data.get("run_log", {}).get("start_time", "") - end_time = wes_data.get("run_log", {}).get("end_time", "") - outputs = wes_data.get("outputs", {}) + # Validate WES data + try: + validated_wes_data = WESData(**wes_data) + except ValidationError as e: + raise ValueError(f"Invalid WES data: {e}") + + # Extract validated data + run_id = validated_wes_data.run_id + name = validated_wes_data.run_log.name + state = validated_wes_data.state + start_time = validated_wes_data.run_log.start_time + end_time = validated_wes_data.run_log.end_time + outputs = validated_wes_data.outputs # Convert to WRROC wrroc_data = { @@ -19,19 +27,25 @@ def convert_to_wrroc(self, wes_data): "status": state, "startTime": convert_to_iso8601(start_time), "endTime": convert_to_iso8601(end_time), - "result": [{"@id": output.get("location", ""), "name": output.get("name", "")} for output in outputs], + "result": [{"@id": output.location, "name": output.name} for output in outputs], } return wrroc_data - def convert_from_wrroc(self, wrroc_data): - # Validate and extract data with defaults - run_id = wrroc_data.get("@id", "") - name = wrroc_data.get("name", "") - start_time = wrroc_data.get("startTime", "") - end_time = wrroc_data.get("endTime", "") - state = wrroc_data.get("status", "") - result_data = wrroc_data.get("result", []) - + def convert_from_wrroc(self, data): + # Validate WRROC data + try: + validated_data = WRROCDataWES(**data) + except ValidationError as e: + raise ValueError(f"Invalid WRROC data for WES conversion: {e}") + + # Extract validated data + run_id = validated_data.id + name = validated_data.name + start_time = validated_data.startTime + end_time = validated_data.endTime + state = validated_data.status + result_data = validated_data.result + # Convert from WRROC to WES wes_data = { "run_id": run_id, @@ -41,6 +55,6 @@ def convert_from_wrroc(self, wrroc_data): "end_time": end_time, }, "state": state, - "outputs": [{"location": res.get("@id", ""), "name": res.get("name", "")} for res in result_data], + "outputs": [{"location": res.id, "name": res.name} for res in result_data], } return wes_data diff --git a/crategen/models.py b/crategen/models.py index 35ad1c2..92c133e 100644 --- a/crategen/models.py +++ b/crategen/models.py @@ -1,83 +1,147 @@ -from pydantic import BaseModel +from pydantic import BaseModel, AnyUrl, Field, root_validator from typing import Optional class Executor(BaseModel): """ - A model representing an executor in the Task Execution Service (TES). + Represents an executor in the Task Execution Service (TES). Attributes: image (str): The Docker image to be used. command (list[str]): The command to be executed. + workdir (Optional[str]): The working directory for the command. + stdout (Optional[str]): The path to the stdout log. + stderr (Optional[str]): The path to the stderr log. + stdin (Optional[str]): The path to the stdin input. + env (Optional[dict[str, str]]): Environment variables for the command. """ image: str command: list[str] + workdir: Optional[str] = None + stdout: Optional[str] = None + stderr: Optional[str] = None + stdin: Optional[str] = None + env: Optional[dict[str, str]] = None + + +class TESResources(BaseModel): + """ + Represents the resources required by a TES task. + + Attributes: + cpu_cores (Optional[int]): The number of CPU cores required. + preemptible (Optional[bool]): Whether the task can run on preemptible instances. + ram_gb (Optional[float]): The amount of RAM in GB required. + disk_gb (Optional[float]): The amount of disk space in GB required. + zones (Optional[list[str]]): The zones where the task can run. + """ + cpu_cores: Optional[int] = None + preemptible: Optional[bool] = None + ram_gb: Optional[float] = None + disk_gb: Optional[float] = None + zones: Optional[list[str]] = None class TESInputs(BaseModel): """ - A model representing input files in TES. + Represents input files in TES. Attributes: - url (str): The URL of the input file. + name (Optional[str]): The name of the input file. + description (Optional[str]): A brief description of the input. + url (AnyUrl): The URL of the input file. path (str): The path where the input file should be placed. + type (Optional[str]): The type of input (e.g., FILE, DIRECTORY). + content (Optional[str]): The content of the input file, if provided inline. """ - url: str + name: Optional[str] = None + description: Optional[str] = None + url: AnyUrl path: str + type: Optional[str] = None + content: Optional[str] = None class TESOutputs(BaseModel): """ - A model representing output files in TES. + Represents output files in TES. Attributes: - url (str): The URL of the output file. + name (Optional[str]): The name of the output file. + description (Optional[str]): A brief description of the output. + url (AnyUrl): The URL of the output file. path (str): The path where the output file is stored. + type (Optional[str]): The type of output (e.g., FILE, DIRECTORY). """ - url: str + name: Optional[str] = None + description: Optional[str] = None + url: AnyUrl path: str + type: Optional[str] = None class TESLogs(BaseModel): """ - A model representing logs in TES. + Represents logs in TES. Attributes: + start_time (Optional[str]): The time the task started. end_time (Optional[str]): The time the task ended. + stdout (Optional[str]): The path to the stdout log. + stderr (Optional[str]): The path to the stderr log. + exit_code (Optional[int]): The exit code of the task. + host_ip (Optional[str]): The IP address of the host running the task. + metadata (Optional[dict[str, str]]): Additional metadata associated with the task. """ + start_time: Optional[str] = None end_time: Optional[str] = None + stdout: Optional[str] = None + stderr: Optional[str] = None + exit_code: Optional[int] = None + host_ip: Optional[str] = None + metadata: Optional[dict[str, str]] = None class TESData(BaseModel): """ - A model representing a TES task. + Represents a TES task. Attributes: id (str): The unique identifier for the TES task. - name (str): The name of the TES task. + name (Optional[str]): The name of the TES task. description (Optional[str]): A brief description of the TES task. - executors (list[Executor]): The executors associated with the TES task. + creation_time (Optional[str]): The time the task was created. + state (Optional[str]): The current state of the task. inputs (list[TESInputs]): The inputs to the TES task. outputs (list[TESOutputs]): The outputs of the TES task. - creation_time (str): The time the task was created. - logs (list[TESLogs]): Logs associated with the TES task. + executors (list[Executor]): The executors associated with the TES task. + resources (Optional[TESResources]): The resources required by the TES task. + volumes (Optional[list[str]]): The volumes to be mounted in the task. + logs (Optional[list[TESLogs]]): Logs associated with the TES task. + tags (Optional[dict[str, str]]): Tags associated with the task. + error (Optional[dict[str, str]]): Error information if the task failed. """ id: str - name: str - description: Optional[str] = "" - executors: list[Executor] + name: Optional[str] = None + description: Optional[str] = None + creation_time: Optional[str] = None + state: Optional[str] = None inputs: list[TESInputs] outputs: list[TESOutputs] - creation_time: str - logs: list[TESLogs] + executors: list[Executor] + resources: Optional[TESResources] = None + volumes: Optional[list[str]] = None + logs: Optional[list[TESLogs]] = None + tags: Optional[dict[str, str]] = None + error: Optional[dict[str, str]] = None class Config: - extra = "forbid" + extra = "allow" class WESRunLog(BaseModel): """ - A model representing a run log in the Workflow Execution Service (WES). + Represents a run log in the Workflow Execution Service (WES). Attributes: name (Optional[str]): The name of the run. @@ -87,6 +151,7 @@ class WESRunLog(BaseModel): stdout (Optional[str]): The path to the stdout log. stderr (Optional[str]): The path to the stderr log. exit_code (Optional[int]): The exit code of the run. + tes_logs_url (Optional[str]): The URL of the TES logs. """ name: Optional[str] = None start_time: Optional[str] = None @@ -95,11 +160,12 @@ class WESRunLog(BaseModel): stdout: Optional[str] = None stderr: Optional[str] = None exit_code: Optional[int] = None + tes_logs_url: Optional[str] = None class WESOutputs(BaseModel): """ - A model representing output files in WES. + Represents output files in WES. Attributes: location (str): The URL of the output file. @@ -111,7 +177,7 @@ class WESOutputs(BaseModel): class WESRequest(BaseModel): """ - A model representing a workflow request in WES. + Represents a workflow request in WES. Attributes: workflow_params (dict[str, str]): The parameters for the workflow. @@ -127,7 +193,7 @@ class WESRequest(BaseModel): class WESData(BaseModel): """ - A model representing a WES run. + Represents a WES run. Attributes: run_id (str): The unique identifier for the WES run. @@ -141,12 +207,17 @@ class WESData(BaseModel): request: WESRequest state: str run_log: WESRunLog - task_logs: Optional[list[WESRunLog]] = None + task_logs: Optional[list[WESRunLog]] = Field(None, description="This field is deprecated. Use tes_logs_url instead.") outputs: list[WESOutputs] class Config: - extra = "forbid" - + extra = "allow" + + @root_validator + def check_deprecated_fields(cls, values): + if values.get('task_logs') is not None: + print("DeprecationWarning: The 'task_logs' field is deprecated and will be removed in future versions. Use 'tes_logs_url' instead.") + return values class WRROCInputs(BaseModel): """ @@ -185,6 +256,7 @@ class WRROCDataBase(BaseModel): result (list[WRROCOutputs]): A list of output results related to the WRROC entity. startTime (Optional[str]): The start time of the WRROC entity. endTime (Optional[str]): The end time of the WRROC entity. + version (Optional[str]): The version of the WRROC entity. """ id: str name: str @@ -194,9 +266,10 @@ class WRROCDataBase(BaseModel): result: list[WRROCOutputs] startTime: Optional[str] = None endTime: Optional[str] = None + version: Optional[str] = None class Config: - extra = "forbid" + extra = "allow" class WRROCData(WRROCDataBase): @@ -235,6 +308,7 @@ class WRROCProcess(BaseModel): startTime (Optional[str]): The start time of the process. endTime (Optional[str]): The end time of the process. object (Optional[list[dict[str, str]]]): A list of input objects related to the process. + profiles (Optional[list[AnyUrl]]): URLs to the RO-Crate profiles used. """ id: str name: str @@ -242,9 +316,10 @@ class WRROCProcess(BaseModel): startTime: Optional[str] = None endTime: Optional[str] = None object: Optional[list[dict[str, str]]] = None + profiles: Optional[list[AnyUrl]] = None class Config: - extra = "forbid" + extra = "allow" class WRROCWorkflow(WRROCProcess): @@ -255,13 +330,15 @@ class WRROCWorkflow(WRROCProcess): workflowType (Optional[str]): The type of the workflow. workflowVersion (Optional[str]): The version of the workflow. result (Optional[list[dict[str, str]]]): A list of output results related to the workflow. + hasPart (Optional[list[AnyUrl]]): A list of parts or steps within the workflow. """ workflowType: Optional[str] = None workflowVersion: Optional[str] = None result: Optional[list[dict[str, str]]] = None + hasPart: Optional[list[AnyUrl]] = None class Config: - extra = "forbid" + extra = "allow" class WRROCProvenance(WRROCWorkflow): @@ -271,9 +348,16 @@ class WRROCProvenance(WRROCWorkflow): Attributes: provenanceData (Optional[str]): Data related to the provenance of the workflow. agents (Optional[list[dict[str, str]]]): A list of agents involved in the workflow. + activity (Optional[list[dict[str, str]]]): Activities related to the provenance. + generatedBy (Optional[list[AnyUrl]]): URLs of the entities that generated the data. + used (Optional[list[AnyUrl]]): URLs of the entities that were used in the data generation. """ provenanceData: Optional[str] = None agents: Optional[list[dict[str, str]]] = None + activity: Optional[list[dict[str, str]]] = None + generatedBy: Optional[list[AnyUrl]] = None + used: Optional[list[AnyUrl]] = None class Config: - extra = "forbid" + extra = "allow" + From 9eca26162bc47c9b064dee84eaac3b95750907bd Mon Sep 17 00:00:00 2001 From: karanjot786 Date: Thu, 22 Aug 2024 21:29:22 +0530 Subject: [PATCH 03/10] feat: add WRROC models, validators & unit tests --- crategen/converters/tes_converter.py | 91 ++++++++++++------------- crategen/converters/wes_converter.py | 79 ++++++++++++---------- crategen/validators.py | 93 +++++++++----------------- tests/unit/test_wrroc_models.py | 99 +++++++++++++++++----------- 4 files changed, 180 insertions(+), 182 deletions(-) diff --git a/crategen/converters/tes_converter.py b/crategen/converters/tes_converter.py index ec2eb3b..7f2ac07 100644 --- a/crategen/converters/tes_converter.py +++ b/crategen/converters/tes_converter.py @@ -4,63 +4,66 @@ from pydantic import ValidationError class TESConverter(AbstractConverter): + def convert_to_wrroc(self, data: dict) -> dict: + """ + Convert TES data to WRROC format. - def convert_to_wrroc(self, tes_data): + Args: + data (dict): The input TES data. + + Returns: + dict: The converted WRROC data. + + Raises: + ValidationError: If TES data is invalid. + """ # Validate TES data try: - validated_tes_data = TESData(**tes_data) + data_tes = TESData(**data) except ValidationError as e: - raise ValueError(f"Invalid TES data: {e}") + raise ValueError(f"Invalid TES data: {e.errors()}") from e - # Extract validated data - id = validated_tes_data.id - name = validated_tes_data.name - description = validated_tes_data.description - executors = validated_tes_data.executors - inputs = validated_tes_data.inputs - outputs = validated_tes_data.outputs - creation_time = validated_tes_data.creation_time - end_time = validated_tes_data.logs[0].end_time if validated_tes_data.logs else "" - - # Convert to WRROC + # Convert to WRROC format wrroc_data = { - "@id": id, - "name": name, - "description": description, - "instrument": executors[0].image if executors else None, - "object": [{"@id": input.url, "name": input.path} for input in inputs], - "result": [{"@id": output.url, "name": output.path} for output in outputs], - "startTime": convert_to_iso8601(creation_time), - "endTime": convert_to_iso8601(end_time), + "@id": data_tes.id, + "name": data_tes.name, + "description": data_tes.description, + "instrument": data_tes.executors[0].image if data_tes.executors else None, + "object": [{"@id": input.url, "name": input.path} for input in data_tes.inputs], + "result": [{"@id": output.url, "name": output.path} for output in data_tes.outputs], + "startTime": convert_to_iso8601(data_tes.creation_time), + "endTime": convert_to_iso8601(data_tes.logs[0].end_time if data_tes.logs else ""), } return wrroc_data - def convert_from_wrroc(self, data): + def convert_from_wrroc(self, data: dict) -> dict: + """ + Convert WRROC data to TES format. + + Args: + data (dict): The input WRROC data. + + Returns: + dict: The converted TES data. + + Raises: + ValidationError: If WRROC data is invalid. + """ # Validate WRROC data try: - validated_data = WRROCDataTES(**data) + data_wrroc = WRROCDataTES(**data) except ValidationError as e: - raise ValueError(f"Invalid WRROC data for TES conversion: {e}") - - # Extract validated data - id = validated_data.id - name = validated_data.name - description = validated_data.description - instrument = validated_data.instrument - object_data = validated_data.object - result_data = validated_data.result - start_time = validated_data.startTime - end_time = validated_data.endTime + raise ValueError(f"Invalid WRROC data: {e.errors()}") from e - # Convert from WRROC to TES + # Convert from WRROC to TES format tes_data = { - "id": id, - "name": name, - "description": description, - "executors": [{"image": instrument}], - "inputs": [{"url": obj.id, "path": obj.name} for obj in object_data], - "outputs": [{"url": res.id, "path": res.name} for res in result_data], - "creation_time": start_time, - "logs": [{"end_time": end_time}], + "id": data_wrroc.id, + "name": data_wrroc.name, + "description": data_wrroc.description, + "executors": [{"image": data_wrroc.instrument}], + "inputs": [{"url": obj.id, "path": obj.name} for obj in data_wrroc.object], + "outputs": [{"url": res.id, "path": res.name} for res in data_wrroc.result], + "creation_time": data_wrroc.startTime, + "logs": [{"end_time": data_wrroc.endTime}], } return tes_data diff --git a/crategen/converters/wes_converter.py b/crategen/converters/wes_converter.py index edd438b..4d50a3c 100644 --- a/crategen/converters/wes_converter.py +++ b/crategen/converters/wes_converter.py @@ -4,57 +4,64 @@ from pydantic import ValidationError class WESConverter(AbstractConverter): + def convert_to_wrroc(self, data: dict) -> dict: + """ + Convert WES data to WRROC format. - def convert_to_wrroc(self, wes_data): + Args: + data (dict): The input WES data. + + Returns: + dict: The converted WRROC data. + + Raises: + ValidationError: If WES data is invalid. + """ # Validate WES data try: - validated_wes_data = WESData(**wes_data) + data_wes = WESData(**data) except ValidationError as e: - raise ValueError(f"Invalid WES data: {e}") + raise ValueError(f"Invalid WES data: {e.errors()}") from e - # Extract validated data - run_id = validated_wes_data.run_id - name = validated_wes_data.run_log.name - state = validated_wes_data.state - start_time = validated_wes_data.run_log.start_time - end_time = validated_wes_data.run_log.end_time - outputs = validated_wes_data.outputs - - # Convert to WRROC + # Convert to WRROC format wrroc_data = { - "@id": run_id, - "name": name, - "status": state, - "startTime": convert_to_iso8601(start_time), - "endTime": convert_to_iso8601(end_time), - "result": [{"@id": output.location, "name": output.name} for output in outputs], + "@id": data_wes.run_id, + "name": data_wes.run_log.name, + "status": data_wes.state, + "startTime": convert_to_iso8601(data_wes.run_log.start_time), + "endTime": convert_to_iso8601(data_wes.run_log.end_time), + "result": [{"@id": output.location, "name": output.name} for output in data_wes.outputs], } return wrroc_data - def convert_from_wrroc(self, data): + def convert_from_wrroc(self, data: dict) -> dict: + """ + Convert WRROC data to WES format. + + Args: + data (dict): The input WRROC data. + + Returns: + dict: The converted WES data. + + Raises: + ValidationError: If WRROC data is invalid. + """ # Validate WRROC data try: - validated_data = WRROCDataWES(**data) + data_wrroc = WRROCDataWES(**data) except ValidationError as e: - raise ValueError(f"Invalid WRROC data for WES conversion: {e}") - - # Extract validated data - run_id = validated_data.id - name = validated_data.name - start_time = validated_data.startTime - end_time = validated_data.endTime - state = validated_data.status - result_data = validated_data.result + raise ValueError(f"Invalid WRROC data for WES conversion: {e.errors()}") from e - # Convert from WRROC to WES + # Convert from WRROC to WES format wes_data = { - "run_id": run_id, + "run_id": data_wrroc.id, "run_log": { - "name": name, - "start_time": start_time, - "end_time": end_time, + "name": data_wrroc.name, + "start_time": data_wrroc.startTime, + "end_time": data_wrroc.endTime, }, - "state": state, - "outputs": [{"location": res.id, "name": res.name} for res in result_data], + "state": data_wrroc.status, + "outputs": [{"location": res.id, "name": res.name} for res in data_wrroc.result], } return wes_data diff --git a/crategen/validators.py b/crategen/validators.py index af19e8d..579ebcf 100644 --- a/crategen/validators.py +++ b/crategen/validators.py @@ -1,109 +1,78 @@ from pydantic import ValidationError from typing import Union -from .models import WRROCProcess, WRROCWorkflow, WRROCProvenance +from .models import WRROCProcess, WRROCWorkflow, WRROCProvenance, WRROCDataTES, WRROCDataWES from urllib.parse import urlparse def validate_wrroc(data: dict) -> Union[WRROCProvenance, WRROCWorkflow, WRROCProcess]: """ Validate that the input data is a valid WRROC entity and determine which profile it adheres to. - + This function attempts to validate the input data against the WRROCProvenance model first. If that validation fails, it attempts validation against the WRROCWorkflow model. If that also fails, it finally attempts validation against the WRROCProcess model. - - Args: - data (dict): The input data to validate. - + Returns: Union[WRROCProvenance, WRROCWorkflow, WRROCProcess]: The validated WRROC data, indicating the highest profile the data adheres to. - + Raises: ValueError: If the data does not adhere to any of the WRROC profiles. """ - # Convert '@id' to 'id' for validation purposes - if '@id' in data: - data['id'] = data.pop('@id') - - errors = [] - try: return WRROCProvenance(**data) - except ValidationError as e: - errors.extend(e.errors()) + except ValidationError: + pass try: return WRROCWorkflow(**data) - except ValidationError as e: - errors.extend(e.errors()) + except ValidationError: + pass try: return WRROCProcess(**data) except ValidationError as e: - errors.extend(e.errors()) - raise ValueError(f"Invalid WRROC data: {errors}") + raise ValueError(f"Invalid WRROC data: {e.errors()}") from e -def validate_wrroc_tes(data: dict) -> WRROCProcess: +def validate_wrroc_tes(data: dict) -> WRROCDataTES: """ Validate that the input data contains the fields required for WRROC to TES conversion. - This function first validates that the data is a valid WRROC entity by calling `validate_wrroc`. - Then it checks that the data contains all necessary fields for TES conversion. - - Args: - data (dict): The input data to validate. - Returns: - WRROCProcess: The validated WRROC data that is suitable for TES conversion. + WRROCDataTES: The validated WRROC data that is suitable for TES conversion. Raises: ValueError: If the data is not valid WRROC data or does not contain the necessary fields for TES conversion. """ - validated_data = validate_wrroc(data) - required_fields = ["id", "name", "object", "result"] - - missing_fields = [field for field in required_fields if getattr(validated_data, field) is None] - - if missing_fields: - raise ValueError(f"Missing required field(s) for TES conversion: {', '.join(missing_fields)}") + data_validated = validate_wrroc(data) + + try: + data_wrroc_tes = WRROCDataTES(**data_validated.dict()) + except ValidationError as exc: + raise ValueError(f"WRROC data insufficient for TES conversion: {exc.errors()}") from exc - return validated_data + return data_wrroc_tes -def validate_wrroc_wes(data: dict) -> WRROCWorkflow: +def validate_wrroc_wes(data: dict) -> WRROCDataWES: """ Validate that the input data contains the fields required for WRROC to WES conversion. - This function first validates that the data is a valid WRROCWorkflow entity by calling `validate_wrroc`. - Then it checks that the data contains all necessary fields for WES conversion. - - Args: - data (dict): The input data to validate. - Returns: - WRROCWorkflow: The validated WRROC data that is suitable for WES conversion. + WRROCDataWES: The validated WRROC data that is suitable for WES conversion. Raises: ValueError: If the data is not valid WRROC data or does not contain the necessary fields for WES conversion. """ - validated_data = validate_wrroc(data) - - if not isinstance(validated_data, WRROCWorkflow): - raise ValueError("The validated data is not a WRROCWorkflow entity.") - - required_fields = ["id", "name", "workflowType", "workflowVersion", "result"] - - missing_fields = [field for field in required_fields if getattr(validated_data, field) is None] - - if missing_fields: - raise ValueError(f"Missing required field(s) for WES conversion: {', '.join(missing_fields)}") + data_validated = validate_wrroc(data) + + try: + data_wrroc_wes = WRROCDataWES(**data_validated.dict()) + except ValidationError as exc: + raise ValueError(f"WRROC data insufficient for WES conversion: {exc.errors()}") from exc # Validate URLs in the result field, only if result is not None - if validated_data.result is not None: - for result in validated_data.result: - url = result['id'] - parsed_url = urlparse(url) + if data_wrroc_wes.result is not None: + for result in data_wrroc_wes.result: + parsed_url = urlparse(result.id) if not all([parsed_url.scheme, parsed_url.netloc]): - raise ValueError(f"Invalid URL in result: {url}") - - return validated_data - + raise ValueError(f"Invalid URL in result: {result.id}") + return data_wrroc_wes diff --git a/tests/unit/test_wrroc_models.py b/tests/unit/test_wrroc_models.py index af02c66..26dec69 100644 --- a/tests/unit/test_wrroc_models.py +++ b/tests/unit/test_wrroc_models.py @@ -1,12 +1,12 @@ import unittest from pydantic import ValidationError -from crategen.models import WRROCProcess, WRROCWorkflow, WRROCProvenance +from crategen.models import WRROCProcess, WRROCWorkflow, WRROCProvenance, WRROCDataWES from crategen.validators import validate_wrroc, validate_wrroc_tes, validate_wrroc_wes -class TestWRROCModels(unittest.TestCase): +class TestWRROCProcessModel(unittest.TestCase): """ - Unit tests for the WRROC models to ensure they work as expected. + Unit tests for the WRROCProcess model. """ def test_wrroc_process_model(self): @@ -37,6 +37,22 @@ def test_wrroc_process_empty_object_list(self): model = WRROCProcess(**data) self.assertEqual(model.object, []) + def test_wrroc_process_invalid_data(self): + """ + Test that the WRROCProcess model raises a ValidationError with invalid data. + """ + data = { + "id": 123, # id should be a string + "name": None # name should be a string + } + with self.assertRaises(ValidationError): + WRROCProcess(**data) + +class TestWRROCWorkflowModel(unittest.TestCase): + """ + Unit tests for the WRROCWorkflow model. + """ + def test_wrroc_workflow_model(self): """ Test that the WRROCWorkflow model correctly validates data and includes additional workflow fields. @@ -46,7 +62,7 @@ def test_wrroc_workflow_model(self): "name": "Test Workflow", "workflowType": "CWL", "workflowVersion": "v1.0", - "result": [{"id": "https://github.com/elixir-cloud-aai/CrateGen/blob/main/LICENSE", "name": "Output 1"}] + "result": [{"id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/LICENSE", "name": "Output 1"}] } model = WRROCWorkflow(**data) self.assertEqual(model.workflowType, "CWL") @@ -64,6 +80,11 @@ def test_wrroc_workflow_missing_optional_fields(self): self.assertIsNone(model.workflowType) self.assertIsNone(model.workflowVersion) +class TestWRROCProvenanceModel(unittest.TestCase): + """ + Unit tests for the WRROCProvenance model. + """ + def test_wrroc_provenance_model(self): """ Test that the WRROCProvenance model correctly validates data and includes additional provenance fields. @@ -90,17 +111,6 @@ def test_wrroc_provenance_empty_agents_list(self): model = WRROCProvenance(**data) self.assertEqual(model.agents, []) - def test_wrroc_process_invalid_data(self): - """ - Test that the WRROCProcess model raises a ValidationError with invalid data. - """ - data = { - "id": 123, # id should be a string - "name": None # name should be a string - } - with self.assertRaises(ValidationError): - WRROCProcess(**data) - class TestWRROCValidators(unittest.TestCase): """ Unit tests for the WRROC validators to ensure they work as expected. @@ -160,7 +170,7 @@ def test_validate_wrroc_tes(self): "id": "process-id", "name": "Test Process", "object": [{"id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/README.md", "name": "Input 1"}], - "result": [{"id": "https://github.com/elixir-cloud-aai/CrateGen/blob/main/LICENSE", "name": "Output 1"}] + "result": [{"id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/LICENSE", "name": "Output 1"}] } model = validate_wrroc_tes(data) self.assertEqual(model.id, "process-id") @@ -174,7 +184,7 @@ def test_validate_wrroc_tes_empty_object_list(self): "id": "process-id", "name": "Test Process", "object": [], - "result": [{"id": "https://github.com/elixir-cloud-aai/CrateGen/blob/main/LICENSE", "name": "Output 1"}] + "result": [{"id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/LICENSE", "name": "Output 1"}] } model = validate_wrroc_tes(data) self.assertEqual(model.object, []) @@ -191,34 +201,43 @@ def test_validate_wrroc_tes_missing_fields(self): validate_wrroc_tes(data) def test_validate_wrroc_wes(self): - """ - Test that validate_wrroc_wes correctly validates a WRROC entity for WES conversion. - """ - data = { - "id": "workflow-id", - "name": "Test Workflow", - "workflowType": "CWL", - "workflowVersion": "v1.0", - "result": [{"id": "https://github.com/elixir-cloud-aai/CrateGen/blob/main/LICENSE", "name": "Output 1"}] - } - model = validate_wrroc_wes(data) - self.assertEqual(model.workflowType, "CWL") - self.assertEqual(model.workflowVersion, "v1.0") + """ + Test that validate_wrroc_wes correctly validates a WRROC entity for WES conversion. + """ + data = { + "id": "workflow-id", + "name": "Test Workflow", + "workflowType": "CWL", + "workflowVersion": "v1.0", + "status": "completed", + "object": [{"id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/README.md", "name": "Input 1"}], + "result": [{"id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/LICENSE", "name": "Output 1"}] + } + model = validate_wrroc_wes(data) + self.assertIsInstance(model, WRROCDataWES) + def test_validate_wrroc_wes_invalid_url(self): """ Test that validate_wrroc_wes raises a ValueError if a result URL is invalid. """ - data = { - "id": "workflow-id", - "name": "Test Workflow", - "workflowType": "CWL", - "workflowVersion": "v1.0", - "result": [{"id": "invalid_url", "name": "Output 1"}] - } - with self.assertRaises(ValueError): - validate_wrroc_wes(data) - + invalid_urls = [ + "invalid_url", + "http:/github.com", + "ftp://github.com", + "//github.com", + "http://", + ] + for url in invalid_urls: + data = { + "id": "workflow-id", + "name": "Test Workflow", + "workflowType": "CWL", + "workflowVersion": "v1.0", + "result": [{"id": url, "name": "Output 1"}] + } + with self.assertRaises(ValueError): + validate_wrroc_wes(data) def test_validate_wrroc_wes_missing_fields(self): """ From 069397fc141ab1afc33641752dd4a5c7ac1009de Mon Sep 17 00:00:00 2001 From: Salihu <91833785+SalihuDickson@users.noreply.github.com> Date: Sat, 24 Aug 2024 23:18:34 +0100 Subject: [PATCH 04/10] Refactor/improve tes models (#24) Co-authored-by: salihuDickson --- .github/workflows/ci.yml | 67 ++-- crategen/cli.py | 33 +- crategen/converter_manager.py | 1 + crategen/converters/abstract_converter.py | 3 +- crategen/converters/tes_converter.py | 46 ++- crategen/converters/wes_converter.py | 22 +- crategen/models.py | 363 ---------------------- crategen/models/__init__.py | 0 crategen/models/tes_models.py | 268 ++++++++++++++++ crategen/models/wes_models.py | 92 ++++++ crategen/models/wrroc_models.py | 155 +++++++++ crategen/{converters => }/utils.py | 13 +- crategen/validators.py | 28 +- lefthook.yml | 35 +++ pyproject.toml | 17 +- tests/unit/test_wrroc_models.py | 137 ++++---- 16 files changed, 782 insertions(+), 498 deletions(-) create mode 100644 crategen/models/__init__.py create mode 100644 crategen/models/tes_models.py create mode 100644 crategen/models/wes_models.py create mode 100644 crategen/models/wrroc_models.py rename crategen/{converters => }/utils.py (65%) create mode 100644 lefthook.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0d1b757..237b6f1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,43 +2,44 @@ name: CI on: push: - branches: [ main ] + branches: '*' pull_request: - branches: [ main ] + branches: '*' jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.11' - - - name: Install Poetry - run: | - curl -sSL https://install.python-poetry.org | python3 - - poetry install - - - name: Lint with Ruff - run: | - poetry run ruff check crategen/ - - - name: Type check with Mypy - run: | - poetry run mypy crategen/ - - - name: Run security checks with Bandit - run: | - poetry run bandit -r crategen/ - - - name: Install test dependencies - run: | - poetry add pytest pytest-cov pytest-mock - - - name: Run tests - run: | - poetry run pytest --cov=crategen + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.11' + + - name: Install Poetry + run: | + curl -sSL https://install.python-poetry.org | python3 - + poetry install + + - name: Lint with Ruff + run: | + poetry run ruff check crategen/ + if: ${{ success() }} + + - name: Type check with Mypy + run: | + poetry run mypy crategen/ + + - name: Run security checks with Bandit + run: | + poetry run bandit -r crategen/ + + - name: Install test dependencies + run: | + poetry add pytest pytest-cov pytest-mock + + - name: Run tests + run: | + poetry run pytest --cov=crategen diff --git a/crategen/cli.py b/crategen/cli.py index 20198fb..3f207b2 100644 --- a/crategen/cli.py +++ b/crategen/cli.py @@ -1,30 +1,37 @@ -import click import json + +import click + from crategen.converter_manager import ConverterManager + @click.command() -@click.option('--input', prompt='Input file', help='Path to the input JSON file.') -@click.option('--output', prompt='Output file', help='Path to the output JSON file.') -@click.option('--conversion-type', prompt='Conversion type', type=click.Choice(['tes-to-wrroc', 'wes-to-wrroc']), help='Type of conversion to perform.') +@click.option("--input", prompt="Input file", help="Path to the input JSON file.") +@click.option("--output", prompt="Output file", help="Path to the output JSON file.") +@click.option( + "--conversion-type", + prompt="Conversion type", + type=click.Choice(["tes-to-wrroc", "wes-to-wrroc"]), + help="Type of conversion to perform.", +) def cli(input, output, conversion_type): - """ - Command Line Interface for converting TES/WES to WRROC. - """ + """Command Line Interface for converting TES/WES to WRROC.""" manager = ConverterManager() # Load input data from JSON file - with open(input, 'r') as input_file: + with open(input) as input_file: data = json.load(input_file) # Perform the conversion based on the specified type - if conversion_type == 'tes-to-wrroc': + if conversion_type == "tes-to-wrroc": result = manager.convert_tes_to_wrroc(data) - elif conversion_type == 'wes-to-wrroc': + elif conversion_type == "wes-to-wrroc": result = manager.convert_wes_to_wrroc(data) - + # Save the result to the output JSON file - with open(output, 'w') as output_file: + with open(output, "w") as output_file: json.dump(result, output_file, indent=4) -if __name__ == '__main__': + +if __name__ == "__main__": cli() diff --git a/crategen/converter_manager.py b/crategen/converter_manager.py index 3a0ef6b..756a565 100644 --- a/crategen/converter_manager.py +++ b/crategen/converter_manager.py @@ -1,6 +1,7 @@ from .converters.tes_converter import TESConverter from .converters.wes_converter import WESConverter + class ConverterManager: def __init__(self): self.tes_converter = TESConverter() diff --git a/crategen/converters/abstract_converter.py b/crategen/converters/abstract_converter.py index d0cd0ee..f686557 100644 --- a/crategen/converters/abstract_converter.py +++ b/crategen/converters/abstract_converter.py @@ -1,10 +1,11 @@ from abc import ABC, abstractmethod + class AbstractConverter(ABC): @abstractmethod def convert_to_wrroc(self, data): """Convert data to WRROC format""" - + @abstractmethod def convert_from_wrroc(self, wrroc_data): """Convert WRROC data to the original format""" diff --git a/crategen/converters/tes_converter.py b/crategen/converters/tes_converter.py index 7f2ac07..0d7a0b9 100644 --- a/crategen/converters/tes_converter.py +++ b/crategen/converters/tes_converter.py @@ -1,8 +1,10 @@ -from .abstract_converter import AbstractConverter -from .utils import convert_to_iso8601 -from ..models import TESData, WRROCDataTES from pydantic import ValidationError +from ..models.tes_models import TESData +from ..models.wrroc_models import WRROCDataTES +from .abstract_converter import AbstractConverter + + class TESConverter(AbstractConverter): def convert_to_wrroc(self, data: dict) -> dict: """ @@ -23,16 +25,38 @@ def convert_to_wrroc(self, data: dict) -> dict: except ValidationError as e: raise ValueError(f"Invalid TES data: {e.errors()}") from e + # Extract validated data + ( + id, + name, + description, + creation_time, + state, + inputs, + outputs, + executors, + resources, + volumes, + logs, + tags, + ) = data_tes.dict().values() + end_time = logs[0].end_time + # Convert to WRROC format wrroc_data = { - "@id": data_tes.id, - "name": data_tes.name, - "description": data_tes.description, - "instrument": data_tes.executors[0].image if data_tes.executors else None, - "object": [{"@id": input.url, "name": input.path} for input in data_tes.inputs], - "result": [{"@id": output.url, "name": output.path} for output in data_tes.outputs], - "startTime": convert_to_iso8601(data_tes.creation_time), - "endTime": convert_to_iso8601(data_tes.logs[0].end_time if data_tes.logs else ""), + "@id": id, + "name": name, + "description": description, + "instrument": executors[0]["image"] if executors else None, + "object": [ + {"@id": input["url"], "name": input["path"], "type": input["type"]} + for input in inputs + ], + "result": [ + {"@id": output["url"], "name": output["path"]} for output in outputs + ], + "startTime": creation_time, + "endTime": end_time, } return wrroc_data diff --git a/crategen/converters/wes_converter.py b/crategen/converters/wes_converter.py index 4d50a3c..619e069 100644 --- a/crategen/converters/wes_converter.py +++ b/crategen/converters/wes_converter.py @@ -1,8 +1,11 @@ -from .abstract_converter import AbstractConverter -from .utils import convert_to_iso8601 -from ..models import WESData, WRROCDataWES from pydantic import ValidationError +from ..models.wes_models import WESData +from ..models.wrroc_models import WRROCDataWES +from ..utils import convert_to_iso8601 +from .abstract_converter import AbstractConverter + + class WESConverter(AbstractConverter): def convert_to_wrroc(self, data: dict) -> dict: """ @@ -30,7 +33,10 @@ def convert_to_wrroc(self, data: dict) -> dict: "status": data_wes.state, "startTime": convert_to_iso8601(data_wes.run_log.start_time), "endTime": convert_to_iso8601(data_wes.run_log.end_time), - "result": [{"@id": output.location, "name": output.name} for output in data_wes.outputs], + "result": [ + {"@id": output.location, "name": output.name} + for output in data_wes.outputs + ], } return wrroc_data @@ -51,7 +57,9 @@ def convert_from_wrroc(self, data: dict) -> dict: try: data_wrroc = WRROCDataWES(**data) except ValidationError as e: - raise ValueError(f"Invalid WRROC data for WES conversion: {e.errors()}") from e + raise ValueError( + f"Invalid WRROC data for WES conversion: {e.errors()}" + ) from e # Convert from WRROC to WES format wes_data = { @@ -62,6 +70,8 @@ def convert_from_wrroc(self, data: dict) -> dict: "end_time": data_wrroc.endTime, }, "state": data_wrroc.status, - "outputs": [{"location": res.id, "name": res.name} for res in data_wrroc.result], + "outputs": [ + {"location": res.id, "name": res.name} for res in data_wrroc.result + ], } return wes_data diff --git a/crategen/models.py b/crategen/models.py index 92c133e..e69de29 100644 --- a/crategen/models.py +++ b/crategen/models.py @@ -1,363 +0,0 @@ -from pydantic import BaseModel, AnyUrl, Field, root_validator -from typing import Optional - - -class Executor(BaseModel): - """ - Represents an executor in the Task Execution Service (TES). - - Attributes: - image (str): The Docker image to be used. - command (list[str]): The command to be executed. - workdir (Optional[str]): The working directory for the command. - stdout (Optional[str]): The path to the stdout log. - stderr (Optional[str]): The path to the stderr log. - stdin (Optional[str]): The path to the stdin input. - env (Optional[dict[str, str]]): Environment variables for the command. - """ - image: str - command: list[str] - workdir: Optional[str] = None - stdout: Optional[str] = None - stderr: Optional[str] = None - stdin: Optional[str] = None - env: Optional[dict[str, str]] = None - - -class TESResources(BaseModel): - """ - Represents the resources required by a TES task. - - Attributes: - cpu_cores (Optional[int]): The number of CPU cores required. - preemptible (Optional[bool]): Whether the task can run on preemptible instances. - ram_gb (Optional[float]): The amount of RAM in GB required. - disk_gb (Optional[float]): The amount of disk space in GB required. - zones (Optional[list[str]]): The zones where the task can run. - """ - cpu_cores: Optional[int] = None - preemptible: Optional[bool] = None - ram_gb: Optional[float] = None - disk_gb: Optional[float] = None - zones: Optional[list[str]] = None - - -class TESInputs(BaseModel): - """ - Represents input files in TES. - - Attributes: - name (Optional[str]): The name of the input file. - description (Optional[str]): A brief description of the input. - url (AnyUrl): The URL of the input file. - path (str): The path where the input file should be placed. - type (Optional[str]): The type of input (e.g., FILE, DIRECTORY). - content (Optional[str]): The content of the input file, if provided inline. - """ - name: Optional[str] = None - description: Optional[str] = None - url: AnyUrl - path: str - type: Optional[str] = None - content: Optional[str] = None - - -class TESOutputs(BaseModel): - """ - Represents output files in TES. - - Attributes: - name (Optional[str]): The name of the output file. - description (Optional[str]): A brief description of the output. - url (AnyUrl): The URL of the output file. - path (str): The path where the output file is stored. - type (Optional[str]): The type of output (e.g., FILE, DIRECTORY). - """ - name: Optional[str] = None - description: Optional[str] = None - url: AnyUrl - path: str - type: Optional[str] = None - - -class TESLogs(BaseModel): - """ - Represents logs in TES. - - Attributes: - start_time (Optional[str]): The time the task started. - end_time (Optional[str]): The time the task ended. - stdout (Optional[str]): The path to the stdout log. - stderr (Optional[str]): The path to the stderr log. - exit_code (Optional[int]): The exit code of the task. - host_ip (Optional[str]): The IP address of the host running the task. - metadata (Optional[dict[str, str]]): Additional metadata associated with the task. - """ - start_time: Optional[str] = None - end_time: Optional[str] = None - stdout: Optional[str] = None - stderr: Optional[str] = None - exit_code: Optional[int] = None - host_ip: Optional[str] = None - metadata: Optional[dict[str, str]] = None - - -class TESData(BaseModel): - """ - Represents a TES task. - - Attributes: - id (str): The unique identifier for the TES task. - name (Optional[str]): The name of the TES task. - description (Optional[str]): A brief description of the TES task. - creation_time (Optional[str]): The time the task was created. - state (Optional[str]): The current state of the task. - inputs (list[TESInputs]): The inputs to the TES task. - outputs (list[TESOutputs]): The outputs of the TES task. - executors (list[Executor]): The executors associated with the TES task. - resources (Optional[TESResources]): The resources required by the TES task. - volumes (Optional[list[str]]): The volumes to be mounted in the task. - logs (Optional[list[TESLogs]]): Logs associated with the TES task. - tags (Optional[dict[str, str]]): Tags associated with the task. - error (Optional[dict[str, str]]): Error information if the task failed. - """ - id: str - name: Optional[str] = None - description: Optional[str] = None - creation_time: Optional[str] = None - state: Optional[str] = None - inputs: list[TESInputs] - outputs: list[TESOutputs] - executors: list[Executor] - resources: Optional[TESResources] = None - volumes: Optional[list[str]] = None - logs: Optional[list[TESLogs]] = None - tags: Optional[dict[str, str]] = None - error: Optional[dict[str, str]] = None - - class Config: - extra = "allow" - - -class WESRunLog(BaseModel): - """ - Represents a run log in the Workflow Execution Service (WES). - - Attributes: - name (Optional[str]): The name of the run. - start_time (Optional[str]): The start time of the run. - end_time (Optional[str]): The end time of the run. - cmd (Optional[list[str]]): The command executed in the run. - stdout (Optional[str]): The path to the stdout log. - stderr (Optional[str]): The path to the stderr log. - exit_code (Optional[int]): The exit code of the run. - tes_logs_url (Optional[str]): The URL of the TES logs. - """ - name: Optional[str] = None - start_time: Optional[str] = None - end_time: Optional[str] = None - cmd: Optional[list[str]] = None - stdout: Optional[str] = None - stderr: Optional[str] = None - exit_code: Optional[int] = None - tes_logs_url: Optional[str] = None - - -class WESOutputs(BaseModel): - """ - Represents output files in WES. - - Attributes: - location (str): The URL of the output file. - name (str): The name of the output file. - """ - location: str - name: str - - -class WESRequest(BaseModel): - """ - Represents a workflow request in WES. - - Attributes: - workflow_params (dict[str, str]): The parameters for the workflow. - workflow_type (str): The type of the workflow (e.g., CWL). - workflow_type_version (str): The version of the workflow type. - tags (Optional[dict[str, str]]): Additional tags associated with the workflow. - """ - workflow_params: dict[str, str] - workflow_type: str - workflow_type_version: str - tags: Optional[dict[str, str]] = None - - -class WESData(BaseModel): - """ - Represents a WES run. - - Attributes: - run_id (str): The unique identifier for the WES run. - request (WESRequest): The request associated with the WES run. - state (str): The state of the WES run. - run_log (WESRunLog): The log of the WES run. - task_logs (Optional[list[WESRunLog]]): The logs of individual tasks within the run. - outputs (list[WESOutputs]): The outputs of the WES run. - """ - run_id: str - request: WESRequest - state: str - run_log: WESRunLog - task_logs: Optional[list[WESRunLog]] = Field(None, description="This field is deprecated. Use tes_logs_url instead.") - outputs: list[WESOutputs] - - class Config: - extra = "allow" - - @root_validator - def check_deprecated_fields(cls, values): - if values.get('task_logs') is not None: - print("DeprecationWarning: The 'task_logs' field is deprecated and will be removed in future versions. Use 'tes_logs_url' instead.") - return values - -class WRROCInputs(BaseModel): - """ - A model representing inputs in WRROC. - - Attributes: - id (str): The unique identifier for the input. - name (str): The name of the input. - """ - id: str - name: str - - -class WRROCOutputs(BaseModel): - """ - A model representing outputs in WRROC. - - Attributes: - id (str): The unique identifier for the output. - name (str): The name of the output. - """ - id: str - name: str - - -class WRROCDataBase(BaseModel): - """ - A base model representing common fields for WRROC entities. - - Attributes: - id (str): The unique identifier for the WRROC entity. - name (str): The name of the WRROC entity. - description (Optional[str]): A brief description of the WRROC entity. - instrument (Optional[str]): The instrument used in the WRROC entity. - object (list[WRROCInputs]): A list of input objects related to the WRROC entity. - result (list[WRROCOutputs]): A list of output results related to the WRROC entity. - startTime (Optional[str]): The start time of the WRROC entity. - endTime (Optional[str]): The end time of the WRROC entity. - version (Optional[str]): The version of the WRROC entity. - """ - id: str - name: str - description: Optional[str] = "" - instrument: Optional[str] = None - object: list[WRROCInputs] - result: list[WRROCOutputs] - startTime: Optional[str] = None - endTime: Optional[str] = None - version: Optional[str] = None - - class Config: - extra = "allow" - - -class WRROCData(WRROCDataBase): - """ - A model representing a WRROC entity, inheriting from WRROCDataBase. - """ - pass - - -class WRROCDataTES(WRROCDataBase): - """ - A model representing WRROC data specifically for TES conversion. - - This model inherits from WRROCDataBase and includes all the necessary fields required for TES conversion. - """ - pass - - -class WRROCDataWES(WRROCDataBase): - """ - A model representing WRROC data specifically for WES conversion. - - This model inherits from WRROCDataBase and includes additional fields required for WES conversion. - """ - status: str - - -class WRROCProcess(BaseModel): - """ - A model representing the WRROC Process Run profile. - - Attributes: - id (str): The unique identifier for the WRROC entity. - name (str): The name of the WRROC entity. - description (Optional[str]): A brief description of the WRROC entity. - startTime (Optional[str]): The start time of the process. - endTime (Optional[str]): The end time of the process. - object (Optional[list[dict[str, str]]]): A list of input objects related to the process. - profiles (Optional[list[AnyUrl]]): URLs to the RO-Crate profiles used. - """ - id: str - name: str - description: Optional[str] = "" - startTime: Optional[str] = None - endTime: Optional[str] = None - object: Optional[list[dict[str, str]]] = None - profiles: Optional[list[AnyUrl]] = None - - class Config: - extra = "allow" - - -class WRROCWorkflow(WRROCProcess): - """ - A model representing the WRROC Workflow Run profile, inheriting from WRROCProcess. - - Attributes: - workflowType (Optional[str]): The type of the workflow. - workflowVersion (Optional[str]): The version of the workflow. - result (Optional[list[dict[str, str]]]): A list of output results related to the workflow. - hasPart (Optional[list[AnyUrl]]): A list of parts or steps within the workflow. - """ - workflowType: Optional[str] = None - workflowVersion: Optional[str] = None - result: Optional[list[dict[str, str]]] = None - hasPart: Optional[list[AnyUrl]] = None - - class Config: - extra = "allow" - - -class WRROCProvenance(WRROCWorkflow): - """ - A model representing the WRROC Provenance Run profile, inheriting from WRROCWorkflow. - - Attributes: - provenanceData (Optional[str]): Data related to the provenance of the workflow. - agents (Optional[list[dict[str, str]]]): A list of agents involved in the workflow. - activity (Optional[list[dict[str, str]]]): Activities related to the provenance. - generatedBy (Optional[list[AnyUrl]]): URLs of the entities that generated the data. - used (Optional[list[AnyUrl]]): URLs of the entities that were used in the data generation. - """ - provenanceData: Optional[str] = None - agents: Optional[list[dict[str, str]]] = None - activity: Optional[list[dict[str, str]]] = None - generatedBy: Optional[list[AnyUrl]] = None - used: Optional[list[AnyUrl]] = None - - class Config: - extra = "allow" - diff --git a/crategen/models/__init__.py b/crategen/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/crategen/models/tes_models.py b/crategen/models/tes_models.py new file mode 100644 index 0000000..fed8d2c --- /dev/null +++ b/crategen/models/tes_models.py @@ -0,0 +1,268 @@ +import os +from datetime import datetime +from enum import Enum +from typing import Optional + +from pydantic import AnyUrl, BaseModel, root_validator, validator + +from ..utils import convert_to_rfc3339_format + + +class TESFileType(str, Enum): + FILE = "FILE" + DIRECTORY = "DIRECTORY" + + +class TESState(str, Enum): + UNKNOWN = "UNKNOWN" + QUEUED = "QUEUED" + INITIALIZING = "INITIALIZING" + RUNNING = "RUNNING" + PAUSED = "PAUSED" + COMPLETE = "COMPLETE" + EXECUTOR_ERROR = "EXECUTOR_ERROR" + SYSTEM_ERROR = "SYSTEM_ERROR" + CANCELLED = "CANCELLED" + + +class TESOutputFileLog(BaseModel): + """ + Information about all output files. Directory outputs are flattened into separate items. + + **Attributes:** + + - **url** (str): URL of the file in storage. + - **path** (str): Path of the file inside the container. Must be an absolute path. + - **size_bytes** (str): Size of the file in bytes. Note, this is currently coded as a string because official JSON doesn't support int64 numbers. + + **Reference:** https://ga4gh.github.io/task-execution-schemas/docs/#operation/GetTask + """ + + url: str + path: str + size_bytes: str + + +class TESExecutorLog(BaseModel): + """ + Logs for each executor + + **Attributes:** + + - **start_time** (`Optional[str]`): Time the executor started, in RFC 3339 format. + - **end_time** (`Optional[str]`): Time the executor ended, in RFC 3339 format. + - **stdout** (`Optional[str]`): Stdout content. + - **stderr** (`Optional[str]`): Stderr content. + - **exit_code** (`int`): The exit code of the executor. + + **Reference:** https://ga4gh.github.io/task-execution-schemas/docs/#operation/GetTask + """ + + start_time: Optional[datetime] = None + end_time: Optional[datetime] = None + stdout: Optional[str] = None + stderr: Optional[str] = None + exit_code: int + + @validator("start_time", "end_time") + def validate_datetime(value): + return convert_to_rfc3339_format(value) + + +class TESExecutor(BaseModel): + """ + An array of executors to be run + + **Attributes:** + - **image** (`str`): Name of the container image. + - **command** (`list[str]`): A sequence of program arguments to execute, where the first argument is the program to execute. + - **workdir** (`Optional[str]`): The working directory that the command will be executed in. + - **stdout** (`Optional[str]`): Path inside the container to a file where the executor's stdout will be written to. Must be an absolute path + - **stderr** (`Optional[str]`): Path inside the container to a file where the executor's stderr will be written to. Must be an absolute path. + - **stdin** (`Optional[str]`): Path inside the container to a file which will be piped to the executor's stdin. Must be an absolute path. + - **env** (`Optional[dict[str, str]]`): Enviromental variables to set within the container + + **Reference:** https://ga4gh.github.io/task-execution-schemas/docs/#operation/GetTask + """ + + image: str + command: list[str] + workdir: Optional[str] = None + stdout: Optional[str] = None + stderr: Optional[str] = None + stdin: Optional[str] = None + env: Optional[dict[str, str]] = None + + @validator("stdin", "stdout") + def validate_stdin_stdin(cls, value): + if not os.path.isabs(value): + raise ValueError(f"The '${value}' attribute must contain an absolute path.") + return value + + +class TESResources(BaseModel): + """ + Represents the resources required by a TES task. + + **Attributes:** + + - **cpu_cores** (`Optional[int]`): Requested number of CPUs. + - **preemptible** (`Optional[bool]`): Define if the task is allowed to run on preemptible compute instances, for example, AWS Spot. + - **ram_gb** (`Optional[float]`): The amount of RAM in GB required. + - **disk_gb** (`Optional[float]`): The amount of disk space in GB required. + - **zones** (`Optional[list[str]]`): Request that the task be run in these compute zones. + + **Reference:** https://ga4gh.github.io/task-execution-schemas/docs/#operation/GetTask + """ + + cpu_cores: Optional[int] = None + preemptible: Optional[bool] = None + ram_gb: Optional[float] = None + disk_gb: Optional[float] = None + zones: Optional[list[str]] = None + + +class TESInput(BaseModel): + """ + Input files that will be used by the task. Inputs will be downloaded and mounted into the executor container as defined by the task request document. + + **Attributes:** + + - **name** (`Optional[str]`): The name of the input file. + - **description** (`Optional[str]`): A brief description of the input. + - **url** (`AnyUrl`): The URL of the input file. Must be an absolute path + - **path** (`str`): TPath of the file inside the container. Must be an absolute path. + - **type** (`TESFileType`): The type of input ('FILE' or 'DIRECTORY'). Default is 'FILE' + - **content** (`Optional[str]`): The content of the input file, if provided inline. + + Reference: https://ga4gh.github.io/task-execution-schemas/docs/#operation/GetTask + """ + + name: Optional[str] = None + description: Optional[str] = None + url: Optional[AnyUrl] + path: str + type: TESFileType = TESFileType.FILE + content: Optional[str] = None + + @root_validator() + def validate_content_and_url(cls, values): + """ + - If content is set url should be ignored + - If content is not set then url should be present + """ + content_is_set = ( + values.get("content") and len(values.get("content").strip()) > 0 + ) + url_is_set = values.get("url") and len(values.get("url").strip()) > 0 + + if content_is_set: + values["url"] = None + elif not url_is_set: + raise ValueError( + "The 'url' attribute is required when the 'content' attribute is empty" + ) + return values + + @validator("path") + def validate_path(cls, value): + if not os.path.isabs(value): + raise ValueError("The 'path' attribute must contain an absolute path.") + return value + + +class TESOutput(BaseModel): + """ + Output files. Outputs will be uploaded from the executor container to long-term storage. + + **Attributes:** + + - **name** (`Optional[str]`): User-provided name of output file + - **description** (`Optional[str]`): Optional users provided description field, can be used for documentation. + - **url** (`AnyUrl`): URL for the file to be copied by the TES server after the task is complete + - **path** (`str`): Path of the file inside the container. Must be an absolute path. + - **type** (`TESFileType`): The type of output (e.g., FILE, DIRECTORY). + + Reference: https://ga4gh.github.io/task-execution-schemas/docs/#operation/GetTask + """ + + name: Optional[str] = None + description: Optional[str] = None + url: AnyUrl + path: str + type: TESFileType = TESFileType.FILE + + @validator("path") + def validate_path(cls, value): + if not os.path.isabs(value): + raise ValueError("The 'path' attribute must contain an absolute path.") + return value + + +class TESTaskLog(BaseModel): + """ + Task logging information. Normally, this will contain only one entry, but in the case where a task fails and is retried, an entry will be appended to this list. + + **Attributes:** + + - **logs** (`list[TESExecutorLog]`): Logs for each executor. + - **metadata** (`Optional[dict[str, str]]`): Arbitrary logging metadata included by the implementation. + - **start_time** (`Optional[datetime]`): When the task started, in RFC 3339 format. + - **end_time** (`Optional[datetime]`): When the task ended, in RFC 3339 format. + - **outputs** (`list[TESOutputFileLog]`): Information about all output files. Directory outputs are flattened into separate items. + - **system_logs** (`Optional[list[str]]`): System logs are any logs the system decides are relevant, which are not tied directly to an Executor process. Content is implementation specific: format, size, etc. + - **status** (`Optional[str]`): The status of the task. + + **Reference:** [https://ga4gh.github.io/task-execution-schemas/docs/#operation/GetTask](https://ga4gh.github.io/task-execution-schemas/docs/#operation/GetTask) + """ + + logs: list[TESExecutorLog] + metadata: Optional[dict[str, str]] + start_time: Optional[datetime] + end_time: Optional[datetime] + outputs: list[TESOutputFileLog] + system_logs: Optional[list[str]] + + @validator("start_time", "end_time") + def validate_datetime(value): + return convert_to_rfc3339_format(value) + + +class TESData(BaseModel): + """ + Represents a TES task. + + **Attributes:** + + - **id** (`str`): Task identifier assigned by the server. + - **name** (`Optional[str]`): User-provided task name. + - **description** (`Optional[str]`): Optional user-provided description of task for documentation purposes. + - **creation_time** (`Optional[str]`): The time the task was created. + - **state** (`Optional[str]`): Task state as defined by the server + - **inputs** (`list[TESInput]`): Input files that will be used by the task. + - **outputs** (`list[TESOutput]`): Output files that will be uploaded from the executor container to long-term storage. + - **executors** (`list[Executor]`): An array of executors to be run. + - **resources** (`Optional[TESResources]`): The resources required by the TES task. + - **volumes** (`Optional[list[str]]`): Volumes are directories which may be used to share data between Executors.. + - **logs** (`Optional[list[TESLogs]]`): Task logging information + - **tags** (`Optional[[str, str]]`): A key-value map of arbitrary tags. + + **Reference:** [https://ga4gh.github.io/task-execution-schemas/docs/#operation/GetTask](https://ga4gh.github.io/task-execution-schemas/docs/#operation/GetTask) + """ + + id: str + name: Optional[str] = None + description: Optional[str] = None + creation_time: Optional[datetime] = None + state: Optional[TESState] = TESState.UNKNOWN + inputs: list[TESInput] + outputs: list[TESOutput] + executors: list[TESExecutor] + resources: Optional[TESResources] = None + volumes: Optional[list[str]] = None + logs: Optional[list[TESTaskLog]] = None + tags: Optional[dict[str, str]] = None + + @validator("creation_time") + def validate_datetime(value): + return convert_to_rfc3339_format(value) diff --git a/crategen/models/wes_models.py b/crategen/models/wes_models.py new file mode 100644 index 0000000..e6e37b7 --- /dev/null +++ b/crategen/models/wes_models.py @@ -0,0 +1,92 @@ +from typing import Optional + +from pydantic import BaseModel, Field, root_validator + + +class WESRunLog(BaseModel): + """ + Represents a run log in the Workflow Execution Service (WES). + + Attributes: + name (Optional[str]): The name of the run. + start_time (Optional[str]): The start time of the run. + end_time (Optional[str]): The end time of the run. + cmd (Optional[list[str]]): The command executed in the run. + stdout (Optional[str]): The path to the stdout log. + stderr (Optional[str]): The path to the stderr log. + exit_code (Optional[int]): The exit code of the run. + tes_logs_url (Optional[str]): The URL of the TES logs. + """ + + name: Optional[str] = None + start_time: Optional[str] = None + end_time: Optional[str] = None + cmd: Optional[list[str]] = None + stdout: Optional[str] = None + stderr: Optional[str] = None + exit_code: Optional[int] = None + tes_logs_url: Optional[str] = None + + +class WESOutputs(BaseModel): + """ + Represents output files in WES. + + Attributes: + location (str): The URL of the output file. + name (str): The name of the output file. + """ + + location: str + name: str + + +class WESRequest(BaseModel): + """ + Represents a workflow request in WES. + + Attributes: + workflow_params (dict[str, str]): The parameters for the workflow. + workflow_type (str): The type of the workflow (e.g., CWL). + workflow_type_version (str): The version of the workflow type. + tags (Optional[dict[str, str]]): Additional tags associated with the workflow. + """ + + workflow_params: dict[str, str] + workflow_type: str + workflow_type_version: str + tags: Optional[dict[str, str]] = None + + +class WESData(BaseModel): + """ + Represents a WES run. + + Attributes: + run_id (str): The unique identifier for the WES run. + request (WESRequest): The request associated with the WES run. + state (str): The state of the WES run. + run_log (WESRunLog): The log of the WES run. + task_logs (Optional[list[WESRunLog]]): The logs of individual tasks within the run. + outputs (list[WESOutputs]): The outputs of the WES run. + """ + + run_id: str + request: WESRequest + state: str + run_log: WESRunLog + task_logs: Optional[list[WESRunLog]] = Field( + None, description="This field is deprecated. Use tes_logs_url instead." + ) + outputs: list[WESOutputs] + + class Config: + extra = "allow" + + @root_validator + def check_deprecated_fields(cls, values): + if values.get("task_logs") is not None: + print( + "DeprecationWarning: The 'task_logs' field is deprecated and will be removed in future versions. Use 'tes_logs_url' instead." + ) + return values diff --git a/crategen/models/wrroc_models.py b/crategen/models/wrroc_models.py new file mode 100644 index 0000000..c01d17a --- /dev/null +++ b/crategen/models/wrroc_models.py @@ -0,0 +1,155 @@ +from typing import Optional + +from pydantic import AnyUrl, BaseModel + + +class WRROCInputs(BaseModel): + """ + A model representing inputs in WRROC. + + Attributes: + id (str): The unique identifier for the input. + name (str): The name of the input. + """ + + id: str + name: str + + +class WRROCOutputs(BaseModel): + """ + A model representing outputs in WRROC. + + Attributes: + id (str): The unique identifier for the output. + name (str): The name of the output. + """ + + id: str + name: str + + +class WRROCDataBase(BaseModel): + """ + A base model representing common fields for WRROC entities. + + Attributes: + id (str): The unique identifier for the WRROC entity. + name (str): The name of the WRROC entity. + description (Optional[str]): A brief description of the WRROC entity. + instrument (Optional[str]): The instrument used in the WRROC entity. + object (list[WRROCInputs]): A list of input objects related to the WRROC entity. + result (list[WRROCOutputs]): A list of output results related to the WRROC entity. + startTime (Optional[str]): The start time of the WRROC entity. + endTime (Optional[str]): The end time of the WRROC entity. + version (Optional[str]): The version of the WRROC entity. + """ + + id: str + name: str + description: Optional[str] = "" + instrument: Optional[str] = None + object: list[WRROCInputs] + result: list[WRROCOutputs] + startTime: Optional[str] = None + endTime: Optional[str] = None + version: Optional[str] = None + + class Config: + extra = "allow" + + +class WRROCData(WRROCDataBase): + """ + A model representing a WRROC entity, inheriting from WRROCDataBase. + """ + + pass + + +class WRROCDataTES(WRROCDataBase): + """ + A model representing WRROC data specifically for TES conversion. + + This model inherits from WRROCDataBase and includes all the necessary fields required for TES conversion. + """ + + pass + + +class WRROCDataWES(WRROCDataBase): + """ + A model representing WRROC data specifically for WES conversion. + + This model inherits from WRROCDataBase and includes additional fields required for WES conversion. + """ + + status: str + + +class WRROCProcess(BaseModel): + """ + A model representing the WRROC Process Run profile. + + Attributes: + id (str): The unique identifier for the WRROC entity. + name (str): The name of the WRROC entity. + description (Optional[str]): A brief description of the WRROC entity. + startTime (Optional[str]): The start time of the process. + endTime (Optional[str]): The end time of the process. + object (Optional[list[dict[str, str]]]): A list of input objects related to the process. + profiles (Optional[list[AnyUrl]]): URLs to the RO-Crate profiles used. + """ + + id: str + name: str + description: Optional[str] = "" + startTime: Optional[str] = None + endTime: Optional[str] = None + object: Optional[list[dict[str, str]]] = None + profiles: Optional[list[AnyUrl]] = None + + class Config: + extra = "allow" + + +class WRROCWorkflow(WRROCProcess): + """ + A model representing the WRROC Workflow Run profile, inheriting from WRROCProcess. + + Attributes: + workflowType (Optional[str]): The type of the workflow. + workflowVersion (Optional[str]): The version of the workflow. + result (Optional[list[dict[str, str]]]): A list of output results related to the workflow. + hasPart (Optional[list[AnyUrl]]): A list of parts or steps within the workflow. + """ + + workflowType: Optional[str] = None + workflowVersion: Optional[str] = None + result: Optional[list[dict[str, str]]] = None + hasPart: Optional[list[AnyUrl]] = None + + class Config: + extra = "allow" + + +class WRROCProvenance(WRROCWorkflow): + """ + A model representing the WRROC Provenance Run profile, inheriting from WRROCWorkflow. + + Attributes: + provenanceData (Optional[str]): Data related to the provenance of the workflow. + agents (Optional[list[dict[str, str]]]): A list of agents involved in the workflow. + activity (Optional[list[dict[str, str]]]): Activities related to the provenance. + generatedBy (Optional[list[AnyUrl]]): URLs of the entities that generated the data. + used (Optional[list[AnyUrl]]): URLs of the entities that were used in the data generation. + """ + + provenanceData: Optional[str] = None + agents: Optional[list[dict[str, str]]] = None + activity: Optional[list[dict[str, str]]] = None + generatedBy: Optional[list[AnyUrl]] = None + used: Optional[list[AnyUrl]] = None + + class Config: + extra = "allow" diff --git a/crategen/converters/utils.py b/crategen/utils.py similarity index 65% rename from crategen/converters/utils.py rename to crategen/utils.py index 2116c7f..cd9fa6d 100644 --- a/crategen/converters/utils.py +++ b/crategen/utils.py @@ -1,5 +1,6 @@ import datetime + def convert_to_iso8601(timestamp): """ Convert a given timestamp to ISO 8601 format. @@ -14,10 +15,10 @@ def convert_to_iso8601(timestamp): if timestamp: # List of supported formats formats = [ - "%Y-%m-%dT%H:%M:%S.%fZ", # RFC 3339 with fractional seconds - "%Y-%m-%dT%H:%M:%SZ", # RFC 3339 without fractional seconds - "%Y-%m-%dT%H:%M:%S%z", # ISO 8601 with timezone - "%Y-%m-%dT%H:%M:%S.%f%z", # ISO 8601 with fractional seconds and timezone + "%Y-%m-%dT%H:%M:%S.%fZ", # RFC 3339 with fractional seconds + "%Y-%m-%dT%H:%M:%SZ", # RFC 3339 without fractional seconds + "%Y-%m-%dT%H:%M:%S%z", # ISO 8601 with timezone + "%Y-%m-%dT%H:%M:%S.%f%z", # ISO 8601 with fractional seconds and timezone ] for fmt in formats: try: @@ -27,3 +28,7 @@ def convert_to_iso8601(timestamp): # Handle incorrect format or other issues return None return None + + +def convert_to_rfc3339_format(date_time: datetime.datetime): + return date_time.isoformat("T") + "Z" diff --git a/crategen/validators.py b/crategen/validators.py index 579ebcf..16dfc1c 100644 --- a/crategen/validators.py +++ b/crategen/validators.py @@ -1,12 +1,22 @@ -from pydantic import ValidationError from typing import Union -from .models import WRROCProcess, WRROCWorkflow, WRROCProvenance, WRROCDataTES, WRROCDataWES from urllib.parse import urlparse +from pydantic import ValidationError + +from .models.wrroc_models import ( + WRROCDataTES, + WRROCDataWES, + WRROCProcess, + WRROCProvenance, + WRROCWorkflow, +) + + def validate_wrroc(data: dict) -> Union[WRROCProvenance, WRROCWorkflow, WRROCProcess]: """ Validate that the input data is a valid WRROC entity and determine which profile it adheres to. + This function attempts to validate the input data against the WRROCProvenance model first. If that validation fails, it attempts validation against the WRROCWorkflow model. If that also fails, it finally attempts validation against the WRROCProcess model. @@ -32,6 +42,7 @@ def validate_wrroc(data: dict) -> Union[WRROCProvenance, WRROCWorkflow, WRROCPro except ValidationError as e: raise ValueError(f"Invalid WRROC data: {e.errors()}") from e + def validate_wrroc_tes(data: dict) -> WRROCDataTES: """ Validate that the input data contains the fields required for WRROC to TES conversion. @@ -43,14 +54,17 @@ def validate_wrroc_tes(data: dict) -> WRROCDataTES: ValueError: If the data is not valid WRROC data or does not contain the necessary fields for TES conversion. """ data_validated = validate_wrroc(data) - + try: data_wrroc_tes = WRROCDataTES(**data_validated.dict()) except ValidationError as exc: - raise ValueError(f"WRROC data insufficient for TES conversion: {exc.errors()}") from exc + raise ValueError( + f"WRROC data insufficient for TES conversion: {exc.errors()}" + ) from exc return data_wrroc_tes + def validate_wrroc_wes(data: dict) -> WRROCDataWES: """ Validate that the input data contains the fields required for WRROC to WES conversion. @@ -62,11 +76,13 @@ def validate_wrroc_wes(data: dict) -> WRROCDataWES: ValueError: If the data is not valid WRROC data or does not contain the necessary fields for WES conversion. """ data_validated = validate_wrroc(data) - + try: data_wrroc_wes = WRROCDataWES(**data_validated.dict()) except ValidationError as exc: - raise ValueError(f"WRROC data insufficient for WES conversion: {exc.errors()}") from exc + raise ValueError( + f"WRROC data insufficient for WES conversion: {exc.errors()}" + ) from exc # Validate URLs in the result field, only if result is not None if data_wrroc_wes.result is not None: diff --git a/lefthook.yml b/lefthook.yml new file mode 100644 index 0000000..f6e5dfe --- /dev/null +++ b/lefthook.yml @@ -0,0 +1,35 @@ +# EXAMPLE USAGE: +# +# Refer for explanation to following link: +# https://github.com/evilmartians/lefthook/blob/master/docs/configuration.md +# +# pre-push: +# commands: +# packages-audit: +# tags: frontend security +# run: yarn audit +# gems-audit: +# tags: backend security +# run: bundle audit +# +# pre-commit: +# parallel: true +# commands: +# eslint: +# glob: "*.{js,ts,jsx,tsx}" +# run: yarn eslint {staged_files} +# rubocop: +# tags: backend style +# glob: "*.rb" +# exclude: '(^|/)(application|routes)\.rb$' +# run: bundle exec rubocop --force-exclusion {all_files} +# govet: +# tags: backend style +# files: git ls-files -m +# glob: "*.go" +# run: go vet {files} +# scripts: +# "hello.js": +# runner: node +# "any.go": +# runner: go run diff --git a/pyproject.toml b/pyproject.toml index 1f82a41..0057e22 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,6 +50,8 @@ pytest-mock = "^3.14.0" [tool.poetry.group.types.dependencies] mypy = "^1.10.1" + + [tool.poetry.scripts] crategen = "crategen.cli:cli" @@ -61,9 +63,13 @@ skips = [ [tool.ruff] exclude = [ - "tests/*", - "tests/unit/*", - "crategen/*" + ".git", + "/.pytest_cache", + "__pycache__", + "build", + "_build", + "dist", + ".env" ] indent-width = 4 @@ -72,11 +78,12 @@ docstring-code-format = true indent-style = "space" line-ending = "lf" quote-style = "double" +skip-magic-trailing-comma = true + [tool.ruff.lint] select = [ "B", # flake8-bugbear - "D", # pydocstyle "E", # pycodestyle "F", # Pyflakes "I", # isort @@ -84,9 +91,11 @@ select = [ "SIM", # flake8-simplify "UP", # pyupgrade ] +ignore = ["E203", "E501"] [tool.ruff.lint.pydocstyle] convention = "google" [tool.typos.default.extend-words] mke = 'mke' + diff --git a/tests/unit/test_wrroc_models.py b/tests/unit/test_wrroc_models.py index 26dec69..1e9538e 100644 --- a/tests/unit/test_wrroc_models.py +++ b/tests/unit/test_wrroc_models.py @@ -1,9 +1,16 @@ import unittest + from pydantic import ValidationError -from crategen.models import WRROCProcess, WRROCWorkflow, WRROCProvenance, WRROCDataWES +from crategen.models.wrroc_models import ( + WRROCDataWES, + WRROCProcess, + WRROCProvenance, + WRROCWorkflow, +) from crategen.validators import validate_wrroc, validate_wrroc_tes, validate_wrroc_wes + class TestWRROCProcessModel(unittest.TestCase): """ Unit tests for the WRROCProcess model. @@ -19,7 +26,12 @@ def test_wrroc_process_model(self): "description": "A simple process", "startTime": "2024-07-10T14:30:00Z", "endTime": "2024-07-10T15:30:00Z", - "object": [{"id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/README.md", "name": "Input 1"}] + "object": [ + { + "id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/README.md", + "name": "Input 1", + } + ], } model = WRROCProcess(**data) self.assertEqual(model.id, "process-id") @@ -29,11 +41,7 @@ def test_wrroc_process_empty_object_list(self): """ Test that the WRROCProcess model handles empty object lists correctly. """ - data = { - "id": "process-id", - "name": "Test Process", - "object": [] - } + data = {"id": "process-id", "name": "Test Process", "object": []} model = WRROCProcess(**data) self.assertEqual(model.object, []) @@ -43,11 +51,12 @@ def test_wrroc_process_invalid_data(self): """ data = { "id": 123, # id should be a string - "name": None # name should be a string + "name": None, # name should be a string } with self.assertRaises(ValidationError): WRROCProcess(**data) + class TestWRROCWorkflowModel(unittest.TestCase): """ Unit tests for the WRROCWorkflow model. @@ -62,24 +71,27 @@ def test_wrroc_workflow_model(self): "name": "Test Workflow", "workflowType": "CWL", "workflowVersion": "v1.0", - "result": [{"id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/LICENSE", "name": "Output 1"}] + "result": [ + { + "id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/LICENSE", + "name": "Output 1", + } + ], } model = WRROCWorkflow(**data) self.assertEqual(model.workflowType, "CWL") - self.assertEqual(model.result[0]['name'], "Output 1") + self.assertEqual(model.result[0]["name"], "Output 1") def test_wrroc_workflow_missing_optional_fields(self): """ Test that the WRROCWorkflow model handles missing optional fields correctly. """ - data = { - "id": "workflow-id", - "name": "Test Workflow" - } + data = {"id": "workflow-id", "name": "Test Workflow"} model = WRROCWorkflow(**data) self.assertIsNone(model.workflowType) self.assertIsNone(model.workflowVersion) + class TestWRROCProvenanceModel(unittest.TestCase): """ Unit tests for the WRROCProvenance model. @@ -93,24 +105,21 @@ def test_wrroc_provenance_model(self): "id": "provenance-id", "name": "Test Provenance", "provenanceData": "Provenance information", - "agents": [{"id": "agent1", "name": "Agent 1"}] + "agents": [{"id": "agent1", "name": "Agent 1"}], } model = WRROCProvenance(**data) self.assertEqual(model.provenanceData, "Provenance information") - self.assertEqual(model.agents[0]['name'], "Agent 1") + self.assertEqual(model.agents[0]["name"], "Agent 1") def test_wrroc_provenance_empty_agents_list(self): """ Test that the WRROCProvenance model handles empty agents lists correctly. """ - data = { - "id": "provenance-id", - "name": "Test Provenance", - "agents": [] - } + data = {"id": "provenance-id", "name": "Test Provenance", "agents": []} model = WRROCProvenance(**data) self.assertEqual(model.agents, []) + class TestWRROCValidators(unittest.TestCase): """ Unit tests for the WRROC validators to ensure they work as expected. @@ -120,10 +129,7 @@ def test_validate_wrroc_process(self): """ Test that validate_wrroc correctly identifies a WRROCProcess entity. """ - data = { - "id": "process-id", - "name": "Test Process" - } + data = {"id": "process-id", "name": "Test Process"} model = validate_wrroc(data) self.assertIsInstance(model, WRROCProcess) @@ -135,7 +141,7 @@ def test_validate_wrroc_workflow(self): "id": "workflow-id", "name": "Test Workflow", "workflowType": "CWL", - "workflowVersion": "v1.0" + "workflowVersion": "v1.0", } model = validate_wrroc(data) self.assertIsInstance(model, WRROCWorkflow) @@ -147,7 +153,7 @@ def test_validate_wrroc_provenance(self): data = { "id": "provenance-id", "name": "Test Provenance", - "provenanceData": "Provenance information" + "provenanceData": "Provenance information", } model = validate_wrroc(data) self.assertIsInstance(model, WRROCProvenance) @@ -156,9 +162,7 @@ def test_validate_wrroc_invalid(self): """ Test that validate_wrroc raises a ValueError for invalid WRROC data. """ - data = { - "unknown_field": "unexpected" - } + data = {"unknown_field": "unexpected"} with self.assertRaises(ValueError): validate_wrroc(data) @@ -169,8 +173,18 @@ def test_validate_wrroc_tes(self): data = { "id": "process-id", "name": "Test Process", - "object": [{"id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/README.md", "name": "Input 1"}], - "result": [{"id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/LICENSE", "name": "Output 1"}] + "object": [ + { + "id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/README.md", + "name": "Input 1", + } + ], + "result": [ + { + "id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/LICENSE", + "name": "Output 1", + } + ], } model = validate_wrroc_tes(data) self.assertEqual(model.id, "process-id") @@ -184,7 +198,12 @@ def test_validate_wrroc_tes_empty_object_list(self): "id": "process-id", "name": "Test Process", "object": [], - "result": [{"id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/LICENSE", "name": "Output 1"}] + "result": [ + { + "id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/LICENSE", + "name": "Output 1", + } + ], } model = validate_wrroc_tes(data) self.assertEqual(model.object, []) @@ -193,29 +212,35 @@ def test_validate_wrroc_tes_missing_fields(self): """ Test that validate_wrroc_tes raises a ValueError if required fields for TES conversion are missing. """ - data = { - "id": "process-id", - "name": "Test Process" - } + data = {"id": "process-id", "name": "Test Process"} with self.assertRaises(ValueError): validate_wrroc_tes(data) def test_validate_wrroc_wes(self): - """ - Test that validate_wrroc_wes correctly validates a WRROC entity for WES conversion. - """ - data = { - "id": "workflow-id", - "name": "Test Workflow", - "workflowType": "CWL", - "workflowVersion": "v1.0", - "status": "completed", - "object": [{"id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/README.md", "name": "Input 1"}], - "result": [{"id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/LICENSE", "name": "Output 1"}] - } - model = validate_wrroc_wes(data) - self.assertIsInstance(model, WRROCDataWES) - + """ + Test that validate_wrroc_wes correctly validates a WRROC entity for WES conversion. + """ + data = { + "id": "workflow-id", + "name": "Test Workflow", + "workflowType": "CWL", + "workflowVersion": "v1.0", + "status": "completed", + "object": [ + { + "id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/README.md", + "name": "Input 1", + } + ], + "result": [ + { + "id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/LICENSE", + "name": "Output 1", + } + ], + } + model = validate_wrroc_wes(data) + self.assertIsInstance(model, WRROCDataWES) def test_validate_wrroc_wes_invalid_url(self): """ @@ -234,7 +259,7 @@ def test_validate_wrroc_wes_invalid_url(self): "name": "Test Workflow", "workflowType": "CWL", "workflowVersion": "v1.0", - "result": [{"id": url, "name": "Output 1"}] + "result": [{"id": url, "name": "Output 1"}], } with self.assertRaises(ValueError): validate_wrroc_wes(data) @@ -243,12 +268,10 @@ def test_validate_wrroc_wes_missing_fields(self): """ Test that validate_wrroc_wes raises a ValueError if required fields for WES conversion are missing. """ - data = { - "id": "workflow-id", - "name": "Test Workflow" - } + data = {"id": "workflow-id", "name": "Test Workflow"} with self.assertRaises(ValueError): validate_wrroc_wes(data) + if __name__ == "__main__": unittest.main() From 3dc009aca361047f28b4c5c19f21a8fa0d716652 Mon Sep 17 00:00:00 2001 From: Salihu <91833785+SalihuDickson@users.noreply.github.com> Date: Sun, 25 Aug 2024 00:34:18 +0100 Subject: [PATCH 05/10] Config lefthook (#25) Co-authored-by: salihuDickson --- crategen/converters/tes_converter.py | 34 +++++++---------------- crategen/converters/wes_converter.py | 1 + crategen/models/tes_models.py | 2 +- crategen/validators.py | 5 +++- lefthook.yml | 41 ++++------------------------ pyproject.toml | 2 +- 6 files changed, 23 insertions(+), 62 deletions(-) diff --git a/crategen/converters/tes_converter.py b/crategen/converters/tes_converter.py index 0d7a0b9..6701627 100644 --- a/crategen/converters/tes_converter.py +++ b/crategen/converters/tes_converter.py @@ -25,37 +25,23 @@ def convert_to_wrroc(self, data: dict) -> dict: except ValidationError as e: raise ValueError(f"Invalid TES data: {e.errors()}") from e - # Extract validated data - ( - id, - name, - description, - creation_time, - state, - inputs, - outputs, - executors, - resources, - volumes, - logs, - tags, - ) = data_tes.dict().values() - end_time = logs[0].end_time + executors = data_tes.executors + end_time = data_tes.logs[0].end_time if data_tes.logs else None # Convert to WRROC format wrroc_data = { - "@id": id, - "name": name, - "description": description, - "instrument": executors[0]["image"] if executors else None, + "@id": data_tes.id, + "name": data_tes.name, + "description": data_tes.description, + "instrument": executors[0].image if executors else None, "object": [ - {"@id": input["url"], "name": input["path"], "type": input["type"]} - for input in inputs + {"@id": input.url, "name": input.path, "type": input.type} + for input in data_tes.inputs ], "result": [ - {"@id": output["url"], "name": output["path"]} for output in outputs + {"@id": output.url, "name": output.path} for output in data_tes.outputs ], - "startTime": creation_time, + "startTime": data_tes.creation_time, "endTime": end_time, } return wrroc_data diff --git a/crategen/converters/wes_converter.py b/crategen/converters/wes_converter.py index 619e069..856ce5f 100644 --- a/crategen/converters/wes_converter.py +++ b/crategen/converters/wes_converter.py @@ -61,6 +61,7 @@ def convert_from_wrroc(self, data: dict) -> dict: f"Invalid WRROC data for WES conversion: {e.errors()}" ) from e + # Convert from WRROC to WES format # Convert from WRROC to WES format wes_data = { "run_id": data_wrroc.id, diff --git a/crategen/models/tes_models.py b/crategen/models/tes_models.py index fed8d2c..407a9c0 100644 --- a/crategen/models/tes_models.py +++ b/crategen/models/tes_models.py @@ -158,7 +158,7 @@ def validate_content_and_url(cls, values): if content_is_set: values["url"] = None - elif not url_is_set: + elif not content_is_set and not url_is_set: raise ValueError( "The 'url' attribute is required when the 'content' attribute is empty" ) diff --git a/crategen/validators.py b/crategen/validators.py index 16dfc1c..6a10d39 100644 --- a/crategen/validators.py +++ b/crategen/validators.py @@ -16,14 +16,17 @@ def validate_wrroc(data: dict) -> Union[WRROCProvenance, WRROCWorkflow, WRROCPro """ Validate that the input data is a valid WRROC entity and determine which profile it adheres to. - This function attempts to validate the input data against the WRROCProvenance model first. If that validation fails, it attempts validation against the WRROCWorkflow model. If that also fails, it finally attempts validation against the WRROCProcess model. + Args: + data (dict): The input data to validate. + Returns: Union[WRROCProvenance, WRROCWorkflow, WRROCProcess]: The validated WRROC data, indicating the highest profile the data adheres to. + Raises: ValueError: If the data does not adhere to any of the WRROC profiles. """ diff --git a/lefthook.yml b/lefthook.yml index f6e5dfe..9d0ec7e 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -1,35 +1,6 @@ -# EXAMPLE USAGE: -# -# Refer for explanation to following link: -# https://github.com/evilmartians/lefthook/blob/master/docs/configuration.md -# -# pre-push: -# commands: -# packages-audit: -# tags: frontend security -# run: yarn audit -# gems-audit: -# tags: backend security -# run: bundle audit -# -# pre-commit: -# parallel: true -# commands: -# eslint: -# glob: "*.{js,ts,jsx,tsx}" -# run: yarn eslint {staged_files} -# rubocop: -# tags: backend style -# glob: "*.rb" -# exclude: '(^|/)(application|routes)\.rb$' -# run: bundle exec rubocop --force-exclusion {all_files} -# govet: -# tags: backend style -# files: git ls-files -m -# glob: "*.go" -# run: go vet {files} -# scripts: -# "hello.js": -# runner: node -# "any.go": -# runner: go run +pre-push: + commands: + ruff: + files: git diff --name-only --diff-filter=d $(git merge-base origin/main HEAD)..HEAD + run: poetry run ruff check {files} + glob: '*.py' diff --git a/pyproject.toml b/pyproject.toml index 0057e22..639b47a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,7 +69,7 @@ exclude = [ "build", "_build", "dist", - ".env" + ".env", ] indent-width = 4 From 7e85db7d088011c3c1cf8584a1e70b6caad026c5 Mon Sep 17 00:00:00 2001 From: karanjot786 Date: Fri, 30 Aug 2024 21:41:22 +0530 Subject: [PATCH 06/10] feat: add WRROC models, validators & unit tests --- crategen/converters/tes_converter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crategen/converters/tes_converter.py b/crategen/converters/tes_converter.py index 6701627..9a2614d 100644 --- a/crategen/converters/tes_converter.py +++ b/crategen/converters/tes_converter.py @@ -1,7 +1,7 @@ from pydantic import ValidationError -from ..models.tes_models import TESData -from ..models.wrroc_models import WRROCDataTES +from ..models.tes_models import TESData, TESInput, TESOutput, TESExecutor, TESResources, TESState, TESTaskLog +from ..models.wrroc_models import WRROCDataTES, WRROCInputs, WRROCOutputs from .abstract_converter import AbstractConverter From ef290a593106c3d75b43cc4b4c7701b2ec38c883 Mon Sep 17 00:00:00 2001 From: karanjot786 Date: Fri, 30 Aug 2024 21:41:49 +0530 Subject: [PATCH 07/10] feat: add WRROC models, validators & unit tests --- .github/workflows/ci.yml | 4 +- crategen/converters/abstract_converter.py | 2 +- crategen/converters/tes_converter.py | 78 ++- crategen/converters/wes_converter.py | 54 +- crategen/models/wrroc_models.py | 18 +- poetry.lock | 708 ++++++++++++---------- pyproject.toml | 6 +- tests/unit/test_wrroc_models.py | 16 +- 8 files changed, 491 insertions(+), 395 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 237b6f1..52a8779 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,10 +11,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v5 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: '3.11' diff --git a/crategen/converters/abstract_converter.py b/crategen/converters/abstract_converter.py index f686557..fc8c98d 100644 --- a/crategen/converters/abstract_converter.py +++ b/crategen/converters/abstract_converter.py @@ -7,5 +7,5 @@ def convert_to_wrroc(self, data): """Convert data to WRROC format""" @abstractmethod - def convert_from_wrroc(self, wrroc_data): + def convert_from_wrroc(self, data): """Convert WRROC data to the original format""" diff --git a/crategen/converters/tes_converter.py b/crategen/converters/tes_converter.py index 9a2614d..e42d950 100644 --- a/crategen/converters/tes_converter.py +++ b/crategen/converters/tes_converter.py @@ -1,7 +1,17 @@ -from pydantic import ValidationError +from datetime import datetime -from ..models.tes_models import TESData, TESInput, TESOutput, TESExecutor, TESResources, TESState, TESTaskLog -from ..models.wrroc_models import WRROCDataTES, WRROCInputs, WRROCOutputs +from pydantic import AnyUrl, ValidationError + +from ..models.tes_models import ( + TESData, + TESExecutor, + TESInput, + TESOutput, + TESState, + TESTaskLog, +) +from ..models.wrroc_models import WRROCDataTES +from ..validators import validate_wrroc_tes from .abstract_converter import AbstractConverter @@ -11,10 +21,10 @@ def convert_to_wrroc(self, data: dict) -> dict: Convert TES data to WRROC format. Args: - data (dict): The input TES data. + data: The input TES data. Returns: - dict: The converted WRROC data. + The converted WRROC data. Raises: ValidationError: If TES data is invalid. @@ -28,15 +38,13 @@ def convert_to_wrroc(self, data: dict) -> dict: executors = data_tes.executors end_time = data_tes.logs[0].end_time if data_tes.logs else None - # Convert to WRROC format wrroc_data = { "@id": data_tes.id, "name": data_tes.name, "description": data_tes.description, "instrument": executors[0].image if executors else None, "object": [ - {"@id": input.url, "name": input.path, "type": input.type} - for input in data_tes.inputs + {"@id": input.url, "name": input.path} for input in data_tes.inputs ], "result": [ {"@id": output.url, "name": output.path} for output in data_tes.outputs @@ -44,6 +52,8 @@ def convert_to_wrroc(self, data: dict) -> dict: "startTime": data_tes.creation_time, "endTime": end_time, } + + validate_wrroc_tes(wrroc_data) return wrroc_data def convert_from_wrroc(self, data: dict) -> dict: @@ -51,10 +61,10 @@ def convert_from_wrroc(self, data: dict) -> dict: Convert WRROC data to TES format. Args: - data (dict): The input WRROC data. + data: The input WRROC data. Returns: - dict: The converted TES data. + The converted TES data. Raises: ValidationError: If WRROC data is invalid. @@ -65,15 +75,39 @@ def convert_from_wrroc(self, data: dict) -> dict: except ValidationError as e: raise ValueError(f"Invalid WRROC data: {e.errors()}") from e - # Convert from WRROC to TES format - tes_data = { - "id": data_wrroc.id, - "name": data_wrroc.name, - "description": data_wrroc.description, - "executors": [{"image": data_wrroc.instrument}], - "inputs": [{"url": obj.id, "path": obj.name} for obj in data_wrroc.object], - "outputs": [{"url": res.id, "path": res.name} for res in data_wrroc.result], - "creation_time": data_wrroc.startTime, - "logs": [{"end_time": data_wrroc.endTime}], - } - return tes_data + # Convert URL strings to AnyUrl + tes_inputs = [TESInput(url=AnyUrl(url=obj.id), path=obj.name) for obj in data_wrroc.object] + tes_outputs = [TESOutput(url=AnyUrl(url=res.id), path=res.name) for res in data_wrroc.result] + + # Ensure 'image' and 'command' fields are provided + tes_executors = [TESExecutor(image=data_wrroc.instrument or "", command=[])] # Provide default empty list for command + + # Ensure correct type for end_time (datetime) + end_time = datetime.fromisoformat(data_wrroc.endTime) if data_wrroc.endTime else None + + tes_logs = [ + TESTaskLog( + logs=[], + metadata=None, + start_time=None, + end_time=end_time, + outputs=[], + system_logs=None + ) + ] + + tes_data = TESData( + id=data_wrroc.id, + name=data_wrroc.name, + description=data_wrroc.description, + executors=tes_executors, + inputs=tes_inputs, + outputs=tes_outputs, + creation_time=None, + logs=tes_logs, + state=TESState.UNKNOWN + ) + + # Validate TES data before returning + tes_data = TESData(**tes_data.dict()) + return tes_data.dict() diff --git a/crategen/converters/wes_converter.py b/crategen/converters/wes_converter.py index 856ce5f..48bbb65 100644 --- a/crategen/converters/wes_converter.py +++ b/crategen/converters/wes_converter.py @@ -1,8 +1,9 @@ from pydantic import ValidationError -from ..models.wes_models import WESData +from ..models.wes_models import WESData, WESOutputs, WESRequest, WESRunLog from ..models.wrroc_models import WRROCDataWES from ..utils import convert_to_iso8601 +from ..validators import validate_wrroc_wes from .abstract_converter import AbstractConverter @@ -12,10 +13,10 @@ def convert_to_wrroc(self, data: dict) -> dict: Convert WES data to WRROC format. Args: - data (dict): The input WES data. + data: The input WES data. Returns: - dict: The converted WRROC data. + The converted WRROC data. Raises: ValidationError: If WES data is invalid. @@ -26,7 +27,6 @@ def convert_to_wrroc(self, data: dict) -> dict: except ValidationError as e: raise ValueError(f"Invalid WES data: {e.errors()}") from e - # Convert to WRROC format wrroc_data = { "@id": data_wes.run_id, "name": data_wes.run_log.name, @@ -38,6 +38,9 @@ def convert_to_wrroc(self, data: dict) -> dict: for output in data_wes.outputs ], } + + # Validate WRROC data before returning + validate_wrroc_wes(wrroc_data) return wrroc_data def convert_from_wrroc(self, data: dict) -> dict: @@ -45,10 +48,10 @@ def convert_from_wrroc(self, data: dict) -> dict: Convert WRROC data to WES format. Args: - data (dict): The input WRROC data. + data: The input WRROC data. Returns: - dict: The converted WES data. + The converted WES data. Raises: ValidationError: If WRROC data is invalid. @@ -61,18 +64,27 @@ def convert_from_wrroc(self, data: dict) -> dict: f"Invalid WRROC data for WES conversion: {e.errors()}" ) from e - # Convert from WRROC to WES format - # Convert from WRROC to WES format - wes_data = { - "run_id": data_wrroc.id, - "run_log": { - "name": data_wrroc.name, - "start_time": data_wrroc.startTime, - "end_time": data_wrroc.endTime, - }, - "state": data_wrroc.status, - "outputs": [ - {"location": res.id, "name": res.name} for res in data_wrroc.result - ], - } - return wes_data + wes_outputs = [WESOutputs(location=res.id, name=res.name) for res in data_wrroc.result] + wes_run_log = WESRunLog( + name=data_wrroc.name, + start_time=data_wrroc.startTime, + end_time=data_wrroc.endTime + ) + wes_request = WESRequest( + workflow_params={}, # Adjust as necessary + workflow_type="CWL", # Example type, adjust as necessary + workflow_type_version="v1.0" # Example version, adjust as necessary + ) + + wes_data = WESData( + run_id=data_wrroc.id, + request=wes_request, + state=data_wrroc.status, + run_log=wes_run_log, + task_logs=None, # Provide appropriate value + outputs=wes_outputs + ) + + # Validate WES data before returning + wes_data = WESData(**wes_data.dict()) + return wes_data.dict() diff --git a/crategen/models/wrroc_models.py b/crategen/models/wrroc_models.py index c01d17a..14bf22a 100644 --- a/crategen/models/wrroc_models.py +++ b/crategen/models/wrroc_models.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import AnyUrl, BaseModel +from pydantic import AnyUrl, BaseModel, Field class WRROCInputs(BaseModel): @@ -12,7 +12,7 @@ class WRROCInputs(BaseModel): name (str): The name of the input. """ - id: str + id: str = Field(alias='@id') name: str @@ -25,7 +25,7 @@ class WRROCOutputs(BaseModel): name (str): The name of the output. """ - id: str + id: str = Field(alias='@id') name: str @@ -45,18 +45,19 @@ class WRROCDataBase(BaseModel): version (Optional[str]): The version of the WRROC entity. """ - id: str + id: str = Field(alias='@id') name: str description: Optional[str] = "" instrument: Optional[str] = None - object: list[WRROCInputs] - result: list[WRROCOutputs] + object: list[WRROCInputs] = Field(default_factory=list) + result: list[WRROCOutputs] = Field(default_factory=list) startTime: Optional[str] = None endTime: Optional[str] = None version: Optional[str] = None class Config: extra = "allow" + allow_population_by_field_name = True # Allows using field name for input data class WRROCData(WRROCDataBase): @@ -101,7 +102,7 @@ class WRROCProcess(BaseModel): profiles (Optional[list[AnyUrl]]): URLs to the RO-Crate profiles used. """ - id: str + id: str = Field(alias='@id') name: str description: Optional[str] = "" startTime: Optional[str] = None @@ -111,6 +112,7 @@ class WRROCProcess(BaseModel): class Config: extra = "allow" + allow_population_by_field_name = True class WRROCWorkflow(WRROCProcess): @@ -131,6 +133,7 @@ class WRROCWorkflow(WRROCProcess): class Config: extra = "allow" + allow_population_by_field_name = True class WRROCProvenance(WRROCWorkflow): @@ -153,3 +156,4 @@ class WRROCProvenance(WRROCWorkflow): class Config: extra = "allow" + allow_population_by_field_name = True diff --git a/poetry.lock b/poetry.lock index 3d2faaa..41f1952 100644 --- a/poetry.lock +++ b/poetry.lock @@ -13,13 +13,13 @@ files = [ [[package]] name = "authlib" -version = "1.3.1" +version = "1.3.2" description = "The ultimate Python library in building OAuth and OpenID Connect servers and clients." optional = false python-versions = ">=3.8" files = [ - {file = "Authlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:d35800b973099bbadc49b42b256ecb80041ad56b7fe1216a362c7943c088f377"}, - {file = "authlib-1.3.1.tar.gz", hash = "sha256:7ae843f03c06c5c0debd63c9db91f9fda64fa62a42a77419fa15fbb7e7a58917"}, + {file = "Authlib-1.3.2-py2.py3-none-any.whl", hash = "sha256:ede026a95e9f5cdc2d4364a52103f5405e75aa156357e831ef2bfd0bc5094dfc"}, + {file = "authlib-1.3.2.tar.gz", hash = "sha256:4b16130117f9eb82aa6eec97f6dd4673c3f960ac0283ccdae2897ee4bc030ba2"}, ] [package.dependencies] @@ -27,13 +27,13 @@ cryptography = "*" [[package]] name = "babel" -version = "2.15.0" +version = "2.16.0" description = "Internationalization utilities" optional = false python-versions = ">=3.8" files = [ - {file = "Babel-2.15.0-py3-none-any.whl", hash = "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb"}, - {file = "babel-2.15.0.tar.gz", hash = "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413"}, + {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, + {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, ] [package.extras] @@ -86,74 +86,89 @@ lxml = ["lxml"] [[package]] name = "certifi" -version = "2024.7.4" +version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, - {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, ] [[package]] name = "cffi" -version = "1.16.0" +version = "1.17.0" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" files = [ - {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, - {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, - {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, - {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, - {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, - {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, - {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, - {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, - {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, - {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, - {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, - {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, - {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, - {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, - {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, + {file = "cffi-1.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f9338cc05451f1942d0d8203ec2c346c830f8e86469903d5126c1f0a13a2bcbb"}, + {file = "cffi-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0ce71725cacc9ebf839630772b07eeec220cbb5f03be1399e0457a1464f8e1a"}, + {file = "cffi-1.17.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c815270206f983309915a6844fe994b2fa47e5d05c4c4cef267c3b30e34dbe42"}, + {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6bdcd415ba87846fd317bee0774e412e8792832e7805938987e4ede1d13046d"}, + {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a98748ed1a1df4ee1d6f927e151ed6c1a09d5ec21684de879c7ea6aa96f58f2"}, + {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a048d4f6630113e54bb4b77e315e1ba32a5a31512c31a273807d0027a7e69ab"}, + {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24aa705a5f5bd3a8bcfa4d123f03413de5d86e497435693b638cbffb7d5d8a1b"}, + {file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:856bf0924d24e7f93b8aee12a3a1095c34085600aa805693fb7f5d1962393206"}, + {file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:4304d4416ff032ed50ad6bb87416d802e67139e31c0bde4628f36a47a3164bfa"}, + {file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:331ad15c39c9fe9186ceaf87203a9ecf5ae0ba2538c9e898e3a6967e8ad3db6f"}, + {file = "cffi-1.17.0-cp310-cp310-win32.whl", hash = "sha256:669b29a9eca6146465cc574659058ed949748f0809a2582d1f1a324eb91054dc"}, + {file = "cffi-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:48b389b1fd5144603d61d752afd7167dfd205973a43151ae5045b35793232aa2"}, + {file = "cffi-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5d97162c196ce54af6700949ddf9409e9833ef1003b4741c2b39ef46f1d9720"}, + {file = "cffi-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ba5c243f4004c750836f81606a9fcb7841f8874ad8f3bf204ff5e56332b72b9"}, + {file = "cffi-1.17.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb9333f58fc3a2296fb1d54576138d4cf5d496a2cc118422bd77835e6ae0b9cb"}, + {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:435a22d00ec7d7ea533db494da8581b05977f9c37338c80bc86314bec2619424"}, + {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1df34588123fcc88c872f5acb6f74ae59e9d182a2707097f9e28275ec26a12d"}, + {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df8bb0010fdd0a743b7542589223a2816bdde4d94bb5ad67884348fa2c1c67e8"}, + {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8b5b9712783415695663bd463990e2f00c6750562e6ad1d28e072a611c5f2a6"}, + {file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ffef8fd58a36fb5f1196919638f73dd3ae0db1a878982b27a9a5a176ede4ba91"}, + {file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e67d26532bfd8b7f7c05d5a766d6f437b362c1bf203a3a5ce3593a645e870b8"}, + {file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45f7cd36186db767d803b1473b3c659d57a23b5fa491ad83c6d40f2af58e4dbb"}, + {file = "cffi-1.17.0-cp311-cp311-win32.whl", hash = "sha256:a9015f5b8af1bb6837a3fcb0cdf3b874fe3385ff6274e8b7925d81ccaec3c5c9"}, + {file = "cffi-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:b50aaac7d05c2c26dfd50c3321199f019ba76bb650e346a6ef3616306eed67b0"}, + {file = "cffi-1.17.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aec510255ce690d240f7cb23d7114f6b351c733a74c279a84def763660a2c3bc"}, + {file = "cffi-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2770bb0d5e3cc0e31e7318db06efcbcdb7b31bcb1a70086d3177692a02256f59"}, + {file = "cffi-1.17.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db9a30ec064129d605d0f1aedc93e00894b9334ec74ba9c6bdd08147434b33eb"}, + {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a47eef975d2b8b721775a0fa286f50eab535b9d56c70a6e62842134cf7841195"}, + {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3e0992f23bbb0be00a921eae5363329253c3b86287db27092461c887b791e5e"}, + {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6107e445faf057c118d5050560695e46d272e5301feffda3c41849641222a828"}, + {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb862356ee9391dc5a0b3cbc00f416b48c1b9a52d252d898e5b7696a5f9fe150"}, + {file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c1c13185b90bbd3f8b5963cd8ce7ad4ff441924c31e23c975cb150e27c2bf67a"}, + {file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17c6d6d3260c7f2d94f657e6872591fe8733872a86ed1345bda872cfc8c74885"}, + {file = "cffi-1.17.0-cp312-cp312-win32.whl", hash = "sha256:c3b8bd3133cd50f6b637bb4322822c94c5ce4bf0d724ed5ae70afce62187c492"}, + {file = "cffi-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:dca802c8db0720ce1c49cce1149ff7b06e91ba15fa84b1d59144fef1a1bc7ac2"}, + {file = "cffi-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ce01337d23884b21c03869d2f68c5523d43174d4fc405490eb0091057943118"}, + {file = "cffi-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cab2eba3830bf4f6d91e2d6718e0e1c14a2f5ad1af68a89d24ace0c6b17cced7"}, + {file = "cffi-1.17.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14b9cbc8f7ac98a739558eb86fabc283d4d564dafed50216e7f7ee62d0d25377"}, + {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b00e7bcd71caa0282cbe3c90966f738e2db91e64092a877c3ff7f19a1628fdcb"}, + {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41f4915e09218744d8bae14759f983e466ab69b178de38066f7579892ff2a555"}, + {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4760a68cab57bfaa628938e9c2971137e05ce48e762a9cb53b76c9b569f1204"}, + {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:011aff3524d578a9412c8b3cfaa50f2c0bd78e03eb7af7aa5e0df59b158efb2f"}, + {file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a003ac9edc22d99ae1286b0875c460351f4e101f8c9d9d2576e78d7e048f64e0"}, + {file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ef9528915df81b8f4c7612b19b8628214c65c9b7f74db2e34a646a0a2a0da2d4"}, + {file = "cffi-1.17.0-cp313-cp313-win32.whl", hash = "sha256:70d2aa9fb00cf52034feac4b913181a6e10356019b18ef89bc7c12a283bf5f5a"}, + {file = "cffi-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:b7b6ea9e36d32582cda3465f54c4b454f62f23cb083ebc7a94e2ca6ef011c3a7"}, + {file = "cffi-1.17.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:964823b2fc77b55355999ade496c54dde161c621cb1f6eac61dc30ed1b63cd4c"}, + {file = "cffi-1.17.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:516a405f174fd3b88829eabfe4bb296ac602d6a0f68e0d64d5ac9456194a5b7e"}, + {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dec6b307ce928e8e112a6bb9921a1cb00a0e14979bf28b98e084a4b8a742bd9b"}, + {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4094c7b464cf0a858e75cd14b03509e84789abf7b79f8537e6a72152109c76e"}, + {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2404f3de742f47cb62d023f0ba7c5a916c9c653d5b368cc966382ae4e57da401"}, + {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa9d43b02a0c681f0bfbc12d476d47b2b2b6a3f9287f11ee42989a268a1833c"}, + {file = "cffi-1.17.0-cp38-cp38-win32.whl", hash = "sha256:0bb15e7acf8ab35ca8b24b90af52c8b391690ef5c4aec3d31f38f0d37d2cc499"}, + {file = "cffi-1.17.0-cp38-cp38-win_amd64.whl", hash = "sha256:93a7350f6706b31f457c1457d3a3259ff9071a66f312ae64dc024f049055f72c"}, + {file = "cffi-1.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a2ddbac59dc3716bc79f27906c010406155031a1c801410f1bafff17ea304d2"}, + {file = "cffi-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6327b572f5770293fc062a7ec04160e89741e8552bf1c358d1a23eba68166759"}, + {file = "cffi-1.17.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbc183e7bef690c9abe5ea67b7b60fdbca81aa8da43468287dae7b5c046107d4"}, + {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bdc0f1f610d067c70aa3737ed06e2726fd9d6f7bfee4a351f4c40b6831f4e82"}, + {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d872186c1617d143969defeadac5a904e6e374183e07977eedef9c07c8953bf"}, + {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d46ee4764b88b91f16661a8befc6bfb24806d885e27436fdc292ed7e6f6d058"}, + {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f76a90c345796c01d85e6332e81cab6d70de83b829cf1d9762d0a3da59c7932"}, + {file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e60821d312f99d3e1569202518dddf10ae547e799d75aef3bca3a2d9e8ee693"}, + {file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:eb09b82377233b902d4c3fbeeb7ad731cdab579c6c6fda1f763cd779139e47c3"}, + {file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:24658baf6224d8f280e827f0a50c46ad819ec8ba380a42448e24459daf809cf4"}, + {file = "cffi-1.17.0-cp39-cp39-win32.whl", hash = "sha256:0fdacad9e0d9fc23e519efd5ea24a70348305e8d7d85ecbb1a5fa66dc834e7fb"}, + {file = "cffi-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:7cbc78dc018596315d4e7841c8c3a7ae31cc4d638c9b627f87d52e8abaaf2d29"}, + {file = "cffi-1.17.0.tar.gz", hash = "sha256:f3157624b7558b914cb039fd1af735e5e8049a87c817cc215109ad1c8779df76"}, ] [package.dependencies] @@ -296,63 +311,83 @@ files = [ [[package]] name = "coverage" -version = "7.6.0" +version = "7.6.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dff044f661f59dace805eedb4a7404c573b6ff0cdba4a524141bc63d7be5c7fd"}, - {file = "coverage-7.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8659fd33ee9e6ca03950cfdcdf271d645cf681609153f218826dd9805ab585c"}, - {file = "coverage-7.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7792f0ab20df8071d669d929c75c97fecfa6bcab82c10ee4adb91c7a54055463"}, - {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4b3cd1ca7cd73d229487fa5caca9e4bc1f0bca96526b922d61053ea751fe791"}, - {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7e128f85c0b419907d1f38e616c4f1e9f1d1b37a7949f44df9a73d5da5cd53c"}, - {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a94925102c89247530ae1dab7dc02c690942566f22e189cbd53579b0693c0783"}, - {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dcd070b5b585b50e6617e8972f3fbbee786afca71b1936ac06257f7e178f00f6"}, - {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d50a252b23b9b4dfeefc1f663c568a221092cbaded20a05a11665d0dbec9b8fb"}, - {file = "coverage-7.6.0-cp310-cp310-win32.whl", hash = "sha256:0e7b27d04131c46e6894f23a4ae186a6a2207209a05df5b6ad4caee6d54a222c"}, - {file = "coverage-7.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:54dece71673b3187c86226c3ca793c5f891f9fc3d8aa183f2e3653da18566169"}, - {file = "coverage-7.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7b525ab52ce18c57ae232ba6f7010297a87ced82a2383b1afd238849c1ff933"}, - {file = "coverage-7.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bea27c4269234e06f621f3fac3925f56ff34bc14521484b8f66a580aacc2e7d"}, - {file = "coverage-7.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed8d1d1821ba5fc88d4a4f45387b65de52382fa3ef1f0115a4f7a20cdfab0e94"}, - {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01c322ef2bbe15057bc4bf132b525b7e3f7206f071799eb8aa6ad1940bcf5fb1"}, - {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03cafe82c1b32b770a29fd6de923625ccac3185a54a5e66606da26d105f37dac"}, - {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d1b923fc4a40c5832be4f35a5dab0e5ff89cddf83bb4174499e02ea089daf57"}, - {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4b03741e70fb811d1a9a1d75355cf391f274ed85847f4b78e35459899f57af4d"}, - {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a73d18625f6a8a1cbb11eadc1d03929f9510f4131879288e3f7922097a429f63"}, - {file = "coverage-7.6.0-cp311-cp311-win32.whl", hash = "sha256:65fa405b837060db569a61ec368b74688f429b32fa47a8929a7a2f9b47183713"}, - {file = "coverage-7.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:6379688fb4cfa921ae349c76eb1a9ab26b65f32b03d46bb0eed841fd4cb6afb1"}, - {file = "coverage-7.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f7db0b6ae1f96ae41afe626095149ecd1b212b424626175a6633c2999eaad45b"}, - {file = "coverage-7.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bbdf9a72403110a3bdae77948b8011f644571311c2fb35ee15f0f10a8fc082e8"}, - {file = "coverage-7.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc44bf0315268e253bf563f3560e6c004efe38f76db03a1558274a6e04bf5d5"}, - {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da8549d17489cd52f85a9829d0e1d91059359b3c54a26f28bec2c5d369524807"}, - {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0086cd4fc71b7d485ac93ca4239c8f75732c2ae3ba83f6be1c9be59d9e2c6382"}, - {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fad32ee9b27350687035cb5fdf9145bc9cf0a094a9577d43e909948ebcfa27b"}, - {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:044a0985a4f25b335882b0966625270a8d9db3d3409ddc49a4eb00b0ef5e8cee"}, - {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:76d5f82213aa78098b9b964ea89de4617e70e0d43e97900c2778a50856dac605"}, - {file = "coverage-7.6.0-cp312-cp312-win32.whl", hash = "sha256:3c59105f8d58ce500f348c5b56163a4113a440dad6daa2294b5052a10db866da"}, - {file = "coverage-7.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:ca5d79cfdae420a1d52bf177de4bc2289c321d6c961ae321503b2ca59c17ae67"}, - {file = "coverage-7.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d39bd10f0ae453554798b125d2f39884290c480f56e8a02ba7a6ed552005243b"}, - {file = "coverage-7.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beb08e8508e53a568811016e59f3234d29c2583f6b6e28572f0954a6b4f7e03d"}, - {file = "coverage-7.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2e16f4cd2bc4d88ba30ca2d3bbf2f21f00f382cf4e1ce3b1ddc96c634bc48ca"}, - {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6616d1c9bf1e3faea78711ee42a8b972367d82ceae233ec0ac61cc7fec09fa6b"}, - {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad4567d6c334c46046d1c4c20024de2a1c3abc626817ae21ae3da600f5779b44"}, - {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d17c6a415d68cfe1091d3296ba5749d3d8696e42c37fca5d4860c5bf7b729f03"}, - {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9146579352d7b5f6412735d0f203bbd8d00113a680b66565e205bc605ef81bc6"}, - {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cdab02a0a941af190df8782aafc591ef3ad08824f97850b015c8c6a8b3877b0b"}, - {file = "coverage-7.6.0-cp38-cp38-win32.whl", hash = "sha256:df423f351b162a702c053d5dddc0fc0ef9a9e27ea3f449781ace5f906b664428"}, - {file = "coverage-7.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:f2501d60d7497fd55e391f423f965bbe9e650e9ffc3c627d5f0ac516026000b8"}, - {file = "coverage-7.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7221f9ac9dad9492cecab6f676b3eaf9185141539d5c9689d13fd6b0d7de840c"}, - {file = "coverage-7.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ddaaa91bfc4477d2871442bbf30a125e8fe6b05da8a0015507bfbf4718228ab2"}, - {file = "coverage-7.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4cbe651f3904e28f3a55d6f371203049034b4ddbce65a54527a3f189ca3b390"}, - {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831b476d79408ab6ccfadaaf199906c833f02fdb32c9ab907b1d4aa0713cfa3b"}, - {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46c3d091059ad0b9c59d1034de74a7f36dcfa7f6d3bde782c49deb42438f2450"}, - {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4d5fae0a22dc86259dee66f2cc6c1d3e490c4a1214d7daa2a93d07491c5c04b6"}, - {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:07ed352205574aad067482e53dd606926afebcb5590653121063fbf4e2175166"}, - {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:49c76cdfa13015c4560702574bad67f0e15ca5a2872c6a125f6327ead2b731dd"}, - {file = "coverage-7.6.0-cp39-cp39-win32.whl", hash = "sha256:482855914928c8175735a2a59c8dc5806cf7d8f032e4820d52e845d1f731dca2"}, - {file = "coverage-7.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:543ef9179bc55edfd895154a51792b01c017c87af0ebaae092720152e19e42ca"}, - {file = "coverage-7.6.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:6fe885135c8a479d3e37a7aae61cbd3a0fb2deccb4dda3c25f92a49189f766d6"}, - {file = "coverage-7.6.0.tar.gz", hash = "sha256:289cc803fa1dc901f84701ac10c9ee873619320f2f9aff38794db4a4a0268d51"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, + {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, + {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, + {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, + {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, + {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, + {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, + {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, + {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, + {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, + {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, + {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, + {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, + {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, + {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, + {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, + {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, ] [package.extras] @@ -467,19 +502,19 @@ typing = ["typing-extensions (>=4.8)"] [[package]] name = "furo" -version = "2024.7.18" +version = "2024.8.6" description = "A clean customisable Sphinx documentation theme." optional = false python-versions = ">=3.8" files = [ - {file = "furo-2024.7.18-py3-none-any.whl", hash = "sha256:b192c7c1f59805494c8ed606d9375fdac6e6ba8178e747e72bc116745fb7e13f"}, - {file = "furo-2024.7.18.tar.gz", hash = "sha256:37b08c5fccc95d46d8712c8be97acd46043963895edde05b0f4f135d58325c83"}, + {file = "furo-2024.8.6-py3-none-any.whl", hash = "sha256:6cd97c58b47813d3619e63e9081169880fbe331f0ca883c871ff1f3f11814f5c"}, + {file = "furo-2024.8.6.tar.gz", hash = "sha256:b63e4cee8abfc3136d3bc03a3d45a76a850bada4d6374d24c1716b0e01394a01"}, ] [package.dependencies] beautifulsoup4 = "*" pygments = ">=2.7" -sphinx = ">=6.0,<8.0" +sphinx = ">=6.0,<9.0" sphinx-basic-ng = ">=1.0.0.beta2" [[package]] @@ -498,13 +533,13 @@ license = ["ukkonen"] [[package]] name = "idna" -version = "3.7" +version = "3.8" description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" files = [ - {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, - {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, + {file = "idna-3.8-py3-none-any.whl", hash = "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac"}, + {file = "idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"}, ] [[package]] @@ -641,13 +676,13 @@ files = [ [[package]] name = "marshmallow" -version = "3.21.3" +version = "3.22.0" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false python-versions = ">=3.8" files = [ - {file = "marshmallow-3.21.3-py3-none-any.whl", hash = "sha256:86ce7fb914aa865001a4b2092c4c2872d13bc347f3d42673272cabfdbad386f1"}, - {file = "marshmallow-3.21.3.tar.gz", hash = "sha256:4f57c5e050a54d66361e826f94fba213eb10b67b2fdb02c3e0343ce207ba1662"}, + {file = "marshmallow-3.22.0-py3-none-any.whl", hash = "sha256:71a2dce49ef901c3f97ed296ae5051135fd3febd2bf43afe0ae9a82143a494d9"}, + {file = "marshmallow-3.22.0.tar.gz", hash = "sha256:4972f529104a220bb8637d595aa4c9762afbe7f7a77d82dc58c1615d70c5823e"}, ] [package.dependencies] @@ -655,7 +690,7 @@ packaging = ">=17.0" [package.extras] dev = ["marshmallow[tests]", "pre-commit (>=3.5,<4.0)", "tox"] -docs = ["alabaster (==0.7.16)", "autodocsumm (==0.2.12)", "sphinx (==7.3.7)", "sphinx-issues (==4.1.0)", "sphinx-version-warning (==1.1.2)"] +docs = ["alabaster (==1.0.0)", "autodocsumm (==0.2.13)", "sphinx (==8.0.2)", "sphinx-issues (==4.1.0)", "sphinx-version-warning (==1.1.2)"] tests = ["pytest", "pytz", "simplejson"] [[package]] @@ -671,38 +706,38 @@ files = [ [[package]] name = "mypy" -version = "1.11.0" +version = "1.11.2" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3824187c99b893f90c845bab405a585d1ced4ff55421fdf5c84cb7710995229"}, - {file = "mypy-1.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:96f8dbc2c85046c81bcddc246232d500ad729cb720da4e20fce3b542cab91287"}, - {file = "mypy-1.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a5d8d8dd8613a3e2be3eae829ee891b6b2de6302f24766ff06cb2875f5be9c6"}, - {file = "mypy-1.11.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:72596a79bbfb195fd41405cffa18210af3811beb91ff946dbcb7368240eed6be"}, - {file = "mypy-1.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:35ce88b8ed3a759634cb4eb646d002c4cef0a38f20565ee82b5023558eb90c00"}, - {file = "mypy-1.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:98790025861cb2c3db8c2f5ad10fc8c336ed2a55f4daf1b8b3f877826b6ff2eb"}, - {file = "mypy-1.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:25bcfa75b9b5a5f8d67147a54ea97ed63a653995a82798221cca2a315c0238c1"}, - {file = "mypy-1.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bea2a0e71c2a375c9fa0ede3d98324214d67b3cbbfcbd55ac8f750f85a414e3"}, - {file = "mypy-1.11.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d2b3d36baac48e40e3064d2901f2fbd2a2d6880ec6ce6358825c85031d7c0d4d"}, - {file = "mypy-1.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:d8e2e43977f0e09f149ea69fd0556623919f816764e26d74da0c8a7b48f3e18a"}, - {file = "mypy-1.11.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1d44c1e44a8be986b54b09f15f2c1a66368eb43861b4e82573026e04c48a9e20"}, - {file = "mypy-1.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cea3d0fb69637944dd321f41bc896e11d0fb0b0aa531d887a6da70f6e7473aba"}, - {file = "mypy-1.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a83ec98ae12d51c252be61521aa5731f5512231d0b738b4cb2498344f0b840cd"}, - {file = "mypy-1.11.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c7b73a856522417beb78e0fb6d33ef89474e7a622db2653bc1285af36e2e3e3d"}, - {file = "mypy-1.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:f2268d9fcd9686b61ab64f077be7ffbc6fbcdfb4103e5dd0cc5eaab53a8886c2"}, - {file = "mypy-1.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:940bfff7283c267ae6522ef926a7887305945f716a7704d3344d6d07f02df850"}, - {file = "mypy-1.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:14f9294528b5f5cf96c721f231c9f5b2733164e02c1c018ed1a0eff8a18005ac"}, - {file = "mypy-1.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7b54c27783991399046837df5c7c9d325d921394757d09dbcbf96aee4649fe9"}, - {file = "mypy-1.11.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:65f190a6349dec29c8d1a1cd4aa71284177aee5949e0502e6379b42873eddbe7"}, - {file = "mypy-1.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:dbe286303241fea8c2ea5466f6e0e6a046a135a7e7609167b07fd4e7baf151bf"}, - {file = "mypy-1.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:104e9c1620c2675420abd1f6c44bab7dd33cc85aea751c985006e83dcd001095"}, - {file = "mypy-1.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f006e955718ecd8d159cee9932b64fba8f86ee6f7728ca3ac66c3a54b0062abe"}, - {file = "mypy-1.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:becc9111ca572b04e7e77131bc708480cc88a911adf3d0239f974c034b78085c"}, - {file = "mypy-1.11.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6801319fe76c3f3a3833f2b5af7bd2c17bb93c00026a2a1b924e6762f5b19e13"}, - {file = "mypy-1.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:c1a184c64521dc549324ec6ef7cbaa6b351912be9cb5edb803c2808a0d7e85ac"}, - {file = "mypy-1.11.0-py3-none-any.whl", hash = "sha256:56913ec8c7638b0091ef4da6fcc9136896914a9d60d54670a75880c3e5b99ace"}, - {file = "mypy-1.11.0.tar.gz", hash = "sha256:93743608c7348772fdc717af4aeee1997293a1ad04bc0ea6efa15bf65385c538"}, + {file = "mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a"}, + {file = "mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef"}, + {file = "mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383"}, + {file = "mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8"}, + {file = "mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7"}, + {file = "mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385"}, + {file = "mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca"}, + {file = "mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104"}, + {file = "mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4"}, + {file = "mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6"}, + {file = "mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318"}, + {file = "mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36"}, + {file = "mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987"}, + {file = "mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca"}, + {file = "mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70"}, + {file = "mypy-1.11.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b"}, + {file = "mypy-1.11.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86"}, + {file = "mypy-1.11.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce"}, + {file = "mypy-1.11.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1"}, + {file = "mypy-1.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b"}, + {file = "mypy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6"}, + {file = "mypy-1.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70"}, + {file = "mypy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d"}, + {file = "mypy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d"}, + {file = "mypy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24"}, + {file = "mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12"}, + {file = "mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79"}, ] [package.dependencies] @@ -750,13 +785,13 @@ files = [ [[package]] name = "pbr" -version = "6.0.0" +version = "6.1.0" description = "Python Build Reasonableness" optional = false python-versions = ">=2.6" files = [ - {file = "pbr-6.0.0-py2.py3-none-any.whl", hash = "sha256:4a7317d5e3b17a3dccb6a8cfe67dab65b20551404c52c8ed41279fa4f0cb4cda"}, - {file = "pbr-6.0.0.tar.gz", hash = "sha256:d1377122a5a00e2f940ee482999518efe16d745d423a670c27773dfbc3c9a7d9"}, + {file = "pbr-6.1.0-py2.py3-none-any.whl", hash = "sha256:a776ae228892d8013649c0aeccbb3d5f99ee15e005a4cbb7e61d55a067b28a2a"}, + {file = "pbr-6.1.0.tar.gz", hash = "sha256:788183e382e3d1d7707db08978239965e8b9e4e5ed42669bf4758186734d5f24"}, ] [[package]] @@ -821,54 +856,54 @@ files = [ [[package]] name = "pydantic" -version = "1.10.17" +version = "1.10.18" description = "Data validation and settings management using python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.17-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fa51175313cc30097660b10eec8ca55ed08bfa07acbfe02f7a42f6c242e9a4b"}, - {file = "pydantic-1.10.17-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7e8988bb16988890c985bd2093df9dd731bfb9d5e0860db054c23034fab8f7a"}, - {file = "pydantic-1.10.17-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:371dcf1831f87c9e217e2b6a0c66842879a14873114ebb9d0861ab22e3b5bb1e"}, - {file = "pydantic-1.10.17-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4866a1579c0c3ca2c40575398a24d805d4db6cb353ee74df75ddeee3c657f9a7"}, - {file = "pydantic-1.10.17-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:543da3c6914795b37785703ffc74ba4d660418620cc273490d42c53949eeeca6"}, - {file = "pydantic-1.10.17-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7623b59876f49e61c2e283551cc3647616d2fbdc0b4d36d3d638aae8547ea681"}, - {file = "pydantic-1.10.17-cp310-cp310-win_amd64.whl", hash = "sha256:409b2b36d7d7d19cd8310b97a4ce6b1755ef8bd45b9a2ec5ec2b124db0a0d8f3"}, - {file = "pydantic-1.10.17-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fa43f362b46741df8f201bf3e7dff3569fa92069bcc7b4a740dea3602e27ab7a"}, - {file = "pydantic-1.10.17-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2a72d2a5ff86a3075ed81ca031eac86923d44bc5d42e719d585a8eb547bf0c9b"}, - {file = "pydantic-1.10.17-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4ad32aed3bf5eea5ca5decc3d1bbc3d0ec5d4fbcd72a03cdad849458decbc63"}, - {file = "pydantic-1.10.17-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb4e741782e236ee7dc1fb11ad94dc56aabaf02d21df0e79e0c21fe07c95741"}, - {file = "pydantic-1.10.17-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d2f89a719411cb234105735a520b7c077158a81e0fe1cb05a79c01fc5eb59d3c"}, - {file = "pydantic-1.10.17-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db3b48d9283d80a314f7a682f7acae8422386de659fffaba454b77a083c3937d"}, - {file = "pydantic-1.10.17-cp311-cp311-win_amd64.whl", hash = "sha256:9c803a5113cfab7bbb912f75faa4fc1e4acff43e452c82560349fff64f852e1b"}, - {file = "pydantic-1.10.17-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:820ae12a390c9cbb26bb44913c87fa2ff431a029a785642c1ff11fed0a095fcb"}, - {file = "pydantic-1.10.17-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c1e51d1af306641b7d1574d6d3307eaa10a4991542ca324f0feb134fee259815"}, - {file = "pydantic-1.10.17-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e53fb834aae96e7b0dadd6e92c66e7dd9cdf08965340ed04c16813102a47fab"}, - {file = "pydantic-1.10.17-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e2495309b1266e81d259a570dd199916ff34f7f51f1b549a0d37a6d9b17b4dc"}, - {file = "pydantic-1.10.17-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:098ad8de840c92ea586bf8efd9e2e90c6339d33ab5c1cfbb85be66e4ecf8213f"}, - {file = "pydantic-1.10.17-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:525bbef620dac93c430d5d6bdbc91bdb5521698d434adf4434a7ef6ffd5c4b7f"}, - {file = "pydantic-1.10.17-cp312-cp312-win_amd64.whl", hash = "sha256:6654028d1144df451e1da69a670083c27117d493f16cf83da81e1e50edce72ad"}, - {file = "pydantic-1.10.17-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c87cedb4680d1614f1d59d13fea353faf3afd41ba5c906a266f3f2e8c245d655"}, - {file = "pydantic-1.10.17-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11289fa895bcbc8f18704efa1d8020bb9a86314da435348f59745473eb042e6b"}, - {file = "pydantic-1.10.17-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94833612d6fd18b57c359a127cbfd932d9150c1b72fea7c86ab58c2a77edd7c7"}, - {file = "pydantic-1.10.17-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d4ecb515fa7cb0e46e163ecd9d52f9147ba57bc3633dca0e586cdb7a232db9e3"}, - {file = "pydantic-1.10.17-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7017971ffa7fd7808146880aa41b266e06c1e6e12261768a28b8b41ba55c8076"}, - {file = "pydantic-1.10.17-cp37-cp37m-win_amd64.whl", hash = "sha256:e840e6b2026920fc3f250ea8ebfdedf6ea7a25b77bf04c6576178e681942ae0f"}, - {file = "pydantic-1.10.17-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bfbb18b616abc4df70591b8c1ff1b3eabd234ddcddb86b7cac82657ab9017e33"}, - {file = "pydantic-1.10.17-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ebb249096d873593e014535ab07145498957091aa6ae92759a32d40cb9998e2e"}, - {file = "pydantic-1.10.17-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8c209af63ccd7b22fba94b9024e8b7fd07feffee0001efae50dd99316b27768"}, - {file = "pydantic-1.10.17-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4b40c9e13a0b61583e5599e7950490c700297b4a375b55b2b592774332798b7"}, - {file = "pydantic-1.10.17-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c31d281c7485223caf6474fc2b7cf21456289dbaa31401844069b77160cab9c7"}, - {file = "pydantic-1.10.17-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae5184e99a060a5c80010a2d53c99aee76a3b0ad683d493e5f0620b5d86eeb75"}, - {file = "pydantic-1.10.17-cp38-cp38-win_amd64.whl", hash = "sha256:ad1e33dc6b9787a6f0f3fd132859aa75626528b49cc1f9e429cdacb2608ad5f0"}, - {file = "pydantic-1.10.17-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e17c0ee7192e54a10943f245dc79e36d9fe282418ea05b886e1c666063a7b54"}, - {file = "pydantic-1.10.17-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cafb9c938f61d1b182dfc7d44a7021326547b7b9cf695db5b68ec7b590214773"}, - {file = "pydantic-1.10.17-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95ef534e3c22e5abbdbdd6f66b6ea9dac3ca3e34c5c632894f8625d13d084cbe"}, - {file = "pydantic-1.10.17-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62d96b8799ae3d782df7ec9615cb59fc32c32e1ed6afa1b231b0595f6516e8ab"}, - {file = "pydantic-1.10.17-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ab2f976336808fd5d539fdc26eb51f9aafc1f4b638e212ef6b6f05e753c8011d"}, - {file = "pydantic-1.10.17-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8ad363330557beac73159acfbeed220d5f1bfcd6b930302a987a375e02f74fd"}, - {file = "pydantic-1.10.17-cp39-cp39-win_amd64.whl", hash = "sha256:48db882e48575ce4b39659558b2f9f37c25b8d348e37a2b4e32971dd5a7d6227"}, - {file = "pydantic-1.10.17-py3-none-any.whl", hash = "sha256:e41b5b973e5c64f674b3b4720286ded184dcc26a691dd55f34391c62c6934688"}, - {file = "pydantic-1.10.17.tar.gz", hash = "sha256:f434160fb14b353caf634149baaf847206406471ba70e64657c1e8330277a991"}, + {file = "pydantic-1.10.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e405ffcc1254d76bb0e760db101ee8916b620893e6edfbfee563b3c6f7a67c02"}, + {file = "pydantic-1.10.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e306e280ebebc65040034bff1a0a81fd86b2f4f05daac0131f29541cafd80b80"}, + {file = "pydantic-1.10.18-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11d9d9b87b50338b1b7de4ebf34fd29fdb0d219dc07ade29effc74d3d2609c62"}, + {file = "pydantic-1.10.18-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b661ce52c7b5e5f600c0c3c5839e71918346af2ef20062705ae76b5c16914cab"}, + {file = "pydantic-1.10.18-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c20f682defc9ef81cd7eaa485879ab29a86a0ba58acf669a78ed868e72bb89e0"}, + {file = "pydantic-1.10.18-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c5ae6b7c8483b1e0bf59e5f1843e4fd8fd405e11df7de217ee65b98eb5462861"}, + {file = "pydantic-1.10.18-cp310-cp310-win_amd64.whl", hash = "sha256:74fe19dda960b193b0eb82c1f4d2c8e5e26918d9cda858cbf3f41dd28549cb70"}, + {file = "pydantic-1.10.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:72fa46abace0a7743cc697dbb830a41ee84c9db8456e8d77a46d79b537efd7ec"}, + {file = "pydantic-1.10.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ef0fe7ad7cbdb5f372463d42e6ed4ca9c443a52ce544472d8842a0576d830da5"}, + {file = "pydantic-1.10.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a00e63104346145389b8e8f500bc6a241e729feaf0559b88b8aa513dd2065481"}, + {file = "pydantic-1.10.18-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae6fa2008e1443c46b7b3a5eb03800121868d5ab6bc7cda20b5df3e133cde8b3"}, + {file = "pydantic-1.10.18-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9f463abafdc92635da4b38807f5b9972276be7c8c5121989768549fceb8d2588"}, + {file = "pydantic-1.10.18-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3445426da503c7e40baccefb2b2989a0c5ce6b163679dd75f55493b460f05a8f"}, + {file = "pydantic-1.10.18-cp311-cp311-win_amd64.whl", hash = "sha256:467a14ee2183bc9c902579bb2f04c3d3dac00eff52e252850509a562255b2a33"}, + {file = "pydantic-1.10.18-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:efbc8a7f9cb5fe26122acba1852d8dcd1e125e723727c59dcd244da7bdaa54f2"}, + {file = "pydantic-1.10.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:24a4a159d0f7a8e26bf6463b0d3d60871d6a52eac5bb6a07a7df85c806f4c048"}, + {file = "pydantic-1.10.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b74be007703547dc52e3c37344d130a7bfacca7df112a9e5ceeb840a9ce195c7"}, + {file = "pydantic-1.10.18-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcb20d4cb355195c75000a49bb4a31d75e4295200df620f454bbc6bdf60ca890"}, + {file = "pydantic-1.10.18-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:46f379b8cb8a3585e3f61bf9ae7d606c70d133943f339d38b76e041ec234953f"}, + {file = "pydantic-1.10.18-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cbfbca662ed3729204090c4d09ee4beeecc1a7ecba5a159a94b5a4eb24e3759a"}, + {file = "pydantic-1.10.18-cp312-cp312-win_amd64.whl", hash = "sha256:c6d0a9f9eccaf7f438671a64acf654ef0d045466e63f9f68a579e2383b63f357"}, + {file = "pydantic-1.10.18-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3d5492dbf953d7d849751917e3b2433fb26010d977aa7a0765c37425a4026ff1"}, + {file = "pydantic-1.10.18-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe734914977eed33033b70bfc097e1baaffb589517863955430bf2e0846ac30f"}, + {file = "pydantic-1.10.18-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15fdbe568beaca9aacfccd5ceadfb5f1a235087a127e8af5e48df9d8a45ae85c"}, + {file = "pydantic-1.10.18-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c3e742f62198c9eb9201781fbebe64533a3bbf6a76a91b8d438d62b813079dbc"}, + {file = "pydantic-1.10.18-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:19a3bd00b9dafc2cd7250d94d5b578edf7a0bd7daf102617153ff9a8fa37871c"}, + {file = "pydantic-1.10.18-cp37-cp37m-win_amd64.whl", hash = "sha256:2ce3fcf75b2bae99aa31bd4968de0474ebe8c8258a0110903478bd83dfee4e3b"}, + {file = "pydantic-1.10.18-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:335a32d72c51a313b33fa3a9b0fe283503272ef6467910338e123f90925f0f03"}, + {file = "pydantic-1.10.18-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:34a3613c7edb8c6fa578e58e9abe3c0f5e7430e0fc34a65a415a1683b9c32d9a"}, + {file = "pydantic-1.10.18-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9ee4e6ca1d9616797fa2e9c0bfb8815912c7d67aca96f77428e316741082a1b"}, + {file = "pydantic-1.10.18-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23e8ec1ce4e57b4f441fc91e3c12adba023fedd06868445a5b5f1d48f0ab3682"}, + {file = "pydantic-1.10.18-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:44ae8a3e35a54d2e8fa88ed65e1b08967a9ef8c320819a969bfa09ce5528fafe"}, + {file = "pydantic-1.10.18-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5389eb3b48a72da28c6e061a247ab224381435256eb541e175798483368fdd3"}, + {file = "pydantic-1.10.18-cp38-cp38-win_amd64.whl", hash = "sha256:069b9c9fc645474d5ea3653788b544a9e0ccd3dca3ad8c900c4c6eac844b4620"}, + {file = "pydantic-1.10.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:80b982d42515632eb51f60fa1d217dfe0729f008e81a82d1544cc392e0a50ddf"}, + {file = "pydantic-1.10.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:aad8771ec8dbf9139b01b56f66386537c6fe4e76c8f7a47c10261b69ad25c2c9"}, + {file = "pydantic-1.10.18-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941a2eb0a1509bd7f31e355912eb33b698eb0051730b2eaf9e70e2e1589cae1d"}, + {file = "pydantic-1.10.18-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65f7361a09b07915a98efd17fdec23103307a54db2000bb92095457ca758d485"}, + {file = "pydantic-1.10.18-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6951f3f47cb5ca4da536ab161ac0163cab31417d20c54c6de5ddcab8bc813c3f"}, + {file = "pydantic-1.10.18-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7a4c5eec138a9b52c67f664c7d51d4c7234c5ad65dd8aacd919fb47445a62c86"}, + {file = "pydantic-1.10.18-cp39-cp39-win_amd64.whl", hash = "sha256:49e26c51ca854286bffc22b69787a8d4063a62bf7d83dc21d44d2ff426108518"}, + {file = "pydantic-1.10.18-py3-none-any.whl", hash = "sha256:06a189b81ffc52746ec9c8c007f16e5167c8b0a696e1a726369327e3db7b2a82"}, + {file = "pydantic-1.10.18.tar.gz", hash = "sha256:baebdff1907d1d96a139c25136a9bb7d17e118f133a76a2ef3b845e831e3403a"}, ] [package.dependencies] @@ -894,13 +929,13 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pytest" -version = "8.3.1" +version = "8.3.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.3.1-py3-none-any.whl", hash = "sha256:e9600ccf4f563976e2c99fa02c7624ab938296551f280835ee6516df8bc4ae8c"}, - {file = "pytest-8.3.1.tar.gz", hash = "sha256:7e8e5c5abd6e93cb1cc151f23e57adc31fcf8cfd2a3ff2da63e23f732de35db6"}, + {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, + {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, ] [package.dependencies] @@ -949,62 +984,64 @@ dev = ["pre-commit", "pytest-asyncio", "tox"] [[package]] name = "pyyaml" -version = "6.0.1" +version = "6.0.2" description = "YAML parser and emitter for Python" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, - {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, - {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, - {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, - {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, - {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, - {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, - {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, - {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] [[package]] @@ -1030,13 +1067,13 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rich" -version = "13.7.1" +version = "13.8.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.7.0" files = [ - {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, - {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, + {file = "rich-13.8.0-py3-none-any.whl", hash = "sha256:2e85306a063b9492dffc86278197a60cbece75bcb766022f3436f567cae11bdc"}, + {file = "rich-13.8.0.tar.gz", hash = "sha256:a5ac1f1cd448ade0d59cc3356f7db7a7ccda2c8cbae9c7a90c28ff463d3e91f4"}, ] [package.dependencies] @@ -1125,28 +1162,29 @@ files = [ [[package]] name = "ruff" -version = "0.4.10" +version = "0.6.3" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.4.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5c2c4d0859305ac5a16310eec40e4e9a9dec5dcdfbe92697acd99624e8638dac"}, - {file = "ruff-0.4.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a79489607d1495685cdd911a323a35871abfb7a95d4f98fc6f85e799227ac46e"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1dd1681dfa90a41b8376a61af05cc4dc5ff32c8f14f5fe20dba9ff5deb80cd6"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c75c53bb79d71310dc79fb69eb4902fba804a81f374bc86a9b117a8d077a1784"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18238c80ee3d9100d3535d8eb15a59c4a0753b45cc55f8bf38f38d6a597b9739"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d8f71885bce242da344989cae08e263de29752f094233f932d4f5cfb4ef36a81"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:330421543bd3222cdfec481e8ff3460e8702ed1e58b494cf9d9e4bf90db52b9d"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e9b6fb3a37b772628415b00c4fc892f97954275394ed611056a4b8a2631365e"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f54c481b39a762d48f64d97351048e842861c6662d63ec599f67d515cb417f6"}, - {file = "ruff-0.4.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:67fe086b433b965c22de0b4259ddfe6fa541c95bf418499bedb9ad5fb8d1c631"}, - {file = "ruff-0.4.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:acfaaab59543382085f9eb51f8e87bac26bf96b164839955f244d07125a982ef"}, - {file = "ruff-0.4.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3cea07079962b2941244191569cf3a05541477286f5cafea638cd3aa94b56815"}, - {file = "ruff-0.4.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:338a64ef0748f8c3a80d7f05785930f7965d71ca260904a9321d13be24b79695"}, - {file = "ruff-0.4.10-py3-none-win32.whl", hash = "sha256:ffe3cd2f89cb54561c62e5fa20e8f182c0a444934bf430515a4b422f1ab7b7ca"}, - {file = "ruff-0.4.10-py3-none-win_amd64.whl", hash = "sha256:67f67cef43c55ffc8cc59e8e0b97e9e60b4837c8f21e8ab5ffd5d66e196e25f7"}, - {file = "ruff-0.4.10-py3-none-win_arm64.whl", hash = "sha256:dd1fcee327c20addac7916ca4e2653fbbf2e8388d8a6477ce5b4e986b68ae6c0"}, - {file = "ruff-0.4.10.tar.gz", hash = "sha256:3aa4f2bc388a30d346c56524f7cacca85945ba124945fe489952aadb6b5cd804"}, + {file = "ruff-0.6.3-py3-none-linux_armv6l.whl", hash = "sha256:97f58fda4e309382ad30ede7f30e2791d70dd29ea17f41970119f55bdb7a45c3"}, + {file = "ruff-0.6.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3b061e49b5cf3a297b4d1c27ac5587954ccb4ff601160d3d6b2f70b1622194dc"}, + {file = "ruff-0.6.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:34e2824a13bb8c668c71c1760a6ac7d795ccbd8d38ff4a0d8471fdb15de910b1"}, + {file = "ruff-0.6.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bddfbb8d63c460f4b4128b6a506e7052bad4d6f3ff607ebbb41b0aa19c2770d1"}, + {file = "ruff-0.6.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ced3eeb44df75353e08ab3b6a9e113b5f3f996bea48d4f7c027bc528ba87b672"}, + {file = "ruff-0.6.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47021dff5445d549be954eb275156dfd7c37222acc1e8014311badcb9b4ec8c1"}, + {file = "ruff-0.6.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7d7bd20dc07cebd68cc8bc7b3f5ada6d637f42d947c85264f94b0d1cd9d87384"}, + {file = "ruff-0.6.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:500f166d03fc6d0e61c8e40a3ff853fa8a43d938f5d14c183c612df1b0d6c58a"}, + {file = "ruff-0.6.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:42844ff678f9b976366b262fa2d1d1a3fe76f6e145bd92c84e27d172e3c34500"}, + {file = "ruff-0.6.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70452a10eb2d66549de8e75f89ae82462159855e983ddff91bc0bce6511d0470"}, + {file = "ruff-0.6.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:65a533235ed55f767d1fc62193a21cbf9e3329cf26d427b800fdeacfb77d296f"}, + {file = "ruff-0.6.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d2e2c23cef30dc3cbe9cc5d04f2899e7f5e478c40d2e0a633513ad081f7361b5"}, + {file = "ruff-0.6.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d8a136aa7d228975a6aee3dd8bea9b28e2b43e9444aa678fb62aeb1956ff2351"}, + {file = "ruff-0.6.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f92fe93bc72e262b7b3f2bba9879897e2d58a989b4714ba6a5a7273e842ad2f8"}, + {file = "ruff-0.6.3-py3-none-win32.whl", hash = "sha256:7a62d3b5b0d7f9143d94893f8ba43aa5a5c51a0ffc4a401aa97a81ed76930521"}, + {file = "ruff-0.6.3-py3-none-win_amd64.whl", hash = "sha256:746af39356fee2b89aada06c7376e1aa274a23493d7016059c3a72e3b296befb"}, + {file = "ruff-0.6.3-py3-none-win_arm64.whl", hash = "sha256:14a9528a8b70ccc7a847637c29e56fd1f9183a9db743bbc5b8e0c4ad60592a82"}, + {file = "ruff-0.6.3.tar.gz", hash = "sha256:183b99e9edd1ef63be34a3b51fee0a9f4ab95add123dbf89a71f7b1f0c991983"}, ] [[package]] @@ -1184,13 +1222,13 @@ spdx = ["spdx-tools (>=0.8.2)"] [[package]] name = "safety-schemas" -version = "0.0.2" +version = "0.0.5" description = "Schemas for Safety tools" optional = false python-versions = ">=3.7" files = [ - {file = "safety_schemas-0.0.2-py3-none-any.whl", hash = "sha256:277c077ce6e53221874a87c29515ffdd2f3773a6db4d035a9f67cc98db3b8c7f"}, - {file = "safety_schemas-0.0.2.tar.gz", hash = "sha256:7d1b040ec06480f05cff6b45ea7a93e09c8942df864fb0d01ddeb67c323cfa8c"}, + {file = "safety_schemas-0.0.5-py3-none-any.whl", hash = "sha256:6ac9eb71e60f0d4e944597c01dd48d6d8cd3d467c94da4aba3702a05a3a6ab4f"}, + {file = "safety_schemas-0.0.5.tar.gz", hash = "sha256:0de5fc9a53d4423644a8ce9a17a2e474714aa27e57f3506146e95a41710ff104"}, ] [package.dependencies] @@ -1202,19 +1240,23 @@ typing-extensions = ">=4.7.1" [[package]] name = "setuptools" -version = "71.1.0" +version = "74.0.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-71.1.0-py3-none-any.whl", hash = "sha256:33874fdc59b3188304b2e7c80d9029097ea31627180896fb549c578ceb8a0855"}, - {file = "setuptools-71.1.0.tar.gz", hash = "sha256:032d42ee9fb536e33087fb66cac5f840eb9391ed05637b3f2a76a7c8fb477936"}, + {file = "setuptools-74.0.0-py3-none-any.whl", hash = "sha256:0274581a0037b638b9fc1c6883cc71c0210865aaa76073f7882376b641b84e8f"}, + {file = "setuptools-74.0.0.tar.gz", hash = "sha256:a85e96b8be2b906f3e3e789adec6a9323abf79758ecfa3065bd740d81158b11e"}, ] [package.extras] -core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "ordered-set (>=3.1.1)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.11.*)", "pytest-mypy"] [[package]] name = "shellingham" @@ -1240,13 +1282,13 @@ files = [ [[package]] name = "soupsieve" -version = "2.5" +version = "2.6" description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.8" files = [ - {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, - {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, + {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, + {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, ] [[package]] @@ -1285,13 +1327,13 @@ test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools [[package]] name = "sphinx-autodoc-typehints" -version = "2.2.3" +version = "2.3.0" description = "Type hints (PEP 484) support for the Sphinx autodoc extension" optional = false python-versions = ">=3.9" files = [ - {file = "sphinx_autodoc_typehints-2.2.3-py3-none-any.whl", hash = "sha256:b7058e8c5831e5598afca1a78fda0695d3291388d954464a6e480c36198680c0"}, - {file = "sphinx_autodoc_typehints-2.2.3.tar.gz", hash = "sha256:fde3d888949bd0a91207cf1e54afda58121dbb4bf1f183d0cc78a0826654c974"}, + {file = "sphinx_autodoc_typehints-2.3.0-py3-none-any.whl", hash = "sha256:3098e2c6d0ba99eacd013eb06861acc9b51c6e595be86ab05c08ee5506ac0c67"}, + {file = "sphinx_autodoc_typehints-2.3.0.tar.gz", hash = "sha256:535c78ed2d6a1bad393ba9f3dfa2602cf424e2631ee207263e07874c38fde084"}, ] [package.dependencies] @@ -1321,49 +1363,49 @@ docs = ["furo", "ipython", "myst-parser", "sphinx-copybutton", "sphinx-inline-ta [[package]] name = "sphinxcontrib-applehelp" -version = "1.0.8" +version = "2.0.0" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" optional = false python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_applehelp-1.0.8-py3-none-any.whl", hash = "sha256:cb61eb0ec1b61f349e5cc36b2028e9e7ca765be05e49641c97241274753067b4"}, - {file = "sphinxcontrib_applehelp-1.0.8.tar.gz", hash = "sha256:c40a4f96f3776c4393d933412053962fac2b84f4c99a7982ba42e09576a70619"}, + {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, + {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, ] [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "sphinxcontrib-devhelp" -version = "1.0.6" +version = "2.0.0" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" optional = false python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_devhelp-1.0.6-py3-none-any.whl", hash = "sha256:6485d09629944511c893fa11355bda18b742b83a2b181f9a009f7e500595c90f"}, - {file = "sphinxcontrib_devhelp-1.0.6.tar.gz", hash = "sha256:9893fd3f90506bc4b97bdb977ceb8fbd823989f4316b28c3841ec128544372d3"}, + {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, + {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, ] [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "sphinxcontrib-htmlhelp" -version = "2.0.6" +version = "2.1.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = false python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_htmlhelp-2.0.6-py3-none-any.whl", hash = "sha256:1b9af5a2671a61410a868fce050cab7ca393c218e6205cbc7f590136f207395c"}, - {file = "sphinxcontrib_htmlhelp-2.0.6.tar.gz", hash = "sha256:c6597da06185f0e3b4dc952777a04200611ef563882e0c244d27a15ee22afa73"}, + {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, + {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, ] [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["html5lib", "pytest"] @@ -1383,59 +1425,59 @@ test = ["flake8", "mypy", "pytest"] [[package]] name = "sphinxcontrib-qthelp" -version = "1.0.8" +version = "2.0.0" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" optional = false python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_qthelp-1.0.8-py3-none-any.whl", hash = "sha256:323d6acc4189af76dfe94edd2a27d458902319b60fcca2aeef3b2180c106a75f"}, - {file = "sphinxcontrib_qthelp-1.0.8.tar.gz", hash = "sha256:db3f8fa10789c7a8e76d173c23364bdf0ebcd9449969a9e6a3dd31b8b7469f03"}, + {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, + {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, ] [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["defusedxml (>=0.7.1)", "pytest"] [[package]] name = "sphinxcontrib-serializinghtml" -version = "1.1.10" +version = "2.0.0" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" optional = false python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_serializinghtml-1.1.10-py3-none-any.whl", hash = "sha256:326369b8df80a7d2d8d7f99aa5ac577f51ea51556ed974e7716cfd4fca3f6cb7"}, - {file = "sphinxcontrib_serializinghtml-1.1.10.tar.gz", hash = "sha256:93f3f5dc458b91b192fe10c397e324f262cf163d79f3282c158e8436a2c4511f"}, + {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, + {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, ] [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "stevedore" -version = "5.2.0" +version = "5.3.0" description = "Manage dynamic plugins for Python applications" optional = false python-versions = ">=3.8" files = [ - {file = "stevedore-5.2.0-py3-none-any.whl", hash = "sha256:1c15d95766ca0569cad14cb6272d4d31dae66b011a929d7c18219c176ea1b5c9"}, - {file = "stevedore-5.2.0.tar.gz", hash = "sha256:46b93ca40e1114cea93d738a6c1e365396981bb6bb78c27045b7587c9473544d"}, + {file = "stevedore-5.3.0-py3-none-any.whl", hash = "sha256:1efd34ca08f474dad08d9b19e934a22c68bb6fe416926479ba29e5013bcc8f78"}, + {file = "stevedore-5.3.0.tar.gz", hash = "sha256:9a64265f4060312828151c204efbe9b7a9852a0d9228756344dbc7e4023e375a"}, ] [package.dependencies] -pbr = ">=2.0.0,<2.1.0 || >2.1.0" +pbr = ">=2.0.0" [[package]] name = "typer" -version = "0.12.3" +version = "0.12.5" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." optional = false python-versions = ">=3.7" files = [ - {file = "typer-0.12.3-py3-none-any.whl", hash = "sha256:070d7ca53f785acbccba8e7d28b08dcd88f79f1fbda035ade0aecec71ca5c914"}, - {file = "typer-0.12.3.tar.gz", hash = "sha256:49e73131481d804288ef62598d97a1ceef3058905aa536a1134f90891ba35482"}, + {file = "typer-0.12.5-py3-none-any.whl", hash = "sha256:62fe4e471711b147e3365034133904df3e235698399bc4de2b36c8579298d52b"}, + {file = "typer-0.12.5.tar.gz", hash = "sha256:f592f089bedcc8ec1b974125d64851029c3b1af145f04aca64d69410f0c9b722"}, ] [package.dependencies] @@ -1457,21 +1499,21 @@ files = [ [[package]] name = "typos" -version = "1.23.2" +version = "1.24.1" description = "Source Code Spelling Correction" optional = false python-versions = ">=3.7" files = [ - {file = "typos-1.23.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b1549225f26cbcb6640e87999f20496287751428c71a7650e6afe3143e39112f"}, - {file = "typos-1.23.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:290c6628b60f570999dfcdcf0ce5c90f195cceba160a1b16316eebd5c68129f2"}, - {file = "typos-1.23.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0461d8e33c9ba51518203ef59df19b0945293626ce620652650927f3e3b1a9af"}, - {file = "typos-1.23.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2873d83a562725c841e6ee3ee03865ebcee7a1b5add57ca7a88578cb752c7f4a"}, - {file = "typos-1.23.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16ea2a308f731729711b660e0c68753047e6b9937d8d97bd6e1d1c274303ad36"}, - {file = "typos-1.23.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2dcd035661c1a45688fd0ffb14fd242bf8907996e12a8cbf8166bec0a6b360e5"}, - {file = "typos-1.23.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:38da8651809e86de5cd338a68552688ab10fb45056d86c1f90a36f7911d29bd4"}, - {file = "typos-1.23.2-py3-none-win32.whl", hash = "sha256:de20d68507126f2577c7dca88ec8d52364e8c519218d72791edcfe256622948a"}, - {file = "typos-1.23.2-py3-none-win_amd64.whl", hash = "sha256:85f0877de4c4024fc846a08ece5a8f56dd85ab69d42d23608c29783e1d6899ed"}, - {file = "typos-1.23.2.tar.gz", hash = "sha256:2a7b0c3523140f1c32ed91e46171a925d3748735648381cec4f6b992217d4167"}, + {file = "typos-1.24.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:927336ba9fa62926606226aa3c7b42f20e146988d183d90f90e67ce1dfe9b4b7"}, + {file = "typos-1.24.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:acc675f1fec90e24a17a2627fa6d742bc56d05e3dcca002691b4ed6eab3113a6"}, + {file = "typos-1.24.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d1852d248b56c17a15461ae8b50a38cdb960e180542e429a0ce33dbe1132e27"}, + {file = "typos-1.24.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42412b7107161743b0ebb9e871ce53f89197e2a4727fb7ba0851db7651919093"}, + {file = "typos-1.24.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0a55d3f03d9f99a076a6aec3f43bff6d9100529ccb2cd76e69b455fd58b7191"}, + {file = "typos-1.24.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e4acf04c1b92c3083eb0f3025e5c4bf19d9ff84ddd3ef60aafa5079e2a2e017e"}, + {file = "typos-1.24.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:112163011f7360448b7514e682e2ae978eba886c26e269725de02abf3faffc63"}, + {file = "typos-1.24.1-py3-none-win32.whl", hash = "sha256:ccc0026863eaaa62686a0ad259c39c51a908281a4e76a3f5200813f372f45db1"}, + {file = "typos-1.24.1-py3-none-win_amd64.whl", hash = "sha256:f73136cccfd732927b75f6d31dc4408fd21944a9bee18977da056babbd06ceee"}, + {file = "typos-1.24.1.tar.gz", hash = "sha256:16f81284e185f1ef65ff6d7bc1b187b05b77a55382c73186d289495f3a00f627"}, ] [[package]] @@ -1514,4 +1556,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "9f7932bdeb9776273203e9e28772b2253eb5587dc9a85f4be3895ecddc8ec1c9" +content-hash = "67d34bcf2eac9690bbfdca04d4a80cd6eadeb60255aeee892e99f8ac3d8cf9e6" diff --git a/pyproject.toml b/pyproject.toml index 639b47a..ad9ec03 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ pytest-mock = "^3.14.0" pre-commit = "^2.13.0" [tool.poetry.group.lint.dependencies] -ruff = "^0.4.4" +ruff = "^0.6.3" typos = "^1.21.0" [tool.poetry.group.docs.dependencies] @@ -52,6 +52,10 @@ mypy = "^1.10.1" + +[tool.poetry.group.dev.dependencies] +ruff = "^0.6.3" + [tool.poetry.scripts] crategen = "crategen.cli:cli" diff --git a/tests/unit/test_wrroc_models.py b/tests/unit/test_wrroc_models.py index 1e9538e..bedf6a2 100644 --- a/tests/unit/test_wrroc_models.py +++ b/tests/unit/test_wrroc_models.py @@ -171,17 +171,17 @@ def test_validate_wrroc_tes(self): Test that validate_wrroc_tes correctly validates a WRROC entity for TES conversion. """ data = { - "id": "process-id", + "@id": "process-id", "name": "Test Process", "object": [ { - "id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/README.md", + "@id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/README.md", "name": "Input 1", } ], "result": [ { - "id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/LICENSE", + "@id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/LICENSE", "name": "Output 1", } ], @@ -195,12 +195,12 @@ def test_validate_wrroc_tes_empty_object_list(self): Test that validate_wrroc_tes correctly validates a WRROC entity with an empty object list for TES conversion. """ data = { - "id": "process-id", + "@id": "process-id", "name": "Test Process", "object": [], "result": [ { - "id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/LICENSE", + "@id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/LICENSE", "name": "Output 1", } ], @@ -221,20 +221,20 @@ def test_validate_wrroc_wes(self): Test that validate_wrroc_wes correctly validates a WRROC entity for WES conversion. """ data = { - "id": "workflow-id", + "@id": "workflow-id", "name": "Test Workflow", "workflowType": "CWL", "workflowVersion": "v1.0", "status": "completed", "object": [ { - "id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/README.md", + "@id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/README.md", "name": "Input 1", } ], "result": [ { - "id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/LICENSE", + "@id": "https://raw.githubusercontent.com/elixir-cloud-aai/CrateGen/main/LICENSE", "name": "Output 1", } ], From 0efb2162a1b31d0fb3f20b1039fb4a9c74efb26c Mon Sep 17 00:00:00 2001 From: karanjot786 Date: Fri, 30 Aug 2024 21:45:40 +0530 Subject: [PATCH 08/10] ci(workflow): update actions/checkout to v4 in CI workflow --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 52a8779..5823476 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 From 71b1143adf1443ed567dcba54c0cdaedec50613a Mon Sep 17 00:00:00 2001 From: Salihu <91833785+SalihuDickson@users.noreply.github.com> Date: Tue, 10 Sep 2024 23:59:25 +0100 Subject: [PATCH 09/10] Chore/refactor wes models (#26) Co-authored-by: salihuDickson --- crategen/converters/wes_converter.py | 20 +-- crategen/models.py | 0 crategen/models/tes_models.py | 12 +- crategen/models/wes_models.py | 175 +++++++++++++++++++-------- 4 files changed, 142 insertions(+), 65 deletions(-) delete mode 100644 crategen/models.py diff --git a/crategen/converters/wes_converter.py b/crategen/converters/wes_converter.py index 48bbb65..4832dd8 100644 --- a/crategen/converters/wes_converter.py +++ b/crategen/converters/wes_converter.py @@ -1,9 +1,8 @@ from pydantic import ValidationError -from ..models.wes_models import WESData, WESOutputs, WESRequest, WESRunLog +from ..models.wes_models import Log, RunRequest, WESData, WESOutputs from ..models.wrroc_models import WRROCDataWES from ..utils import convert_to_iso8601 -from ..validators import validate_wrroc_wes from .abstract_converter import AbstractConverter @@ -27,6 +26,7 @@ def convert_to_wrroc(self, data: dict) -> dict: except ValidationError as e: raise ValueError(f"Invalid WES data: {e.errors()}") from e + # create the object using the model wrroc_data = { "@id": data_wes.run_id, "name": data_wes.run_log.name, @@ -39,8 +39,6 @@ def convert_to_wrroc(self, data: dict) -> dict: ], } - # Validate WRROC data before returning - validate_wrroc_wes(wrroc_data) return wrroc_data def convert_from_wrroc(self, data: dict) -> dict: @@ -64,16 +62,18 @@ def convert_from_wrroc(self, data: dict) -> dict: f"Invalid WRROC data for WES conversion: {e.errors()}" ) from e - wes_outputs = [WESOutputs(location=res.id, name=res.name) for res in data_wrroc.result] - wes_run_log = WESRunLog( + wes_outputs = [ + WESOutputs(location=res.id, name=res.name) for res in data_wrroc.result + ] + wes_run_log = Log( name=data_wrroc.name, start_time=data_wrroc.startTime, - end_time=data_wrroc.endTime + end_time=data_wrroc.endTime, ) - wes_request = WESRequest( + wes_request = RunRequest( workflow_params={}, # Adjust as necessary workflow_type="CWL", # Example type, adjust as necessary - workflow_type_version="v1.0" # Example version, adjust as necessary + workflow_type_version="v1.0", # Example version, adjust as necessary ) wes_data = WESData( @@ -82,7 +82,7 @@ def convert_from_wrroc(self, data: dict) -> dict: state=data_wrroc.status, run_log=wes_run_log, task_logs=None, # Provide appropriate value - outputs=wes_outputs + outputs=wes_outputs, ) # Validate WES data before returning diff --git a/crategen/models.py b/crategen/models.py deleted file mode 100644 index e69de29..0000000 diff --git a/crategen/models/tes_models.py b/crategen/models/tes_models.py index 407a9c0..ad80560 100644 --- a/crategen/models/tes_models.py +++ b/crategen/models/tes_models.py @@ -1,3 +1,7 @@ +""" +Each model in this module conforms to the corresponding TES model names as specified by the GA4GH schema (https://ga4gh.github.io/task-execution-schemas/docs/). +""" + import os from datetime import datetime from enum import Enum @@ -31,9 +35,9 @@ class TESOutputFileLog(BaseModel): **Attributes:** - - **url** (str): URL of the file in storage. - - **path** (str): Path of the file inside the container. Must be an absolute path. - - **size_bytes** (str): Size of the file in bytes. Note, this is currently coded as a string because official JSON doesn't support int64 numbers. + - **url** (`str`): URL of the file in storage. + - **path** (`str`): Path of the file inside the container. Must be an absolute path. + - **size_bytes** (`str`): Size of the file in bytes. Note, this is currently coded as a string because official JSON doesn't support int64 numbers. **Reference:** https://ga4gh.github.io/task-execution-schemas/docs/#operation/GetTask """ @@ -94,7 +98,7 @@ class TESExecutor(BaseModel): env: Optional[dict[str, str]] = None @validator("stdin", "stdout") - def validate_stdin_stdin(cls, value): + def validate_stdin_stdin(value): if not os.path.isabs(value): raise ValueError(f"The '${value}' attribute must contain an absolute path.") return value diff --git a/crategen/models/wes_models.py b/crategen/models/wes_models.py index e6e37b7..7c661b8 100644 --- a/crategen/models/wes_models.py +++ b/crategen/models/wes_models.py @@ -1,87 +1,160 @@ +""" +Each model in this module conforms to the corresponding WES model names as specified by the GA4GH schema (https://ga4gh.github.io/workflow-execution-service-schemas/docs/). +""" + +from datetime import datetime +from enum import Enum from typing import Optional -from pydantic import BaseModel, Field, root_validator +from pydantic import BaseModel, Field, root_validator, validator + +from ..utils import convert_to_rfc3339_format + +class State(str, Enum): + UNKNOWN = "UNKNOWN" + QUEUED = "QUEUED" + INITIALIZING = "INITIALIZING" + RUNNING = "RUNNING" + PAUSED = "PAUSED" + COMPLETE = "COMPLETE" + EXECUTOR_ERROR = "EXECUTOR_ERROR" + SYSTEM_ERROR = "SYSTEM_ERROR" + CANCELLED = "CANCELLED" + CANCELING = "CANCELING" + PREEMPTED = "PREEMPTED" -class WESRunLog(BaseModel): + +class WESOutputs(BaseModel): + location: str + name: str + + +class Log(BaseModel): """ Represents a run log in the Workflow Execution Service (WES). - Attributes: - name (Optional[str]): The name of the run. - start_time (Optional[str]): The start time of the run. - end_time (Optional[str]): The end time of the run. - cmd (Optional[list[str]]): The command executed in the run. - stdout (Optional[str]): The path to the stdout log. - stderr (Optional[str]): The path to the stderr log. - exit_code (Optional[int]): The exit code of the run. - tes_logs_url (Optional[str]): The URL of the TES logs. + **Attributes:** + + - **name** (`Optional[str]`): The task or workflow name. + - **cmd** (`Optional[list[str]]`): The command line that was executed. + - **start_time** (`Optional[str]`): When the command started executing, in ISO 8601 format. + - **end_time** (`Optional[str]`): When the command stopped executing, in ISO 8601 format. + - **stdout** (`Optional[str]`): A URL to retrieve standard output logs of the workflow run or task.. + - **stderr** (`Optional[str]`): A URL to retrieve standard error logs of the workflow run or task. + - **exit_code** (`Optional[int]`): The exit code of the program. + - **system_logs** (`optional[list[str]]`): Any logs the system decides are relevant, which are not tied directly to a workflow. + + **Reference:** https://ga4gh.github.io/workflow-execution-service-schemas/docs/#tag/runlog_model """ - name: Optional[str] = None - start_time: Optional[str] = None - end_time: Optional[str] = None - cmd: Optional[list[str]] = None - stdout: Optional[str] = None - stderr: Optional[str] = None - exit_code: Optional[int] = None - tes_logs_url: Optional[str] = None + name: Optional[str] + start_time: Optional[datetime] + end_time: Optional[datetime] + cmd: Optional[list[str]] + stdout: Optional[str] + stderr: Optional[str] + exit_code: Optional[int] + system_logs: Optional[list[str]] + @validator("start_time", "end_time") + def validate_datetime(value): + return convert_to_rfc3339_format(value) -class WESOutputs(BaseModel): - """ - Represents output files in WES. - Attributes: - location (str): The URL of the output file. - name (str): The name of the output file. +class TaskLog(Log): + """ + Represents a task log in the Workflow Execution Service (WES). + + **Attributes:** + + - **name** (`str`): The task or workflow name. + - **cmd** (`Optional[list[str]]`): The command line that was executed. + - **start_time** (`Optional[str]`): When the command started executing, in ISO 8601 format. + - **end_time** (`Optional[str]`): When the command stopped executing, in ISO 8601 format. + - **stdout** (`Optional[str]`): A URL to retrieve standard output logs of the workflow run or task.. + - **stderr** (`Optional[str]`): A URL to retrieve standard error logs of the workflow run or task. + - **exit_code** (`Optional[int]`): The exit code of the program. + - **system_logs** (`Optional[list[str]]`): Any logs the system decides are relevant, which are not tied directly to a workflow. + - **id** (`str`): A unique identifier which maybe used to reference the task + - **tes_uri** (`Optional[str]`): An optional URL pointing to an extended task definition defined by a TES api + + **Reference:** https://ga4gh.github.io/workflow-execution-service-schemas/docs/#tag/runlog_model """ - location: str - name: str + id: str + tes_uri: Optional[str] + name: str = Field( + ... + ) # test if adding Field makes a diff, gemini says no on specific questioning. -class WESRequest(BaseModel): +class RunRequest(BaseModel): """ Represents a workflow request in WES. - Attributes: - workflow_params (dict[str, str]): The parameters for the workflow. - workflow_type (str): The type of the workflow (e.g., CWL). - workflow_type_version (str): The version of the workflow type. - tags (Optional[dict[str, str]]): Additional tags associated with the workflow. + **Attributes:** + + - **workflow_params** (`Optional[dict[str, str]]`): The workflow run parameterizations(JSON encoded), including input and output file locations. + - **workflow_type** (`str`): The workflow descriptor type. + - **workflow_type_version** (`str`): The workflow descriptor type version. + - **tags** (`Optional[dict[str, str]]`): Additional tags associated with the workflow. + - **workflow_engine_parameters** (Optional[dict[str, str]]): Input values specific to the workflow engine. + - **workflow_engine** (`Optional[str]`): The workflow engine. + - **workflow_engine_version (`Optional[str]`): The workflow engine version. + - **workflow_url** (`str`): The workflow url + + **Reference:** https://ga4gh.github.io/workflow-execution-service-schemas/docs/#tag/runlog_model """ workflow_params: dict[str, str] workflow_type: str workflow_type_version: str - tags: Optional[dict[str, str]] = None + tags: Optional[dict[str, str]] = {} + workflow_engine_parameters: Optional[dict[str, str]] + workflow_engine: Optional[str] + workflow_engine_version: Optional[str] + workflow_url: str + + @root_validator() + def validate_workflow_engine(cls, values): + """ + - If workflow_engine_version is set the workflow_engine must be set. + """ + engine_version = values.get("workflow_engine_version") + engine = values.get("workflow_engine") + + if engine_version is not None and engine is None: + raise ValueError( + "The 'workflow_engine' attribute is required when the 'workflow_engine_verision' attribute is set" + ) + return values class WESData(BaseModel): """ Represents a WES run. - Attributes: - run_id (str): The unique identifier for the WES run. - request (WESRequest): The request associated with the WES run. - state (str): The state of the WES run. - run_log (WESRunLog): The log of the WES run. - task_logs (Optional[list[WESRunLog]]): The logs of individual tasks within the run. - outputs (list[WESOutputs]): The outputs of the WES run. + **Attributes:** + + - **run_id** (`str`): The unique identifier for the WES run. + - **request** (`Optional[RunRequest]`): The request associated with the WES run. + - **state** (`Optional[State]`): The state of the WES run. + - **run_log** (`Object`): The log of the WES run. + - **task_logs_url** (`Optional[str]`): A reference to the complete url which may be used to obtain a paginated list of task logs for this workflow. + - **task_logs** (`Optional[list[Log | RunLog] | None]`): The logs of individual tasks within the run. This attribute is deprecated. + - **outputs** (`dict[str, str]`): The outputs of the WES run. + + **Reference:** https://ga4gh.github.io/workflow-execution-service-schemas/docs/#tag/runlog_model """ run_id: str - request: WESRequest - state: str - run_log: WESRunLog - task_logs: Optional[list[WESRunLog]] = Field( - None, description="This field is deprecated. Use tes_logs_url instead." - ) - outputs: list[WESOutputs] - - class Config: - extra = "allow" + request: Optional[RunRequest] + state: Optional[State] + run_log: Optional[Log] + task_logs_url: Optional[str] + task_logs: Optional[list[Log | TaskLog] | None] + outputs: dict[str, str] @root_validator def check_deprecated_fields(cls, values): From 9f5b8876123a38157a31d1d81b38f295a491cac8 Mon Sep 17 00:00:00 2001 From: Salihu <91833785+SalihuDickson@users.noreply.github.com> Date: Thu, 12 Sep 2024 12:06:22 +0100 Subject: [PATCH 10/10] Chore/refactor wes models (#28) Co-authored-by: salihuDickson --- .github/workflows/ci.yml | 1 - crategen/converters/tes_converter.py | 24 +++++++------ crategen/converters/wes_converter.py | 46 +++++++++++++------------ crategen/models/wes_models.py | 50 +++++++++++++++++----------- crategen/models/wrroc_models.py | 25 ++++++++------ lefthook.yml | 4 +++ pyproject.toml | 6 +++- 7 files changed, 92 insertions(+), 64 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5823476..062a545 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,6 @@ jobs: - name: Lint with Ruff run: | poetry run ruff check crategen/ - if: ${{ success() }} - name: Type check with Mypy run: | diff --git a/crategen/converters/tes_converter.py b/crategen/converters/tes_converter.py index e42d950..2b5f5ec 100644 --- a/crategen/converters/tes_converter.py +++ b/crategen/converters/tes_converter.py @@ -1,4 +1,3 @@ -from datetime import datetime from pydantic import AnyUrl, ValidationError @@ -52,7 +51,7 @@ def convert_to_wrroc(self, data: dict) -> dict: "startTime": data_tes.creation_time, "endTime": end_time, } - + validate_wrroc_tes(wrroc_data) return wrroc_data @@ -76,23 +75,28 @@ def convert_from_wrroc(self, data: dict) -> dict: raise ValueError(f"Invalid WRROC data: {e.errors()}") from e # Convert URL strings to AnyUrl - tes_inputs = [TESInput(url=AnyUrl(url=obj.id), path=obj.name) for obj in data_wrroc.object] - tes_outputs = [TESOutput(url=AnyUrl(url=res.id), path=res.name) for res in data_wrroc.result] + tes_inputs = [ + TESInput(url=AnyUrl(url=obj.id), path=obj.name) for obj in data_wrroc.object + ] + tes_outputs = [ + TESOutput(url=AnyUrl(url=data_wrroc.result.id), path=data_wrroc.result.name) + ] # Ensure 'image' and 'command' fields are provided - tes_executors = [TESExecutor(image=data_wrroc.instrument or "", command=[])] # Provide default empty list for command + tes_executors = [ + TESExecutor(image=data_wrroc.instrument or "", command=[]) + ] # Provide default empty list for command # Ensure correct type for end_time (datetime) - end_time = datetime.fromisoformat(data_wrroc.endTime) if data_wrroc.endTime else None tes_logs = [ TESTaskLog( logs=[], metadata=None, start_time=None, - end_time=end_time, + end_time=data_wrroc.endTime, outputs=[], - system_logs=None + system_logs=None, ) ] @@ -103,9 +107,9 @@ def convert_from_wrroc(self, data: dict) -> dict: executors=tes_executors, inputs=tes_inputs, outputs=tes_outputs, - creation_time=None, + creation_time=None, logs=tes_logs, - state=TESState.UNKNOWN + state=TESState.UNKNOWN, ) # Validate TES data before returning diff --git a/crategen/converters/wes_converter.py b/crategen/converters/wes_converter.py index 4832dd8..b1bcd54 100644 --- a/crategen/converters/wes_converter.py +++ b/crategen/converters/wes_converter.py @@ -1,7 +1,7 @@ from pydantic import ValidationError -from ..models.wes_models import Log, RunRequest, WESData, WESOutputs -from ..models.wrroc_models import WRROCDataWES +from ..models.wes_models import Log, RunRequest, State, WESData +from ..models.wrroc_models import WRROCDataWES, WRROCOutputs from ..utils import convert_to_iso8601 from .abstract_converter import AbstractConverter @@ -26,20 +26,24 @@ def convert_to_wrroc(self, data: dict) -> dict: except ValidationError as e: raise ValueError(f"Invalid WES data: {e.errors()}") from e - # create the object using the model - wrroc_data = { - "@id": data_wes.run_id, - "name": data_wes.run_log.name, - "status": data_wes.state, - "startTime": convert_to_iso8601(data_wes.run_log.start_time), - "endTime": convert_to_iso8601(data_wes.run_log.end_time), - "result": [ - {"@id": output.location, "name": output.name} - for output in data_wes.outputs - ], - } - - return wrroc_data + wrroc_output = ( + WRROCOutputs( + id=data_wes.outputs.get("location"), name=data_wes.outputs.get("name") + ) + if data_wes.outputs.get("location") + else None + ) + + wrroc_data = WRROCDataWES( + id=data_wes.run_id, + name=data_wes.run_log.name, + status=data_wes.state, + startTime=convert_to_iso8601(data_wes.run_log.start_time), + endTime=convert_to_iso8601(data_wes.run_log.end_time), + result=wrroc_output, + ) + + return wrroc_data.dict(exclude_none=True) def convert_from_wrroc(self, data: dict) -> dict: """ @@ -62,9 +66,6 @@ def convert_from_wrroc(self, data: dict) -> dict: f"Invalid WRROC data for WES conversion: {e.errors()}" ) from e - wes_outputs = [ - WESOutputs(location=res.id, name=res.name) for res in data_wrroc.result - ] wes_run_log = Log( name=data_wrroc.name, start_time=data_wrroc.startTime, @@ -74,15 +75,18 @@ def convert_from_wrroc(self, data: dict) -> dict: workflow_params={}, # Adjust as necessary workflow_type="CWL", # Example type, adjust as necessary workflow_type_version="v1.0", # Example version, adjust as necessary + workflow_url="", ) + state = State(data_wrroc.status) + wes_data = WESData( run_id=data_wrroc.id, request=wes_request, - state=data_wrroc.status, + state=state, run_log=wes_run_log, task_logs=None, # Provide appropriate value - outputs=wes_outputs, + outputs={"location": data_wrroc.result.id, "name": data_wrroc.result.name}, ) # Validate WES data before returning diff --git a/crategen/models/wes_models.py b/crategen/models/wes_models.py index 7c661b8..cdba35f 100644 --- a/crategen/models/wes_models.py +++ b/crategen/models/wes_models.py @@ -45,17 +45,29 @@ class Log(BaseModel): - **exit_code** (`Optional[int]`): The exit code of the program. - **system_logs** (`optional[list[str]]`): Any logs the system decides are relevant, which are not tied directly to a workflow. + **Reference:** https://ga4gh.github.io/workflow-execution-service-schemas/docs/#tag/runlog_model + **Attributes:** + + - **name** (`Optional[str]`): The task or workflow name. + - **cmd** (`Optional[list[str]]`): The command line that was executed. + - **start_time** (`Optional[str]`): When the command started executing, in ISO 8601 format. + - **end_time** (`Optional[str]`): When the command stopped executing, in ISO 8601 format. + - **stdout** (`Optional[str]`): A URL to retrieve standard output logs of the workflow run or task.. + - **stderr** (`Optional[str]`): A URL to retrieve standard error logs of the workflow run or task. + - **exit_code** (`Optional[int]`): The exit code of the program. + - **system_logs** (`optional[list[str]]`): Any logs the system decides are relevant, which are not tied directly to a workflow. + **Reference:** https://ga4gh.github.io/workflow-execution-service-schemas/docs/#tag/runlog_model """ - name: Optional[str] - start_time: Optional[datetime] - end_time: Optional[datetime] - cmd: Optional[list[str]] - stdout: Optional[str] - stderr: Optional[str] - exit_code: Optional[int] - system_logs: Optional[list[str]] + name: Optional[str] = None + start_time: Optional[datetime] = None + end_time: Optional[datetime] = None + cmd: Optional[list[str]] = None + stdout: Optional[str] = None + stderr: Optional[str] = None + exit_code: Optional[int] = None + system_logs: Optional[list[str]] = None @validator("start_time", "end_time") def validate_datetime(value): @@ -84,9 +96,7 @@ class TaskLog(Log): id: str tes_uri: Optional[str] - name: str = Field( - ... - ) # test if adding Field makes a diff, gemini says no on specific questioning. + name: str = Field(...) class RunRequest(BaseModel): @@ -111,9 +121,9 @@ class RunRequest(BaseModel): workflow_type: str workflow_type_version: str tags: Optional[dict[str, str]] = {} - workflow_engine_parameters: Optional[dict[str, str]] - workflow_engine: Optional[str] - workflow_engine_version: Optional[str] + workflow_engine_parameters: Optional[dict[str, str]] = None + workflow_engine: Optional[str] = None + workflow_engine_version: Optional[str] = None workflow_url: str @root_validator() @@ -149,12 +159,12 @@ class WESData(BaseModel): """ run_id: str - request: Optional[RunRequest] - state: Optional[State] - run_log: Optional[Log] - task_logs_url: Optional[str] - task_logs: Optional[list[Log | TaskLog] | None] - outputs: dict[str, str] + request: Optional[RunRequest] = None + state: Optional[State] = None + run_log: Optional[Log] = None + task_logs_url: Optional[str] = None + task_logs: Optional[list[Log | TaskLog] | None] = None + outputs: dict[str, str] = None @root_validator def check_deprecated_fields(cls, values): diff --git a/crategen/models/wrroc_models.py b/crategen/models/wrroc_models.py index 14bf22a..b07510e 100644 --- a/crategen/models/wrroc_models.py +++ b/crategen/models/wrroc_models.py @@ -1,7 +1,10 @@ -from typing import Optional +from datetime import datetime +from typing import Annotated, Optional from pydantic import AnyUrl, BaseModel, Field +WRROC_ID = Annotated[str, Field(alias="@id")] + class WRROCInputs(BaseModel): """ @@ -12,7 +15,7 @@ class WRROCInputs(BaseModel): name (str): The name of the input. """ - id: str = Field(alias='@id') + id: WRROC_ID name: str @@ -25,7 +28,7 @@ class WRROCOutputs(BaseModel): name (str): The name of the output. """ - id: str = Field(alias='@id') + id: WRROC_ID name: str @@ -39,20 +42,20 @@ class WRROCDataBase(BaseModel): description (Optional[str]): A brief description of the WRROC entity. instrument (Optional[str]): The instrument used in the WRROC entity. object (list[WRROCInputs]): A list of input objects related to the WRROC entity. - result (list[WRROCOutputs]): A list of output results related to the WRROC entity. - startTime (Optional[str]): The start time of the WRROC entity. - endTime (Optional[str]): The end time of the WRROC entity. + result (WRROCOutputs): A list of output results related to the WRROC entity. + startTime (Optional[datetime]): The start time of the WRROC entity. + endTime (Optional[datetime]): The end time of the WRROC entity. version (Optional[str]): The version of the WRROC entity. """ - id: str = Field(alias='@id') + id: WRROC_ID name: str description: Optional[str] = "" instrument: Optional[str] = None object: list[WRROCInputs] = Field(default_factory=list) - result: list[WRROCOutputs] = Field(default_factory=list) - startTime: Optional[str] = None - endTime: Optional[str] = None + result: Optional[WRROCOutputs] = None + startTime: Optional[datetime] = None + endTime: Optional[datetime] = None version: Optional[str] = None class Config: @@ -102,7 +105,7 @@ class WRROCProcess(BaseModel): profiles (Optional[list[AnyUrl]]): URLs to the RO-Crate profiles used. """ - id: str = Field(alias='@id') + id: WRROC_ID name: str description: Optional[str] = "" startTime: Optional[str] = None diff --git a/lefthook.yml b/lefthook.yml index 9d0ec7e..b20da8f 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -4,3 +4,7 @@ pre-push: files: git diff --name-only --diff-filter=d $(git merge-base origin/main HEAD)..HEAD run: poetry run ruff check {files} glob: '*.py' + mypy: + files: git diff --name-only --diff-filter=d $(git merge-base origin/main HEAD)..HEAD + run: poetry run mypy {files} + glob: '*.py' diff --git a/pyproject.toml b/pyproject.toml index ad9ec03..0482d20 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,8 +50,12 @@ pytest-mock = "^3.14.0" [tool.poetry.group.types.dependencies] mypy = "^1.10.1" +[tool.mypy] +strict_optional = false - +[[tool.mypy.overrides]] +module = ['crategen.validators'] +ignore_errors = true [tool.poetry.group.dev.dependencies] ruff = "^0.6.3"