From db0e5a0197271f1735e8201564a86157cc2c33eb Mon Sep 17 00:00:00 2001 From: paulnoirel <87332996+paulnoirel@users.noreply.github.com> Date: Wed, 13 Aug 2025 01:11:37 +0100 Subject: [PATCH 1/2] Fix validation errors for workflow cloning --- libs/labelbox/src/labelbox/schema/workflow/nodes/done_node.py | 4 +++- .../labelbox/schema/workflow/nodes/initial_labeling_node.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/libs/labelbox/src/labelbox/schema/workflow/nodes/done_node.py b/libs/labelbox/src/labelbox/schema/workflow/nodes/done_node.py index fce1d3bd6..98558214e 100644 --- a/libs/labelbox/src/labelbox/schema/workflow/nodes/done_node.py +++ b/libs/labelbox/src/labelbox/schema/workflow/nodes/done_node.py @@ -2,7 +2,7 @@ import logging from typing import Dict, List, Any, Optional -from pydantic import Field, field_validator +from pydantic import Field, field_validator, ConfigDict from labelbox.schema.workflow.base import BaseWorkflowNode from labelbox.schema.workflow.enums import ( @@ -56,6 +56,8 @@ class DoneNode(BaseWorkflowNode): Work reaching a DoneNode is considered successfully completed and will not flow to any other nodes in the workflow. """ + + model_config = ConfigDict(extra="ignore") label: str = Field(default="Done", max_length=50) definition_id: WorkflowDefinitionId = Field( diff --git a/libs/labelbox/src/labelbox/schema/workflow/nodes/initial_labeling_node.py b/libs/labelbox/src/labelbox/schema/workflow/nodes/initial_labeling_node.py index 1adff2e2b..f14f0cc52 100644 --- a/libs/labelbox/src/labelbox/schema/workflow/nodes/initial_labeling_node.py +++ b/libs/labelbox/src/labelbox/schema/workflow/nodes/initial_labeling_node.py @@ -2,7 +2,7 @@ import logging from typing import Dict, List, Any, Optional, Literal, Union -from pydantic import Field, field_validator, model_validator +from pydantic import Field, field_validator, model_validator, ConfigDict from labelbox.schema.workflow.base import BaseWorkflowNode from labelbox.schema.workflow.enums import ( @@ -58,6 +58,8 @@ class InitialLabelingNode(BaseWorkflowNode): This node type is automatically positioned as a workflow entry point and cannot have incoming connections from other nodes. """ + + model_config = ConfigDict(extra="ignore") label: str = Field( default="Initial labeling task", frozen=True, max_length=50 From 0b762e2cfb0b909584d7087aeaf429d831f57a2e Mon Sep 17 00:00:00 2001 From: paulnoirel <87332996+paulnoirel@users.noreply.github.com> Date: Wed, 13 Aug 2025 01:32:29 +0100 Subject: [PATCH 2/2] Update validation for the different nodes --- .../src/labelbox/schema/workflow/nodes/done_node.py | 2 +- .../schema/workflow/nodes/initial_labeling_node.py | 2 +- .../labelbox/schema/workflow/nodes/initial_rework_node.py | 4 +++- .../src/labelbox/schema/workflow/nodes/logic_node.py | 3 ++- .../src/labelbox/schema/workflow/nodes/review_node.py | 4 +++- .../src/labelbox/schema/workflow/nodes/rework_node.py | 3 ++- libs/labelbox/src/labelbox/schema/workflow/workflow.py | 7 +++++-- 7 files changed, 17 insertions(+), 8 deletions(-) diff --git a/libs/labelbox/src/labelbox/schema/workflow/nodes/done_node.py b/libs/labelbox/src/labelbox/schema/workflow/nodes/done_node.py index 98558214e..2c2e2c3cc 100644 --- a/libs/labelbox/src/labelbox/schema/workflow/nodes/done_node.py +++ b/libs/labelbox/src/labelbox/schema/workflow/nodes/done_node.py @@ -56,7 +56,7 @@ class DoneNode(BaseWorkflowNode): Work reaching a DoneNode is considered successfully completed and will not flow to any other nodes in the workflow. """ - + model_config = ConfigDict(extra="ignore") label: str = Field(default="Done", max_length=50) diff --git a/libs/labelbox/src/labelbox/schema/workflow/nodes/initial_labeling_node.py b/libs/labelbox/src/labelbox/schema/workflow/nodes/initial_labeling_node.py index f14f0cc52..5de2af0d6 100644 --- a/libs/labelbox/src/labelbox/schema/workflow/nodes/initial_labeling_node.py +++ b/libs/labelbox/src/labelbox/schema/workflow/nodes/initial_labeling_node.py @@ -58,7 +58,7 @@ class InitialLabelingNode(BaseWorkflowNode): This node type is automatically positioned as a workflow entry point and cannot have incoming connections from other nodes. """ - + model_config = ConfigDict(extra="ignore") label: str = Field( diff --git a/libs/labelbox/src/labelbox/schema/workflow/nodes/initial_rework_node.py b/libs/labelbox/src/labelbox/schema/workflow/nodes/initial_rework_node.py index fca96aeb0..fb86f8e80 100644 --- a/libs/labelbox/src/labelbox/schema/workflow/nodes/initial_rework_node.py +++ b/libs/labelbox/src/labelbox/schema/workflow/nodes/initial_rework_node.py @@ -2,7 +2,7 @@ import logging from typing import Dict, List, Any, Optional, Literal, Union -from pydantic import Field, field_validator, model_validator +from pydantic import Field, field_validator, model_validator, ConfigDict from labelbox.schema.workflow.base import BaseWorkflowNode from labelbox.schema.workflow.enums import ( @@ -66,6 +66,8 @@ class InitialReworkNode(BaseWorkflowNode): to ensure proper routing in the Labelbox platform. """ + model_config = ConfigDict(extra="ignore") + label: str = Field( default="Rework (all rejected)", frozen=True, max_length=50 ) diff --git a/libs/labelbox/src/labelbox/schema/workflow/nodes/logic_node.py b/libs/labelbox/src/labelbox/schema/workflow/nodes/logic_node.py index 1457a7227..6b53437cb 100644 --- a/libs/labelbox/src/labelbox/schema/workflow/nodes/logic_node.py +++ b/libs/labelbox/src/labelbox/schema/workflow/nodes/logic_node.py @@ -6,7 +6,7 @@ import logging from typing import Dict, List, Any, Optional, Literal -from pydantic import Field, model_validator, field_validator +from pydantic import Field, model_validator, field_validator, ConfigDict from labelbox.schema.workflow.base import BaseWorkflowNode from labelbox.schema.workflow.enums import ( @@ -27,6 +27,7 @@ class LogicNode(BaseWorkflowNode): + model_config = ConfigDict(extra="ignore") """Logic node. One or more instances possible. One input, two outputs (if/else).""" label: str = Field( diff --git a/libs/labelbox/src/labelbox/schema/workflow/nodes/review_node.py b/libs/labelbox/src/labelbox/schema/workflow/nodes/review_node.py index 4c51d38c0..3f1861cf1 100644 --- a/libs/labelbox/src/labelbox/schema/workflow/nodes/review_node.py +++ b/libs/labelbox/src/labelbox/schema/workflow/nodes/review_node.py @@ -2,7 +2,7 @@ import logging from typing import Dict, List, Any, Optional, Literal, Union -from pydantic import Field, field_validator, model_validator +from pydantic import Field, field_validator, model_validator, ConfigDict from labelbox.schema.workflow.base import BaseWorkflowNode from labelbox.schema.workflow.enums import ( @@ -69,6 +69,8 @@ class ReviewNode(BaseWorkflowNode): which default to "and" logic. This allows more flexible routing. """ + model_config = ConfigDict(extra="ignore") + label: str = Field(default="Review task", max_length=50) # For ReviewNode, filter_logic defaults to "or" filter_logic: Literal["and", "or"] = Field( diff --git a/libs/labelbox/src/labelbox/schema/workflow/nodes/rework_node.py b/libs/labelbox/src/labelbox/schema/workflow/nodes/rework_node.py index 079e4b434..5e6ddf5e8 100644 --- a/libs/labelbox/src/labelbox/schema/workflow/nodes/rework_node.py +++ b/libs/labelbox/src/labelbox/schema/workflow/nodes/rework_node.py @@ -2,7 +2,7 @@ import logging from typing import Dict, List, Any, Optional, Literal -from pydantic import Field, field_validator +from pydantic import Field, field_validator, ConfigDict from labelbox.schema.workflow.base import BaseWorkflowNode from labelbox.schema.workflow.enums import ( @@ -17,6 +17,7 @@ class ReworkNode(BaseWorkflowNode): + model_config = ConfigDict(extra="ignore") """ Terminal rework node for sending work back for corrections. diff --git a/libs/labelbox/src/labelbox/schema/workflow/workflow.py b/libs/labelbox/src/labelbox/schema/workflow/workflow.py index 4af0f9e25..55682ff58 100644 --- a/libs/labelbox/src/labelbox/schema/workflow/workflow.py +++ b/libs/labelbox/src/labelbox/schema/workflow/workflow.py @@ -283,10 +283,13 @@ def get_nodes(self) -> List[BaseWorkflowNode]: node_kwargs["config"] = node_data["config"] if "customFields" in node_data: - node_kwargs["customFields"] = node_data["customFields"] + # Handle customFields being None by converting to empty dict + custom_fields = node_data["customFields"] + if custom_fields is None: + custom_fields = {} + node_kwargs["customFields"] = custom_fields # Extract instructions from customFields if available - custom_fields = node_data["customFields"] if ( isinstance(custom_fields, dict) and "description" in custom_fields