feat: pydantic descriptors

This commit is contained in:
TheNoxium
2025-10-06 19:13:55 +05:00
parent 8e01072ff6
commit 46d9381905
28 changed files with 589 additions and 582 deletions

View File

@@ -38,12 +38,12 @@ from .node_if_models import (
IfNodeCoreSchemaData
)
# Экспорты для моделей узла start
from .node_start_models import (
StartNodeData,
StartNodeLinks,
StartNodeCoreSchema,
StartNodeCoreSchemaData
# Экспорты для моделей узла test (ранее start)
from .node_test_models import (
TestNodeData,
TestNodeLinks,
TestNodeCoreSchema,
TestNodeCoreSchemaData
)
# Экспорты для моделей узла switch
@@ -123,11 +123,11 @@ __all__ = [
"IfNodeCoreSchema",
"IfNodeCoreSchemaData",
# Start node models
"StartNodeData",
"StartNodeLinks",
"StartNodeCoreSchema",
"StartNodeCoreSchemaData",
# Test node models
"TestNodeData",
"TestNodeLinks",
"TestNodeCoreSchema",
"TestNodeCoreSchemaData",
# Switch node models
"SwitchNodeData",

View File

@@ -0,0 +1,75 @@
from __future__ import annotations
from typing import Optional, Annotated, Literal, List
from pydantic import BaseModel, Field
class LinkPort(BaseModel):
id: str
label: str
is_addable: Optional[bool] = None
class LineElement(BaseModel):
type: Literal['line']
name: str
label: str
placeholder: Optional[str] = None
class AreaElement(BaseModel):
type: Literal['area']
name: str
label: str
placeholder: Optional[str] = None
class SelectElement(BaseModel):
type: Literal['select']
name: str
label: str
data: List[str]
default: Optional[str] = None
placeholder: Optional[str] = None
class NumberElement(BaseModel):
type: Literal['number']
name: str
label: str
placeholder: Optional[str] = None
class DateElement(BaseModel):
type: Literal['date']
name: str
label: str
placeholder: Optional[str] = None
FieldElement = Annotated[
LineElement | AreaElement | SelectElement | NumberElement | DateElement,
Field(discriminator='type'),
]
class RowElement(BaseModel):
type: Literal['row']
name: str
label: str
link_port: Optional[LinkPort] = None
elements: Optional[List[FieldElement]] = None
class FormDescriptor(BaseModel):
elements: List[RowElement]
# Descriptor-suffixed aliases for clarity (backward compatible)
LinkPortDescriptor = LinkPort
LineElementDescriptor = LineElement
AreaElementDescriptor = AreaElement
SelectElementDescriptor = SelectElement
NumberElementDescriptor = NumberElement
DateElementDescriptor = DateElement
RowDescriptor = RowElement
FormDescriptorModel = FormDescriptor

View File

@@ -1,5 +1,6 @@
from typing import Optional
from pydantic import BaseModel, Field
from .form_base_descriptor_models import FormDescriptor, RowElement, LineElement, AreaElement, SelectElement, LinkPort
class CallbackNodeData(BaseModel):
@@ -34,3 +35,83 @@ class CallbackNodeCoreSchema(BaseModel):
parent_id: Optional[int] = Field(default=None, description="ID родительского узла")
parent_port_number: Optional[int] = Field(default=None, description="Номер порта родительского узла")
data: Optional[CallbackNodeCoreSchemaData] = Field(default=None, description="Данные узла")
# Дескриптор формы узла CALLBACK
CALLBACK_FORM_DESCRIPTOR = FormDescriptor(
elements=[
RowElement(
name="uri_row",
type="row",
label="URI",
elements=[
LineElement(
name="uri",
type="line",
label="URI",
placeholder="Webhook URL",
)
],
),
RowElement(
name="method_row",
type="row",
label="Method",
elements=[
SelectElement(
name="method",
type="select",
label="Method",
data=["POST", "GET", "PUT", "PATCH", "DELETE"],
default="POST",
)
],
),
RowElement(
name="headers_row",
type="row",
label="Headers",
elements=[
AreaElement(
name="headers",
type="area",
label="Headers",
placeholder="Normalize and validate headers",
)
],
),
RowElement(
name="body_row",
type="row",
label="Body",
elements=[
AreaElement(
name="body",
type="area",
label="Data",
placeholder="Normalize values and validate JSON",
)
],
),
RowElement(
name="sync_row",
type="row",
label="Wait for request completion",
elements=[
SelectElement(
name="sync",
type="select",
label="Wait for request completion",
data=["Yes", "No"],
default="Yes",
)
],
),
RowElement(
name="then_row",
type="row",
label="Then",
link_port=LinkPort(id="then_output", label="Then"),
),
]
)

View File

@@ -1,5 +1,6 @@
from typing import Optional
from pydantic import BaseModel, Field
from .form_base_descriptor_models import FormDescriptor, RowElement, LineElement, LinkPort
class EachNodeData(BaseModel):
@@ -35,3 +36,30 @@ class EachNodeCoreSchema(BaseModel):
parent_id: Optional[int] = Field(default=None, description="ID родительского узла")
parent_port_number: Optional[int] = Field(default=None, description="Номер порта родительского узла")
data: Optional[EachNodeCoreSchemaData] = Field(default=None, description="Данные узла")
# Дескриптор формы узла EACH
EACH_FORM_DESCRIPTOR = FormDescriptor(
elements=[
RowElement(
name="list_row",
type="row",
label="List",
link_port=LinkPort(id="then_output", label="Then"),
elements=[
LineElement(
name="list",
type="line",
label="List",
placeholder="Validate list and extract next value from list",
)
],
),
RowElement(
name="else_row",
type="row",
label="Alternative path",
link_port=LinkPort(id="else_output", label="Else"),
),
]
)

View File

@@ -1,5 +1,6 @@
from typing import Optional
from pydantic import BaseModel, Field
from .form_base_descriptor_models import FormDescriptor, RowElement, AreaElement, LinkPort
class IfNodeData(BaseModel):
@@ -35,3 +36,30 @@ class IfNodeCoreSchema(BaseModel):
parent_id: Optional[int] = Field(default=None, description="ID родительского узла")
parent_port_number: Optional[int] = Field(default=None, description="Номер порта родительского узла")
data: Optional[IfNodeCoreSchemaData] = Field(default=None, description="Данные узла")
# Дескриптор формы узла IF
IF_FORM_DESCRIPTOR = FormDescriptor(
elements=[
RowElement(
name="condition_row",
type="row",
label="Condition",
link_port=LinkPort(id="then_output", label="Then"),
elements=[
AreaElement(
name="condition",
type="area",
label="Condition",
placeholder="Enter condition",
)
],
),
RowElement(
name="else_row",
type="row",
label="Alternative path",
link_port=LinkPort(id="else_output", label="Else"),
),
]
)

View File

@@ -1,5 +1,6 @@
from typing import Optional
from pydantic import BaseModel, Field
from .form_base_descriptor_models import FormDescriptor, RowElement, LineElement, AreaElement, LinkPort
class ListenNodeData(BaseModel):
@@ -36,3 +37,42 @@ class ListenNodeCoreSchema(BaseModel):
parent_id: Optional[int] = Field(default=None, description="ID родительского узла")
parent_port_number: Optional[int] = Field(default=None, description="Номер порта родительского узла")
data: Optional[ListenNodeCoreSchemaData] = Field(default=None, description="Данные узла")
# Дескриптор формы узла LISTEN
LISTEN_FORM_DESCRIPTOR = FormDescriptor(
elements=[
RowElement(
name="event_row",
type="row",
label="Event",
elements=[
LineElement(
name="event_name",
type="line",
label="Event",
placeholder="Event identifier",
)
],
),
RowElement(
name="condition_row",
type="row",
label="Condition",
elements=[
AreaElement(
name="condition",
type="area",
label="Condition",
placeholder="If P([condition(i), [AND, OR]]) == true",
)
],
),
RowElement(
name="then_row",
type="row",
label="Then",
link_port=LinkPort(id="then_output", label="Then"),
),
]
)

View File

@@ -1,5 +1,6 @@
from typing import Optional
from pydantic import BaseModel, Field
from .form_base_descriptor_models import FormDescriptor, RowElement, AreaElement, SelectElement, LinkPort
class RunNodeData(BaseModel):
@@ -34,3 +35,43 @@ class RunNodeCoreSchema(BaseModel):
parent_id: Optional[int] = Field(default=None, description="ID родительского узла")
parent_port_number: Optional[int] = Field(default=None, description="Номер порта родительского узла")
data: Optional[RunNodeCoreSchemaData] = Field(default=None, description="Данные узла")
# Дескриптор формы для узла Run
RUN_FORM_DESCRIPTOR = FormDescriptor(
elements=[
RowElement(
name="task_row",
type="row",
label="Task",
elements=[
SelectElement(
name="task",
type="select",
label="Task",
data=["send_email", "process_data", "generate_report", "validate_input", "transform_data"],
default="send_email",
)
],
),
RowElement(
name="context_row",
type="row",
label="Arguments",
elements=[
AreaElement(
name="context",
type="area",
label="Arguments",
placeholder="Build request JSON from provided environment variables",
)
],
),
RowElement(
name="then_row",
type="row",
label="Then",
link_port=LinkPort(id="then_output", label="Then"),
),
]
)

View File

@@ -1,5 +1,6 @@
from typing import Optional
from pydantic import BaseModel, Field
from .form_base_descriptor_models import FormDescriptor, RowElement, LineElement, AreaElement, LinkPort
class SetNodeData(BaseModel):
@@ -34,3 +35,43 @@ class SetNodeCoreSchema(BaseModel):
parent_id: Optional[int] = Field(default=None, description="ID родительского узла")
parent_port_number: Optional[int] = Field(default=None, description="Номер порта родительского узла")
data: Optional[SetNodeCoreSchemaData] = Field(default=None, description="Данные узла")
# Дескриптор формы узла SET
SET_FORM_DESCRIPTOR = FormDescriptor(
elements=[
RowElement(
name="variable_row",
type="row",
label="Parameter name",
elements=[
LineElement(
name="variable",
type="line",
label="Parameter name",
placeholder="Enter parameter name",
)
],
),
RowElement(
name="value_row",
type="row",
label="Value",
elements=[
AreaElement(
name="value",
type="area",
label="Value",
placeholder="Enter value",
)
],
),
RowElement(
name="link_row",
type="row",
label="Link",
link_port=LinkPort(id="then_output", label="then"),
elements=[],
),
]
)

View File

@@ -1,5 +1,6 @@
from typing import Optional
from pydantic import BaseModel, Field
from .form_base_descriptor_models import FormDescriptor, RowElement, AreaElement, LinkPort
class SwitchNodeData(BaseModel):
@@ -35,3 +36,30 @@ class SwitchNodeCoreSchema(BaseModel):
parent_id: Optional[int] = Field(default=None, description="ID родительского узла")
parent_port_number: Optional[int] = Field(default=None, description="Номер порта родительского узла")
data: Optional[SwitchNodeCoreSchemaData] = Field(default=None, description="Данные узла")
# Дескриптор формы узла SWITCH
SWITCH_FORM_DESCRIPTOR = FormDescriptor(
elements=[
RowElement(
name="switch_row",
type="row",
label="Switch condition",
link_port=LinkPort(id="case_1_output", label="case_1", is_addable=True),
elements=[
AreaElement(
name="switch_value",
type="area",
label="Switch condition",
placeholder="Enter condition",
)
],
),
RowElement(
name="default_row",
type="row",
label="Default",
link_port=LinkPort(id="default_output", label="default"),
),
]
)

View File

@@ -1,33 +1,33 @@
from typing import Optional, Dict, Any
from typing import Optional
from pydantic import BaseModel, Field
class StartNodeData(BaseModel):
class TestNodeData(BaseModel):
"""
Pydantic модель для валидации данных узла start
Pydantic модель для валидации данных узла test
"""
ps_id: Optional[int] = Field(default=None, description="ID процесса")
node_type: Optional[str] = Field(default=None, description="Тип узла")
class StartNodeLinks(BaseModel):
class TestNodeLinks(BaseModel):
"""
Pydantic модель для валидации связей узла start
Pydantic модель для валидации связей узла test
"""
# Start узел не имеет родительских связей, только исходящие
# Test узел не имеет родительских связей, только исходящие
class StartNodeCoreSchemaData(BaseModel):
class TestNodeCoreSchemaData(BaseModel):
"""
Pydantic модель для данных портов узла start
Pydantic модель для данных портов узла test
"""
node_port_number: Optional[int] = Field(default=0, description="Номер порта для перехода по Связи Then (LINK)")
class StartNodeCoreSchema(BaseModel):
class TestNodeCoreSchema(BaseModel):
"""
Pydantic модель для схемы узла start
Pydantic модель для схемы узла test
"""
ps_id: Optional[int] = Field(default=None, description="ID процесса")
node_type: Optional[str] = Field(default=None, description="Тип узла")
data: Optional[StartNodeCoreSchemaData] = Field(default=None, description="Данные узла")
data: Optional[TestNodeCoreSchemaData] = Field(default=None, description="Данные узла")

View File

@@ -1,5 +1,6 @@
from typing import Optional
from pydantic import BaseModel, Field
from .form_base_descriptor_models import FormDescriptor, RowElement, LineElement, AreaElement, LinkPort
class TriggerNodeData(BaseModel):
@@ -34,3 +35,42 @@ class TriggerNodeCoreSchema(BaseModel):
parent_id: Optional[int] = Field(default=None, description="ID родительского узла")
parent_port_number: Optional[int] = Field(default=None, description="Номер порта родительского узла")
data: Optional[TriggerNodeCoreSchemaData] = Field(default=None, description="Данные узла")
# Дескриптор формы узла TRIGGER
TRIGGER_FORM_DESCRIPTOR = FormDescriptor(
elements=[
RowElement(
name="event_row",
type="row",
label="Event",
elements=[
LineElement(
name="event_name",
type="line",
label="Event",
placeholder="Event identifier",
)
],
),
RowElement(
name="context_row",
type="row",
label="Context",
elements=[
AreaElement(
name="context",
type="area",
label="Payload",
placeholder="Build request JSON from provided environment variables",
)
],
),
RowElement(
name="then_row",
type="row",
label="Then",
link_port=LinkPort(id="then_output", label="Then"),
),
]
)

View File

@@ -1,5 +1,6 @@
from typing import Optional
from pydantic import BaseModel, Field
from .form_base_descriptor_models import FormDescriptor, RowElement, SelectElement, NumberElement, DateElement, LinkPort
class WaitNodeData(BaseModel):
@@ -34,3 +35,56 @@ class WaitNodeCoreSchema(BaseModel):
parent_id: Optional[int] = Field(default=None, description="ID родительского узла")
parent_port_number: Optional[int] = Field(default=None, description="Номер порта родительского узла")
data: Optional[WaitNodeCoreSchemaData] = Field(default=None, description="Данные узла")
# Дескриптор формы узла WAIT
WAIT_FORM_DESCRIPTOR = FormDescriptor(
elements=[
RowElement(
name="type_row",
type="row",
label="Parameter",
elements=[
SelectElement(
name="type",
type="select",
label="Parameter",
data=["seconds", "datetime"],
placeholder="Selected option",
)
],
),
RowElement(
name="wait_for_row",
type="row",
label="Time",
elements=[
NumberElement(
name="wait_for",
type="number",
label="Time",
placeholder="Compute wait time in seconds",
)
],
),
RowElement(
name="wait_datetime_row",
type="row",
label="Wait until",
elements=[
DateElement(
name="wait_datetime",
type="date",
label="Wait until",
placeholder="Compute value until specific datetime",
)
],
),
RowElement(
name="then_row",
type="row",
label="Transition",
link_port=LinkPort(id="then_output", label="then"),
),
]
)

View File

@@ -1,5 +1,6 @@
from typing import Optional
from pydantic import BaseModel, Field
from .form_base_descriptor_models import FormDescriptor, RowElement, AreaElement, LinkPort
class WhileNodeData(BaseModel):
@@ -35,3 +36,30 @@ class WhileNodeCoreSchema(BaseModel):
parent_id: Optional[int] = Field(default=None, description="ID родительского узла")
parent_port_number: Optional[int] = Field(default=None, description="Номер порта родительского узла")
data: Optional[WhileNodeCoreSchemaData] = Field(default=None, description="Данные узла")
# Дескриптор формы узла WHILE
WHILE_FORM_DESCRIPTOR = FormDescriptor(
elements=[
RowElement(
name="condition_row",
type="row",
label="Parameter",
link_port=LinkPort(id="then_output", label="Then"),
elements=[
AreaElement(
name="condition",
type="area",
label="Parameter",
placeholder="P(condition) == true",
)
],
),
RowElement(
name="else_row",
type="row",
label="Alternative path",
link_port=LinkPort(id="else_output", label="Else"),
),
]
)