-
Notifications
You must be signed in to change notification settings - Fork 2
test(tes,wes): add comprehensive unit tests #17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 3 commits
de59af2
879a84a
ddaa0de
75ee3a5
29f774d
1300eb3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,52 +1,42 @@ | ||
| from pydantic import ValidationError | ||
| from .abstract_converter import AbstractConverter | ||
| from .utils import convert_to_iso8601 | ||
| from ..models import TESData, WRROCData | ||
|
|
||
| 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 | ||
| try: | ||
| validated_tes_data = TESData(**tes_data) | ||
| except ValidationError as e: | ||
| raise ValueError(f"Invalid TES data: {e}") | ||
|
|
||
| # 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], | ||
| "startTime": convert_to_iso8601(creation_time), | ||
| "endTime": convert_to_iso8601(end_time), | ||
| "@id": validated_tes_data.id, | ||
| "name": validated_tes_data.name, | ||
| "description": validated_tes_data.description, | ||
| "instrument": validated_tes_data.executors[0].image if validated_tes_data.executors else None, | ||
| "object": [{"@id": input.url, "name": input.path} for input in validated_tes_data.inputs], | ||
| "result": [{"@id": output.url, "name": output.path} for output in validated_tes_data.outputs], | ||
| "startTime": convert_to_iso8601(validated_tes_data.creation_time), | ||
| "endTime": convert_to_iso8601(validated_tes_data.logs[0].end_time) if validated_tes_data.logs else None, | ||
| } | ||
| 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", "") | ||
| try: | ||
| validated_wrroc_data = WRROCData(**wrroc_data) | ||
| except ValidationError as e: | ||
| raise ValueError(f"Invalid WRROC data: {e}") | ||
|
|
||
| # Convert from WRROC to TES | ||
| tes_data = { | ||
| "id": id, | ||
| "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], | ||
| "creation_time": start_time, | ||
| "logs": [{"end_time": end_time}], # Added to logs | ||
| "id": validated_wrroc_data.id, | ||
| "name": validated_wrroc_data.name, | ||
| "description": validated_wrroc_data.description, | ||
| "executors": [{"image": validated_wrroc_data.instrument}], | ||
| "inputs": [{"url": obj.id, "path": obj.name} for obj in validated_wrroc_data.object], | ||
| "outputs": [{"url": res.id, "path": res.name} for res in validated_wrroc_data.result], | ||
| "creation_time": validated_wrroc_data.startTime, | ||
| "logs": [{"end_time": validated_wrroc_data.endTime}], | ||
| } | ||
| return tes_data | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,46 +1,43 @@ | ||
| from pydantic import ValidationError | ||
| from .abstract_converter import AbstractConverter | ||
| from ..models import WESData, WRROCDataWES | ||
| from .utils import convert_to_iso8601 | ||
|
|
||
| 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", {}) | ||
| try: | ||
| wes_model = WESData(**wes_data) | ||
| except ValidationError as e: | ||
| raise ValueError(f"Invalid WES data: {e}") | ||
| outputs = wes_model.outputs | ||
|
|
||
| # Convert to WRROC | ||
| wrroc_data = { | ||
| "@id": run_id, | ||
| "name": name, | ||
| "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], | ||
| "@id": wes_model.run_id, | ||
| "name": wes_model.run_log.name, | ||
| "status": wes_model.state, | ||
| "startTime": convert_to_iso8601(wes_model.run_log.start_time), | ||
| "endTime": convert_to_iso8601(wes_model.run_log.end_time), | ||
| "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", []) | ||
|
|
||
| # Convert from WRROC to WES | ||
| try: | ||
| wrroc_model = WRROCDataWES(**wrroc_data) | ||
| except ValidationError as e: | ||
| raise ValueError(f"Invalid WRROC data: {e}") | ||
|
|
||
| result_data = wrroc_model.result | ||
|
|
||
| wes_data = { | ||
| "run_id": run_id, | ||
| "run_id": wrroc_model.id, | ||
| "run_log": { | ||
| "name": name, | ||
| "start_time": start_time, | ||
| "end_time": end_time, | ||
| "name": wrroc_model.name, | ||
| "start_time": wrroc_model.startTime, | ||
| "end_time": wrroc_model.endTime, | ||
| }, | ||
| "state": state, | ||
| "outputs": [{"location": res.get("@id", ""), "name": res.get("name", "")} for res in result_data], | ||
| "state": wrroc_model.status, | ||
| "outputs": [{"location": res.id, "name": res.name} for res in result_data], | ||
| } | ||
| return wes_data |
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When running the tool with WRROC data as input, you probably want to first validate that the input is indeed a valid WRROC entity. For that, you could use a model In a next step, you then want to validate that the WRROC data has all the fields required for the requested conversion. This code is specific to the converter, i.e., you may need different data for the conversion to TES than you need for the conversion to WES. For that you could define models
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will implement an external WRROC validator function to ensure WRROC data validity and avoid code repetition. Additionally, I will define separate models for WRROCDataTES and WRROCDataWES for specific validation in the respective converters.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please double check your
I think the models and the corresponding validators (including unit tests etc.) should be a single PR. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| from pydantic import BaseModel, Field, validator,root_validator | ||
| from typing import List, Optional, Dict, Any | ||
|
|
||
| class Executor(BaseModel): | ||
| image: str | ||
| command: List[str] | ||
|
|
||
| class TESInputs(BaseModel): | ||
| url: str | ||
| path: str | ||
|
|
||
| class TESOutputs(BaseModel): | ||
| url: str | ||
| path: str | ||
|
|
||
| class TESLogs(BaseModel): | ||
| end_time: Optional[str] = None | ||
|
|
||
| class TESData(BaseModel): | ||
| id: str | ||
| name: str | ||
| description: Optional[str] = "" | ||
| executors: List[Executor] | ||
| inputs: List[TESInputs] | ||
| outputs: List[TESOutputs] | ||
| creation_time: str | ||
| logs: List[TESLogs] | ||
|
|
||
| class WESRunLog(BaseModel): | ||
| name: Optional[str] = None | ||
| start_time: Optional[str] = None | ||
| end_time: Optional[str] = None | ||
|
|
||
| class WESOutputs(BaseModel): | ||
| location: str | ||
| name: str | ||
|
|
||
| class WESData(BaseModel): | ||
| run_id: str | ||
| run_log: WESRunLog | ||
| state: str | ||
| outputs: List[WESOutputs] | ||
|
|
||
| @root_validator(pre=True) | ||
|
||
| def check_unexpected_fields(cls, values): | ||
| allowed_fields = {"run_id", "run_log", "state", "outputs"} | ||
| unexpected = set(values.keys()) - allowed_fields | ||
| if unexpected: | ||
| raise ValueError(f"Unexpected fields: {unexpected}") | ||
| return values | ||
|
|
||
| class WRROCInputs(BaseModel): | ||
| id: str | ||
| name: str | ||
|
|
||
| class WRROCOutputs(BaseModel): | ||
| id: str | ||
| name: str | ||
|
|
||
| class WRROCData(BaseModel): | ||
| 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 | ||
|
|
||
| @validator('id') | ||
|
||
| def id_must_be_string(cls, value): | ||
| if not isinstance(value, str): | ||
| raise ValueError('Invalid id type') | ||
| return value | ||
|
|
||
| @validator('name') | ||
| def name_must_be_string(cls, value): | ||
| if not isinstance(value, str): | ||
| raise ValueError('Invalid name type') | ||
| return value | ||
|
|
||
| class WRROCDataWES(BaseModel): | ||
| id: str | ||
| name: str | ||
| status: str | ||
| result: List[WRROCOutputs] | ||
| startTime: Optional[str] = None | ||
| endTime: Optional[str] = None | ||
|
|
||
| @root_validator(pre=True) | ||
|
||
| def check_unexpected_fields(cls, values): | ||
| allowed_fields = {"id", "name", "startTime", "endTime", "status", "result"} | ||
| unexpected = set(values.keys()) - allowed_fields | ||
| if unexpected: | ||
| raise ValueError(f"Unexpected fields: {unexpected}") | ||
| return values | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In such cases, it is good practice to chain exceptions, i.e., instead of
do
Please address this in a future PR for all these cases (but not in this PR).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will address exception chaining in all relevant cases in a future PR to follow best practices as you suggested.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay. Please create an issue for that and then close this conversation.