diff --git a/csfunctions/actions/__init__.py b/csfunctions/actions/__init__.py index aebcd82..103301b 100644 --- a/csfunctions/actions/__init__.py +++ b/csfunctions/actions/__init__.py @@ -3,6 +3,8 @@ from pydantic import Field from .abort_and_show_error import AbortAndShowErrorAction +from .base import ActionNames from .dummy import DummyAction -Action = Annotated[Union[AbortAndShowErrorAction, DummyAction], Field(discriminator="name")] +ActionUnion = Union[AbortAndShowErrorAction, DummyAction] +Action = Annotated[ActionUnion, Field(discriminator="name")] diff --git a/csfunctions/actions/abort_and_show_error.py b/csfunctions/actions/abort_and_show_error.py index 94c91af..72bff6d 100644 --- a/csfunctions/actions/abort_and_show_error.py +++ b/csfunctions/actions/abort_and_show_error.py @@ -1,23 +1,10 @@ from typing import Literal -from pydantic import BaseModel, Field +from pydantic import Field from .base import ActionNames, BaseAction -class AbortAndShowErrorData(BaseModel): - def __init__(self, message: str, **kwargs): - super().__init__(message=message, **kwargs) - - message: str = Field("unknown error", description="error message to be shown to the user") - - class AbortAndShowErrorAction(BaseAction): - def __init__(self, id: str, message: str | None = None, **kwargs): # pylint: disable=redefined-builtin - if message: - super().__init__(name=ActionNames.ABORT_AND_SHOW_ERROR, id=id, data=AbortAndShowErrorData(message=message)) - else: - super().__init__(name=ActionNames.ABORT_AND_SHOW_ERROR, id=id, data=kwargs["data"]) - - name: Literal[ActionNames.ABORT_AND_SHOW_ERROR] - data: AbortAndShowErrorData + name: Literal[ActionNames.ABORT_AND_SHOW_ERROR] = ActionNames.ABORT_AND_SHOW_ERROR + message: str = Field("unknown error", description="error message to be shown to the user") diff --git a/csfunctions/actions/base.py b/csfunctions/actions/base.py index 7e77762..19fbc9e 100644 --- a/csfunctions/actions/base.py +++ b/csfunctions/actions/base.py @@ -1,6 +1,6 @@ from enum import Enum -from pydantic import BaseModel, Field +from pydantic import BaseModel class ActionNames(str, Enum): @@ -10,5 +10,4 @@ class ActionNames(str, Enum): class BaseAction(BaseModel): name: str - id: str = Field(..., description="unique identifier") - data: dict | None = None + id: str | None = None diff --git a/csfunctions/events/__init__.py b/csfunctions/events/__init__.py index e0b449a..2e494af 100644 --- a/csfunctions/events/__init__.py +++ b/csfunctions/events/__init__.py @@ -2,29 +2,39 @@ from pydantic import Field -from .document_release import DocumentReleaseData, DocumentReleaseDialogData, DocumentReleaseEvent +from .dialog_data import DocumentReleaseDialogData, PartReleaseDialogData +from .document_release import DocumentReleaseData, DocumentReleaseEvent +from .document_release_check import DocumentReleaseCheckData, DocumentReleaseCheckEvent from .dummy import DummyEvent, DummyEventData from .engineering_change_release import EngineeringChangeRelease, EngineeringChangeReleaseData +from .engineering_change_release_check import EngineeringChangeReleaseCheck, EngineeringChangeReleaseCheckData from .field_value_calculation import FieldValueCalculationData, FieldValueCalculationEvent -from .part_release import PartReleaseData, PartReleaseDialogData, PartReleaseEvent +from .part_release import PartReleaseData, PartReleaseEvent +from .part_release_check import PartReleaseCheckData, PartReleaseCheckEvent from .workflow_task_trigger import WorkflowTaskTriggerEvent, WorkflowTaskTriggerEventData Event = Annotated[ Union[ DocumentReleaseEvent, + DocumentReleaseCheckEvent, PartReleaseEvent, + PartReleaseCheckEvent, FieldValueCalculationEvent, DummyEvent, EngineeringChangeRelease, + EngineeringChangeReleaseCheck, WorkflowTaskTriggerEvent, ], Field(discriminator="name"), ] EventData = Union[ DocumentReleaseData, + DocumentReleaseCheckData, PartReleaseData, + PartReleaseCheckData, FieldValueCalculationData, DummyEventData, EngineeringChangeReleaseData, + EngineeringChangeReleaseCheckData, WorkflowTaskTriggerEventData, ] diff --git a/csfunctions/events/base.py b/csfunctions/events/base.py index 27102ca..282c256 100644 --- a/csfunctions/events/base.py +++ b/csfunctions/events/base.py @@ -6,8 +6,11 @@ class EventNames(str, Enum): DUMMY = "dummy" DOCUMENT_RELEASE = "document_release" + DOCUMENT_RELEASE_CHECK = "document_release_check" PART_RELEASE = "part_release" + PART_RELEASE_CHECK = "part_release_check" ENGINEERING_CHANGE_RELEASE = "engineering_change_release" + ENGINEERING_CHANGE_RELEASE_CHECK = "engineering_change_release_check" FIELD_VALUE_CALCULATION = "field_value_calculation" WORKFLOW_TASK_TRIGGER = "workflow_task_trigger" @@ -15,4 +18,3 @@ class EventNames(str, Enum): class BaseEvent(BaseModel): name: str event_id: str = Field(..., description="unique identifier") - data: dict | None = None diff --git a/csfunctions/events/dialog_data.py b/csfunctions/events/dialog_data.py new file mode 100644 index 0000000..4b5919f --- /dev/null +++ b/csfunctions/events/dialog_data.py @@ -0,0 +1,15 @@ +from typing import Literal + +from pydantic import BaseModel, Field + + +class DocumentReleaseDialogData(BaseModel): + dialog_type: Literal["document_release"] = "document_release" + cdbprot_remark: str | None = Field(None, description="remark") + cdb_ec_id: str | None = Field(None, description="Engineering Change ID") + + +class PartReleaseDialogData(BaseModel): + dialog_type: Literal["part_release"] = "part_release" + cdbprot_remark: str | None = Field("", description="remark") + cdb_ec_id: str | None = Field("", description="Engineering Change ID") diff --git a/csfunctions/events/document_release.py b/csfunctions/events/document_release.py index a9e97a9..93d2871 100644 --- a/csfunctions/events/document_release.py +++ b/csfunctions/events/document_release.py @@ -5,25 +5,15 @@ from csfunctions.objects import Document, Part from .base import BaseEvent, EventNames - - -class DocumentReleaseDialogData(BaseModel): - cdbprot_remark: str | None = Field(None, description="remark") - cdb_ec_id: str | None = Field(None, description="Engineering Change ID") +from .dialog_data import DocumentReleaseDialogData class DocumentReleaseData(BaseModel): - def __init__(self, documents: list[Document], parts: list[Part], dialog_data: dict, **kwargs): - super().__init__(documents=documents, parts=parts, dialog_data=dialog_data, **kwargs) - - documents: list[Document] = Field(..., description="List if documents that were released.") + documents: list[Document] = Field(..., description="List of documents that were released.") parts: list[Part] = Field(..., description="List of parts that belong to the released documents") dialog_data: DocumentReleaseDialogData class DocumentReleaseEvent(BaseEvent): - def __init__(self, event_id: str, data: DocumentReleaseData, **_): - super().__init__(name=EventNames.DOCUMENT_RELEASE, event_id=event_id, data=data) - - name: Literal[EventNames.DOCUMENT_RELEASE] + name: Literal[EventNames.DOCUMENT_RELEASE] = EventNames.DOCUMENT_RELEASE data: DocumentReleaseData diff --git a/csfunctions/events/document_release_check.py b/csfunctions/events/document_release_check.py new file mode 100644 index 0000000..27797a8 --- /dev/null +++ b/csfunctions/events/document_release_check.py @@ -0,0 +1,19 @@ +from typing import Literal + +from pydantic import BaseModel, Field + +from csfunctions.objects import Document, Part + +from .base import BaseEvent, EventNames +from .dialog_data import DocumentReleaseDialogData + + +class DocumentReleaseCheckData(BaseModel): + documents: list[Document] = Field(..., description="List of documents that will be released.") + attached_parts: list[Part] = Field(..., description="List of parts that belong to the documents") + dialog_data: DocumentReleaseDialogData + + +class DocumentReleaseCheckEvent(BaseEvent): + name: Literal[EventNames.DOCUMENT_RELEASE_CHECK] = EventNames.DOCUMENT_RELEASE_CHECK + data: DocumentReleaseCheckData diff --git a/csfunctions/events/engineering_change_release.py b/csfunctions/events/engineering_change_release.py index da4ebec..e5f7c54 100644 --- a/csfunctions/events/engineering_change_release.py +++ b/csfunctions/events/engineering_change_release.py @@ -16,8 +16,5 @@ class EngineeringChangeReleaseData(BaseModel): class EngineeringChangeRelease(BaseEvent): - def __init__(self, event_id: str, data: EngineeringChangeReleaseData, **_): - super().__init__(name=EventNames.ENGINEERING_CHANGE_RELEASE, event_id=event_id, data=data) - - name: Literal[EventNames.ENGINEERING_CHANGE_RELEASE] + name: Literal[EventNames.ENGINEERING_CHANGE_RELEASE] = EventNames.ENGINEERING_CHANGE_RELEASE data: EngineeringChangeReleaseData diff --git a/csfunctions/events/engineering_change_release_check.py b/csfunctions/events/engineering_change_release_check.py new file mode 100644 index 0000000..254328b --- /dev/null +++ b/csfunctions/events/engineering_change_release_check.py @@ -0,0 +1,20 @@ +from typing import Literal + +from pydantic import BaseModel, Field + +from csfunctions.objects import Document, EngineeringChange, Part + +from .base import BaseEvent, EventNames + + +class EngineeringChangeReleaseCheckData(BaseModel): + attached_documents: list[Document] = Field(..., description="List of included documents.") + attached_parts: list[Part] = Field(..., description="List of included parts.") + engineering_changes: list[EngineeringChange] = Field( + ..., description="List of engineering changes that will be released." + ) + + +class EngineeringChangeReleaseCheck(BaseEvent): + name: Literal[EventNames.ENGINEERING_CHANGE_RELEASE_CHECK] = EventNames.ENGINEERING_CHANGE_RELEASE_CHECK + data: EngineeringChangeReleaseCheckData diff --git a/csfunctions/events/part_release.py b/csfunctions/events/part_release.py index e91d9eb..b86e827 100644 --- a/csfunctions/events/part_release.py +++ b/csfunctions/events/part_release.py @@ -5,25 +5,15 @@ from csfunctions.objects import Document, Part from .base import BaseEvent, EventNames - - -class PartReleaseDialogData(BaseModel): - cdbprot_remark: str | None = Field("", description="remark") - cdb_ec_id: str | None = Field("", description="Engineering Change ID") +from .dialog_data import PartReleaseDialogData class PartReleaseData(BaseModel): - def __init__(self, parts: list[Part], dialog_data: dict, **kwargs): - super().__init__(parts=parts, dialog_data=dialog_data, **kwargs) - parts: list[Part] = Field(..., description="List if parts that were released.") documents: list[Document] = Field(..., description="List if documents that are referenced by the released part.") dialog_data: PartReleaseDialogData class PartReleaseEvent(BaseEvent): - def __init__(self, event_id: str, data: PartReleaseData, **_): - super().__init__(name=EventNames.PART_RELEASE, event_id=event_id, data=data) - - name: Literal[EventNames.PART_RELEASE] + name: Literal[EventNames.PART_RELEASE] = EventNames.PART_RELEASE data: PartReleaseData diff --git a/csfunctions/events/part_release_check.py b/csfunctions/events/part_release_check.py new file mode 100644 index 0000000..a848359 --- /dev/null +++ b/csfunctions/events/part_release_check.py @@ -0,0 +1,19 @@ +from typing import Literal + +from pydantic import BaseModel, Field + +from csfunctions.objects import Document, Part + +from .base import BaseEvent, EventNames +from .dialog_data import PartReleaseDialogData + + +class PartReleaseCheckData(BaseModel): + parts: list[Part] = Field(..., description="List of parts that will be released.") + attached_documents: list[Document] = Field(..., description="List of documents that are referenced by the parts.") + dialog_data: PartReleaseDialogData + + +class PartReleaseCheckEvent(BaseEvent): + name: Literal[EventNames.PART_RELEASE_CHECK] + data: PartReleaseCheckData diff --git a/csfunctions/handler.py b/csfunctions/handler.py index 708f9a5..34d785f 100644 --- a/csfunctions/handler.py +++ b/csfunctions/handler.py @@ -8,9 +8,10 @@ import yaml from pydantic import BaseModel -from csfunctions import ErrorResponse, Event, Request +from csfunctions import ErrorResponse, Event, Request, WorkloadResponse +from csfunctions.actions import ActionUnion from csfunctions.config import ConfigModel, FunctionModel -from csfunctions.objects.base import BaseObject +from csfunctions.objects import BaseObject from csfunctions.response import ResponseUnion from csfunctions.service import Service @@ -46,26 +47,6 @@ def get_function_callable(function_name: str, function_dir: str) -> Callable: return getattr(mod, function_name) -def link_documents2parts(event: Event) -> None: - """ - Links the relationship document -> part - """ - if hasattr(event.data, "documents") and hasattr(event.data, "parts"): - # build parts dicts, indexed by teilenummer@index - parts = {} - for part in event.data.parts: - key = part.teilenummer + "@" + part.t_index - parts[key] = part - - # link the parts to the documents - for document in event.data.documents: - if document.teilenummer is None or document.t_index is None: - continue - key = document.teilenummer + "@" + document.t_index - if key in parts: - document.part = parts[key] - - def link_objects(event: Event): """ Link the relationships between objects, to allow accessing relationships with dot notation, @@ -108,6 +89,13 @@ def execute(function_name: str, request_body: str, function_dir: str = "src") -> if response is None: return "" + if isinstance(response, ActionUnion): + # wrap returned Actions into a WorkloadResponse + response = WorkloadResponse(actions=[response]) + elif isinstance(response, list) and all(isinstance(o, ActionUnion) for o in response): + # wrap list of Actions into a WorkloadResponse + response = WorkloadResponse(actions=response) + if not isinstance( response, ResponseUnion ): # need to check for ResponseUnion instead of Response, because isinstance doesn't work with annotated unions diff --git a/csfunctions/objects/__init__.py b/csfunctions/objects/__init__.py index 66ea4a9..1f51df8 100644 --- a/csfunctions/objects/__init__.py +++ b/csfunctions/objects/__init__.py @@ -2,6 +2,7 @@ from pydantic import Field +from .base import BaseObject from .briefcase import Briefcase from .classification import ObjectPropertyValue from .document import CADDocument, Document diff --git a/csfunctions/objects/document.py b/csfunctions/objects/document.py index 35b5e7c..b4c5715 100644 --- a/csfunctions/objects/document.py +++ b/csfunctions/objects/document.py @@ -3,6 +3,8 @@ from pydantic import Field +from csfunctions.util import get_items_of_type + from .base import BaseObject, ObjectType from .file import File @@ -31,7 +33,7 @@ class Document(BaseObject): category2_name_de: str | None = Field(..., description="Category") z_categ1: str | None = Field(..., description="Main Category") z_categ2: str | None = Field(..., description="Category") - cdb_obsolete: int = Field(..., description="Obsolete") + cdb_obsolete: int | None = Field(..., description="Obsolete") z_status: int = Field(..., description="Status Number") z_status_txt: str = Field(..., description="Status Text") autoren: str | None = Field(..., description="Authors, comma separated") @@ -79,8 +81,8 @@ class Document(BaseObject): part: Optional["Part"] = Field(None, exclude=True) def link_objects(self, data: "EventData"): - parts = getattr(data, "parts", None) - if parts and self.teilenummer: + if self.teilenummer: + parts = get_items_of_type(data, Part) self._link_part(parts) def _link_part(self, parts: list["Part"]): diff --git a/csfunctions/objects/part.py b/csfunctions/objects/part.py index 9986481..23fc2ea 100644 --- a/csfunctions/objects/part.py +++ b/csfunctions/objects/part.py @@ -3,12 +3,17 @@ from pydantic import Field +from csfunctions.util import get_items_of_type + from .base import BaseObject, ObjectType if TYPE_CHECKING: from csfunctions.events import EventData +try: from .document import Document +except ImportError: + pass class Part(BaseObject): @@ -77,8 +82,8 @@ class Part(BaseObject): documents: list["Document"] = Field([], exclude=True) def link_objects(self, data: "EventData"): - documents = getattr(data, "documents", None) - if documents and self.document_ids: + if self.document_ids: + documents = get_items_of_type(data, Document) self._link_documents(documents) def _link_documents(self, documents: list["Document"]): diff --git a/csfunctions/util.py b/csfunctions/util.py new file mode 100644 index 0000000..ad322d9 --- /dev/null +++ b/csfunctions/util.py @@ -0,0 +1,29 @@ +from typing import List, Type, TypeVar + +from pydantic import BaseModel + +T = TypeVar("T") + + +def get_items_of_type(model: BaseModel, target_type: Type[T]) -> List[T]: + """ + Retrieve items of a specific type from lists within a Pydantic model. + + This is useful, e.g. to get all Documents or Parts from an events data package, + without having to know how the fields are called. + + Args: + model (BaseModel): The Pydantic model to search through. + target_type (Type[T]): The type of items to search for. + + Returns: + List[T]: A list of items of the specified type found within lists in the model. + """ + items = [] + for field_name in model.model_fields_set: + attr = getattr(model, field_name) + if isinstance(attr, list): + for item in attr: + if isinstance(item, target_type): + items.append(item) + return items diff --git a/docs/reference/actions.md b/docs/reference/actions.md index f813863..685b401 100644 --- a/docs/reference/actions.md +++ b/docs/reference/actions.md @@ -1 +1,24 @@ -Actions are not yet implemented in CIM Database Cloud. +Functions can return a list of "Actions" that should be performed in CIM Database Cloud. Not all Events support the same actions, so check the supported actions in the [Events documentation](events.md). For example Events that are triggered **after** the release of an object don't support AbortAndShowError, because the release can't be aborted anymore, however the "release check" events do support it. + +### Return an Action: + +Example: + +```python +from csfunctions.actions import AbortAndShowErrorAction + +def my_function(metadata, event, service): + # this will show an error message to the user + return AbortAndShowErrorAction(message="Custom error message.") +``` + +## AbortAndShowErrorAction + +`csfunctions.actions.AbortAndShowErrorAction` + +Aborts the current operation and shows an error message to the user. + + +**AbortAndShowErrorAction.name:** abort_and_show_error + +**AbortAndShowErrorAction.message:** Error message that will be shown to the user diff --git a/docs/reference/events.md b/docs/reference/events.md index 1a7777c..f7fba6a 100644 --- a/docs/reference/events.md +++ b/docs/reference/events.md @@ -1,5 +1,32 @@ Events always have a `name` and a `data` attribute. The contents of those attributes depend on the type of the event. +## DocumentReleaseCheckEvent +`csfunctions.events.DocumentReleaseCheckEvent` + +This event is fired when a user tries to release a document. Raising an exception will prevent the release. If the release is done via the express release, the event is triggered before reviewers are notified. +Be aware that the document is not released yet and the release might still be aborted for other reasons (e.g. the reviewers don't give approval), so don't sent the document to e.g. an ERP system yet. + +**Supported actions:** + +- [AbortAndShowErrorAction](actions.md#AbortAndShowErrorAction) + +**DocumentReleaseCheckEvent.name:** document_release_check + +**DocumentReleaseCheckEvent.data:** + +|Attribute|Type|Description| +|-|-|-| +|documents| list[[Document](objects.md#document)]|List of documents that will be released.| +|attached_parts| list[[Part](objects.md#part)]|List of parts that belong to the documents.| +|dialog_data|DocumentReleaseDialogData|Contents of the dialog.| + +**DocumentReleaseCheckDialogData:** + +|Attribute|Type|Description| +|-|-|-| +|cdbprot_remark|str \| None | Remark| +|cdb_ec_id|str \| None| Engineering Change ID| + ## DocumentReleaseEvent `csfunctions.events.DocumentReleaseEvent` @@ -23,6 +50,27 @@ This event is fired **after** a document has been released. Raising an exception |cdb_ec_id|str \| None| Engineering Change ID| +## EngineeringChangeReleaseCheck +`csfunctions.events.EngineeringChangeReleaseCheck` + +This event is fired when a user tries to release an engineering change. Raising an exception will prevent the release. +Be aware that the engineering change is not released yet and the release might still be aborted for other reasons, so don't sent the engineering change to e.g. an ERP system yet. + +**Supported actions:** + +- [AbortAndShowErrorAction](actions.md#AbortAndShowErrorAction) + +**EngineeringChangeReleaseCheck.name:** engineering_change_check_release + +**EngineeringChangeReleaseCheck.data:** + +|Attribute|Type|Description| +|-|-|-| +|engineering_changes| list[[EngineeringChange](objects.md#engineeringchange)]|List of engineering changes that will be released.| +|attached_documents| list[[Document](objects.md#document)]|List of included documents.| +|attached_parts| list[[Part](objects.md#part)]|List of included parts.| + + ## EngineeringChangeRelease `csfunctions.events.EngineeringChangeRelease` @@ -39,6 +87,34 @@ This event is fired **after** an engineering change has been released. Raising a |parts| list[[Part](objects.md#part)]|List of included parts.| +## PartReleaseCheckEvent +`csfunctions.events.PartReleaseCheckEvent` + +This event is fired when a user tries to release a part. Raising an exception will prevent the release. If the release is done via the express release, the event is triggered before reviewers are notified. +Be aware that the part is not released yet and the release might still be aborted for other reasons (e.g. the reviewers don't give approval), so don't sent the part to e.g. an ERP system yet. + +**Supported actions:** + +- [AbortAndShowErrorAction](actions.md#AbortAndShowErrorAction) + +**PartRPartReleaseCheckEventeleaseEvent.name:** part_release_check + +**PartReleaseCheckEvent.data:** + +|Attribute|Type|Description| +|-|-|-| +|parts| list[[Part](objects.md#part)]|List of parts that will released.| +|attached_documents| list[[Document](objects.md#document)]|List of documents that belong to the released parts.| +|dialog_data|PartReleaseDialogData|Contents of the dialog.| + +**PartReleaseCheckDialogData:** + +|Attribute|Type|Description| +|-|-|-| +|cdbprot_remark|str \| None | Remark| +|cdb_ec_id|str \| None| Engineering Change ID| + + ## PartReleaseEvent `csfunctions.events.PartReleaseEvent` @@ -61,6 +137,7 @@ This event is fired **after** a part has been released. Raising an exception thu |cdbprot_remark|str \| None | Remark| |cdb_ec_id|str \| None| Engineering Change ID| + ## WorkflowTaskTriggerEvent `csfunctions.events.WorkflowTaskTriggerEvent` diff --git a/docs/reference/objects.md b/docs/reference/objects.md index a4bbe85..1536682 100644 --- a/docs/reference/objects.md +++ b/docs/reference/objects.md @@ -48,7 +48,7 @@ Normal Document that doesn't contain a CAD-Model. |category2_name_de|str \| None|Category| |z_categ1|str \| None|Main Category| |z_categ2|str \| None|Category| -|cdb_obsolete|int|Obsolete| +|cdb_obsolete|int \| None|Obsolete| |z_status|int|Status Number| |z_status_txt|str|Status Text| |autoren|str \| None|Authors, comma separated| @@ -86,6 +86,7 @@ Normal Document that doesn't contain a CAD-Model. |cdb_m2date|datetime \| None|File Last Saved on| |z_art|str \| None|Document Type| |mapped_materialnr_erp|str \| None|Materialnumber ERP| +|cdb_object_id|str \| None|Object ID| |files|list[[File](objects.md#file)]|Files attached to the document| |part|typing.Optional[[Part](objects.md#part)]|| @@ -101,6 +102,7 @@ Normal Document that doesn't contain a CAD-Model. |status|int|Status| |title|str \| None|Title| |template_ec_id|str \| None|Template ID| +|cdb_object_id|str \| None|Object ID| |c_department|str \| None|Department| |c_description|str \| None|Description| |c_event|str \| None|Event| diff --git a/json_schemas/request.json b/json_schemas/request.json index 079c7b3..d892f1d 100644 --- a/json_schemas/request.json +++ b/json_schemas/request.json @@ -195,9 +195,16 @@ "title": "Z Categ2" }, "cdb_obsolete": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], "description": "Obsolete", - "title": "Cdb Obsolete", - "type": "integer" + "title": "Cdb Obsolete" }, "z_status": { "description": "Status Number", @@ -730,10 +737,67 @@ "title": "Document", "type": "object" }, + "DocumentReleaseCheckData": { + "properties": { + "documents": { + "description": "List of documents that will be released.", + "items": { + "$ref": "#/$defs/Document" + }, + "title": "Documents", + "type": "array" + }, + "attached_parts": { + "description": "List of parts that belong to the documents", + "items": { + "$ref": "#/$defs/Part" + }, + "title": "Attached Parts", + "type": "array" + }, + "dialog_data": { + "$ref": "#/$defs/DocumentReleaseDialogData" + } + }, + "required": [ + "documents", + "attached_parts", + "dialog_data" + ], + "title": "DocumentReleaseCheckData", + "type": "object" + }, + "DocumentReleaseCheckEvent": { + "properties": { + "name": { + "const": "document_release_check", + "default": "document_release_check", + "enum": [ + "document_release_check" + ], + "title": "Name", + "type": "string" + }, + "event_id": { + "description": "unique identifier", + "title": "Event Id", + "type": "string" + }, + "data": { + "$ref": "#/$defs/DocumentReleaseCheckData" + } + }, + "required": [ + "event_id", + "data" + ], + "title": "DocumentReleaseCheckEvent", + "type": "object" + }, "DocumentReleaseData": { "properties": { "documents": { - "description": "List if documents that were released.", + "description": "List of documents that were released.", "items": { "$ref": "#/$defs/Document" }, @@ -762,6 +826,15 @@ }, "DocumentReleaseDialogData": { "properties": { + "dialog_type": { + "const": "document_release", + "default": "document_release", + "enum": [ + "document_release" + ], + "title": "Dialog Type", + "type": "string" + }, "cdbprot_remark": { "anyOf": [ { @@ -796,6 +869,7 @@ "properties": { "name": { "const": "document_release", + "default": "document_release", "enum": [ "document_release" ], @@ -812,7 +886,6 @@ } }, "required": [ - "name", "event_id", "data" ], @@ -1190,6 +1263,7 @@ "properties": { "name": { "const": "engineering_change_release", + "default": "engineering_change_release", "enum": [ "engineering_change_release" ], @@ -1206,13 +1280,74 @@ } }, "required": [ - "name", "event_id", "data" ], "title": "EngineeringChangeRelease", "type": "object" }, + "EngineeringChangeReleaseCheck": { + "properties": { + "name": { + "const": "engineering_change_release_check", + "default": "engineering_change_release_check", + "enum": [ + "engineering_change_release_check" + ], + "title": "Name", + "type": "string" + }, + "event_id": { + "description": "unique identifier", + "title": "Event Id", + "type": "string" + }, + "data": { + "$ref": "#/$defs/EngineeringChangeReleaseCheckData" + } + }, + "required": [ + "event_id", + "data" + ], + "title": "EngineeringChangeReleaseCheck", + "type": "object" + }, + "EngineeringChangeReleaseCheckData": { + "properties": { + "attached_documents": { + "description": "List of included documents.", + "items": { + "$ref": "#/$defs/Document" + }, + "title": "Attached Documents", + "type": "array" + }, + "attached_parts": { + "description": "List of included parts.", + "items": { + "$ref": "#/$defs/Part" + }, + "title": "Attached Parts", + "type": "array" + }, + "engineering_changes": { + "description": "List of engineering changes that will be released.", + "items": { + "$ref": "#/$defs/EngineeringChange" + }, + "title": "Engineering Changes", + "type": "array" + } + }, + "required": [ + "attached_documents", + "attached_parts", + "engineering_changes" + ], + "title": "EngineeringChangeReleaseCheckData", + "type": "object" + }, "EngineeringChangeReleaseData": { "properties": { "documents": { @@ -2327,6 +2462,63 @@ "title": "Part", "type": "object" }, + "PartReleaseCheckData": { + "properties": { + "parts": { + "description": "List of parts that will be released.", + "items": { + "$ref": "#/$defs/Part" + }, + "title": "Parts", + "type": "array" + }, + "attached_documents": { + "description": "List of documents that are referenced by the parts.", + "items": { + "$ref": "#/$defs/Document" + }, + "title": "Attached Documents", + "type": "array" + }, + "dialog_data": { + "$ref": "#/$defs/PartReleaseDialogData" + } + }, + "required": [ + "parts", + "attached_documents", + "dialog_data" + ], + "title": "PartReleaseCheckData", + "type": "object" + }, + "PartReleaseCheckEvent": { + "properties": { + "name": { + "const": "part_release_check", + "enum": [ + "part_release_check" + ], + "title": "Name", + "type": "string" + }, + "event_id": { + "description": "unique identifier", + "title": "Event Id", + "type": "string" + }, + "data": { + "$ref": "#/$defs/PartReleaseCheckData" + } + }, + "required": [ + "name", + "event_id", + "data" + ], + "title": "PartReleaseCheckEvent", + "type": "object" + }, "PartReleaseData": { "properties": { "parts": { @@ -2359,6 +2551,15 @@ }, "PartReleaseDialogData": { "properties": { + "dialog_type": { + "const": "part_release", + "default": "part_release", + "enum": [ + "part_release" + ], + "title": "Dialog Type", + "type": "string" + }, "cdbprot_remark": { "anyOf": [ { @@ -2393,6 +2594,7 @@ "properties": { "name": { "const": "part_release", + "default": "part_release", "enum": [ "part_release" ], @@ -2409,7 +2611,6 @@ } }, "required": [ - "name", "event_id", "data" ], @@ -2607,10 +2808,13 @@ "discriminator": { "mapping": { "document_release": "#/$defs/DocumentReleaseEvent", + "document_release_check": "#/$defs/DocumentReleaseCheckEvent", "dummy": "#/$defs/DummyEvent", "engineering_change_release": "#/$defs/EngineeringChangeRelease", + "engineering_change_release_check": "#/$defs/EngineeringChangeReleaseCheck", "field_value_calculation": "#/$defs/FieldValueCalculationEvent", "part_release": "#/$defs/PartReleaseEvent", + "part_release_check": "#/$defs/PartReleaseCheckEvent", "workflow_task_trigger": "#/$defs/WorkflowTaskTriggerEvent" }, "propertyName": "name" @@ -2619,9 +2823,15 @@ { "$ref": "#/$defs/DocumentReleaseEvent" }, + { + "$ref": "#/$defs/DocumentReleaseCheckEvent" + }, { "$ref": "#/$defs/PartReleaseEvent" }, + { + "$ref": "#/$defs/PartReleaseCheckEvent" + }, { "$ref": "#/$defs/FieldValueCalculationEvent" }, @@ -2631,6 +2841,9 @@ { "$ref": "#/$defs/EngineeringChangeRelease" }, + { + "$ref": "#/$defs/EngineeringChangeReleaseCheck" + }, { "$ref": "#/$defs/WorkflowTaskTriggerEvent" } diff --git a/json_schemas/workload_response.json b/json_schemas/workload_response.json index 3a53c29..9930872 100644 --- a/json_schemas/workload_response.json +++ b/json_schemas/workload_response.json @@ -4,6 +4,7 @@ "properties": { "name": { "const": "abort_and_show_error", + "default": "abort_and_show_error", "enum": [ "abort_and_show_error" ], @@ -11,24 +12,17 @@ "type": "string" }, "id": { - "description": "unique identifier", - "title": "Id", - "type": "string" + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Id" }, - "data": { - "$ref": "#/$defs/AbortAndShowErrorData" - } - }, - "required": [ - "name", - "id", - "data" - ], - "title": "AbortAndShowErrorAction", - "type": "object" - }, - "AbortAndShowErrorData": { - "properties": { "message": { "default": "unknown error", "description": "error message to be shown to the user", @@ -36,7 +30,7 @@ "type": "string" } }, - "title": "AbortAndShowErrorData", + "title": "AbortAndShowErrorAction", "type": "object" }, "DummyAction": { @@ -51,26 +45,20 @@ "type": "string" }, "id": { - "description": "unique identifier", - "title": "Id", - "type": "string" - }, - "data": { "anyOf": [ { - "type": "object" + "type": "string" }, { "type": "null" } ], "default": null, - "title": "Data" + "title": "Id" } }, "required": [ - "name", - "id" + "name" ], "title": "DummyAction", "type": "object" diff --git a/pyproject.toml b/pyproject.toml index 07a6529..49ffe19 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "contactsoftware-functions" -version = "0.7.2" +version = "0.8.0.dev2" readme = "README.md" license = "MIT" diff --git a/tests/events/test_ec_release.py b/tests/events/test_ec_release.py index 9f7d6af..90dd1c8 100644 --- a/tests/events/test_ec_release.py +++ b/tests/events/test_ec_release.py @@ -18,7 +18,7 @@ def test_link_objects(self): data = EngineeringChangeReleaseData( documents=[document], parts=[part], engineering_changes=[engineering_change] ) - event = EngineeringChangeRelease("123", data) + event = EngineeringChangeRelease(event_id="123", data=data) request.event = event # objects are not linked yet diff --git a/tests/test_handler.py b/tests/test_handler.py index a8d781d..e93e7f1 100644 --- a/tests/test_handler.py +++ b/tests/test_handler.py @@ -3,6 +3,7 @@ from copy import deepcopy from unittest import TestCase +from csfunctions import WorkloadResponse from csfunctions.handler import execute, get_function_callable, link_objects from tests.utils import dummy_request, ping_function @@ -21,6 +22,10 @@ def setUp(self) -> None: entrypoint: tests.utils.ping_function - name: empty entrypoint: tests.utils.empty_function + - name: action + entrypoint: tests.utils.action_function + - name: actionlist + entrypoint: tests.utils.action_list_function """ ) @@ -52,6 +57,21 @@ def test_execute(self): result = execute("empty", json.dumps(request.model_dump(mode="json"), separators=(",", ":")), self.source_dir) self.assertEqual("", result) + # test function returns Action + request = deepcopy(dummy_request) # make a deepcopy, since request object will be modified by link_objects + result = execute("action", json.dumps(request.model_dump(mode="json"), separators=(",", ":")), self.source_dir) + parsed_result = WorkloadResponse.model_validate_json(result) # should be a workload response + self.assertEqual(1, len(parsed_result.actions)) + + # test function returns list of Actions + request = deepcopy(dummy_request) # make a deepcopy, since request object will be modified by link_objects + result = execute( + "actionlist", json.dumps(request.model_dump(mode="json"), separators=(",", ":")), self.source_dir + ) + + parsed_result = WorkloadResponse.model_validate_json(result) # should be a workload response + self.assertEqual(2, len(parsed_result.actions)) + def test_link_objects(self): request = deepcopy(dummy_request) # make a deepcopy, since request object will be modified by link_objects diff --git a/tests/test_workloadresponse.py b/tests/test_workloadresponse.py new file mode 100644 index 0000000..7ad752e --- /dev/null +++ b/tests/test_workloadresponse.py @@ -0,0 +1,24 @@ +from unittest import TestCase + +from pydantic import TypeAdapter + +from csfunctions import Response, WorkloadResponse +from csfunctions.actions import AbortAndShowErrorAction + + +class TestWorkloadResponse(TestCase): + def test_discriminator(self): + """ + Test that the discriminator on action objects (and responses) works + """ + + # create some workload response with an action and serialize it + response_json = WorkloadResponse(actions=[AbortAndShowErrorAction(message="123")]).model_dump_json() + + # parse the json with the Pydantic TypeAdapter to turn the json back into a Response object + response_obj: WorkloadResponse = TypeAdapter(Response).validate_json(response_json) + action_obj = response_obj.actions[0] + + # check that the objects are of the correct type again + self.assertIsInstance(response_obj, WorkloadResponse) + self.assertIsInstance(action_obj, AbortAndShowErrorAction) diff --git a/tests/utils.py b/tests/utils.py index 3ec0064..bd20035 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,6 +1,7 @@ from datetime import datetime from csfunctions import DataResponse, MetaData, Request, Service +from csfunctions.actions import AbortAndShowErrorAction from csfunctions.events import DummyEvent from csfunctions.objects import Document, EngineeringChange, Part @@ -20,6 +21,20 @@ def empty_function(*args, **kwargs): # pylint: disable=unused-argument pass # pylint: disable=unnecessary-pass +def action_function(*args, **kwargs): # pylint: disable=unused-argument + """ + A Function that returns an Action + """ + return AbortAndShowErrorAction(message="Testerror") + + +def action_list_function(*args, **kwargs): # pylint: disable=unused-argument + """ + A Function that returns a list of Actions + """ + return [AbortAndShowErrorAction(message="Testerror"), AbortAndShowErrorAction(message="Testerror")] + + dummy_document = Document( **{ "object_type": "document",