# """ # Snippet draft workflow variable APIs. # Mirrors console app routes under /apps/.../workflows/draft/variables for snippet scope, # using CustomizedSnippet.id as WorkflowDraftVariable.app_id (same invariant as snippet execution). # Snippet workflows do not expose system variables (`node_id == sys`) or conversation variables # (`node_id == conversation`): paginated list queries exclude those rows; single-variable GET/PATCH/DELETE/reset # reject them; `GET .../system-variables` and `GET .../conversation-variables` return empty lists for API parity. # Other routes mirror `workflow_draft_variable` app APIs under `/snippets/...`. # """ # from collections.abc import Callable # from functools import wraps # from typing import Any # from flask import Response, request # from flask_restx import Resource, marshal, marshal_with # from sqlalchemy.orm import Session # from controllers.console import console_ns # from controllers.console.app.error import DraftWorkflowNotExist # from controllers.console.app.workflow_draft_variable import ( # WorkflowDraftVariableListQuery, # WorkflowDraftVariableUpdatePayload, # _ensure_variable_access, # _file_access_controller, # validate_node_id, # workflow_draft_variable_list_model, # workflow_draft_variable_list_without_value_model, # workflow_draft_variable_model, # ) # from controllers.console.snippets.snippet_workflow import get_snippet # from controllers.console.wraps import account_initialization_required, edit_permission_required, setup_required # from controllers.web.error import InvalidArgumentError, NotFoundError # from core.workflow.variable_prefixes import CONVERSATION_VARIABLE_NODE_ID, SYSTEM_VARIABLE_NODE_ID # from extensions.ext_database import db # from factories.file_factory import build_from_mapping, build_from_mappings # from factories.variable_factory import build_segment_with_type # from graphon.variables.types import SegmentType # from libs.login import current_user, login_required # from models.snippet import CustomizedSnippet # from models.workflow import WorkflowDraftVariable # from services.snippet_service import SnippetService # from services.workflow_draft_variable_service import WorkflowDraftVariableList, WorkflowDraftVariableService # _SNIPPET_EXCLUDED_DRAFT_VARIABLE_NODE_IDS: frozenset[str] = frozenset( # {SYSTEM_VARIABLE_NODE_ID, CONVERSATION_VARIABLE_NODE_ID} # ) # def _ensure_snippet_draft_variable_row_allowed( # *, # variable: WorkflowDraftVariable, # variable_id: str, # ) -> None: # """Snippet scope only supports canvas-node draft variables; treat sys/conversation rows as not found.""" # if variable.node_id in _SNIPPET_EXCLUDED_DRAFT_VARIABLE_NODE_IDS: # raise NotFoundError(description=f"variable not found, id={variable_id}") # def _snippet_draft_var_prerequisite[**P, R](f: Callable[P, R]) -> Callable[P, R]: # """Setup, auth, snippet resolution, and tenant edit permission (same stack as snippet workflow APIs).""" # @setup_required # @login_required # @account_initialization_required # @get_snippet # @edit_permission_required # @wraps(f) # def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: # return f(*args, **kwargs) # return wrapper # @console_ns.route("/snippets//workflows/draft/variables") # class SnippetWorkflowVariableCollectionApi(Resource): # @console_ns.expect(console_ns.models[WorkflowDraftVariableListQuery.__name__]) # @console_ns.doc("get_snippet_workflow_variables") # @console_ns.doc(description="List draft workflow variables without values (paginated, snippet scope)") # @console_ns.response( # 200, # "Workflow variables retrieved successfully", # workflow_draft_variable_list_without_value_model, # ) # @_snippet_draft_var_prerequisite # @marshal_with(workflow_draft_variable_list_without_value_model) # def get(self, snippet: CustomizedSnippet) -> WorkflowDraftVariableList: # args = WorkflowDraftVariableListQuery.model_validate(request.args.to_dict(flat=True)) # type: ignore # snippet_service = SnippetService() # if snippet_service.get_draft_workflow(snippet=snippet) is None: # raise DraftWorkflowNotExist() # with Session(bind=db.engine, expire_on_commit=False) as session: # draft_var_srv = WorkflowDraftVariableService(session=session) # workflow_vars = draft_var_srv.list_variables_without_values( # app_id=snippet.id, # page=args.page, # limit=args.limit, # user_id=current_user.id, # exclude_node_ids=_SNIPPET_EXCLUDED_DRAFT_VARIABLE_NODE_IDS, # ) # return workflow_vars # @console_ns.doc("delete_snippet_workflow_variables") # @console_ns.doc(description="Delete all draft workflow variables for the current user (snippet scope)") # @console_ns.response(204, "Workflow variables deleted successfully") # @_snippet_draft_var_prerequisite # def delete(self, snippet: CustomizedSnippet) -> Response: # draft_var_srv = WorkflowDraftVariableService(session=db.session()) # draft_var_srv.delete_user_workflow_variables(snippet.id, user_id=current_user.id) # db.session.commit() # return Response("", 204) # @console_ns.route("/snippets//workflows/draft/nodes//variables") # class SnippetNodeVariableCollectionApi(Resource): # @console_ns.doc("get_snippet_node_variables") # @console_ns.doc(description="Get variables for a specific node (snippet draft workflow)") # @console_ns.response(200, "Node variables retrieved successfully", workflow_draft_variable_list_model) # @_snippet_draft_var_prerequisite # @marshal_with(workflow_draft_variable_list_model) # def get(self, snippet: CustomizedSnippet, node_id: str) -> WorkflowDraftVariableList: # validate_node_id(node_id) # with Session(bind=db.engine, expire_on_commit=False) as session: # draft_var_srv = WorkflowDraftVariableService(session=session) # node_vars = draft_var_srv.list_node_variables(snippet.id, node_id, user_id=current_user.id) # return node_vars # @console_ns.doc("delete_snippet_node_variables") # @console_ns.doc(description="Delete all variables for a specific node (snippet draft workflow)") # @console_ns.response(204, "Node variables deleted successfully") # @_snippet_draft_var_prerequisite # def delete(self, snippet: CustomizedSnippet, node_id: str) -> Response: # validate_node_id(node_id) # srv = WorkflowDraftVariableService(db.session()) # srv.delete_node_variables(snippet.id, node_id, user_id=current_user.id) # db.session.commit() # return Response("", 204) # @console_ns.route("/snippets//workflows/draft/variables/") # class SnippetVariableApi(Resource): # @console_ns.doc("get_snippet_workflow_variable") # @console_ns.doc(description="Get a specific draft workflow variable (snippet scope)") # @console_ns.response(200, "Variable retrieved successfully", workflow_draft_variable_model) # @console_ns.response(404, "Variable not found") # @_snippet_draft_var_prerequisite # @marshal_with(workflow_draft_variable_model) # def get(self, snippet: CustomizedSnippet, variable_id: str) -> WorkflowDraftVariable: # draft_var_srv = WorkflowDraftVariableService(session=db.session()) # variable = _ensure_variable_access( # variable=draft_var_srv.get_variable(variable_id=variable_id), # app_id=snippet.id, # variable_id=variable_id, # ) # _ensure_snippet_draft_variable_row_allowed(variable=variable, variable_id=variable_id) # return variable # @console_ns.doc("update_snippet_workflow_variable") # @console_ns.doc(description="Update a draft workflow variable (snippet scope)") # @console_ns.expect(console_ns.models[WorkflowDraftVariableUpdatePayload.__name__]) # @console_ns.response(200, "Variable updated successfully", workflow_draft_variable_model) # @console_ns.response(404, "Variable not found") # @_snippet_draft_var_prerequisite # @marshal_with(workflow_draft_variable_model) # def patch(self, snippet: CustomizedSnippet, variable_id: str) -> WorkflowDraftVariable: # draft_var_srv = WorkflowDraftVariableService(session=db.session()) # args_model = WorkflowDraftVariableUpdatePayload.model_validate(console_ns.payload or {}) # variable = _ensure_variable_access( # variable=draft_var_srv.get_variable(variable_id=variable_id), # app_id=snippet.id, # variable_id=variable_id, # ) # _ensure_snippet_draft_variable_row_allowed(variable=variable, variable_id=variable_id) # new_name = args_model.name # raw_value = args_model.value # if new_name is None and raw_value is None: # return variable # new_value = None # if raw_value is not None: # if variable.value_type == SegmentType.FILE: # if not isinstance(raw_value, dict): # raise InvalidArgumentError(description=f"expected dict for file, got {type(raw_value)}") # raw_value = build_from_mapping( # mapping=raw_value, # tenant_id=snippet.tenant_id, # access_controller=_file_access_controller, # ) # elif variable.value_type == SegmentType.ARRAY_FILE: # if not isinstance(raw_value, list): # raise InvalidArgumentError(description=f"expected list for files, got {type(raw_value)}") # if len(raw_value) > 0 and not isinstance(raw_value[0], dict): # raise InvalidArgumentError(description=f"expected dict for files[0], got {type(raw_value)}") # raw_value = build_from_mappings( # mappings=raw_value, # tenant_id=snippet.tenant_id, # access_controller=_file_access_controller, # ) # new_value = build_segment_with_type(variable.value_type, raw_value) # draft_var_srv.update_variable(variable, name=new_name, value=new_value) # db.session.commit() # return variable # @console_ns.doc("delete_snippet_workflow_variable") # @console_ns.doc(description="Delete a draft workflow variable (snippet scope)") # @console_ns.response(204, "Variable deleted successfully") # @console_ns.response(404, "Variable not found") # @_snippet_draft_var_prerequisite # def delete(self, snippet: CustomizedSnippet, variable_id: str) -> Response: # draft_var_srv = WorkflowDraftVariableService(session=db.session()) # variable = _ensure_variable_access( # variable=draft_var_srv.get_variable(variable_id=variable_id), # app_id=snippet.id, # variable_id=variable_id, # ) # _ensure_snippet_draft_variable_row_allowed(variable=variable, variable_id=variable_id) # draft_var_srv.delete_variable(variable) # db.session.commit() # return Response("", 204) # @console_ns.route("/snippets//workflows/draft/variables//reset") # class SnippetVariableResetApi(Resource): # @console_ns.doc("reset_snippet_workflow_variable") # @console_ns.doc(description="Reset a draft workflow variable to its default value (snippet scope)") # @console_ns.response(200, "Variable reset successfully", workflow_draft_variable_model) # @console_ns.response(204, "Variable reset (no content)") # @console_ns.response(404, "Variable not found") # @_snippet_draft_var_prerequisite # def put(self, snippet: CustomizedSnippet, variable_id: str) -> Response | Any: # draft_var_srv = WorkflowDraftVariableService(session=db.session()) # snippet_service = SnippetService() # draft_workflow = snippet_service.get_draft_workflow(snippet=snippet) # if draft_workflow is None: # raise NotFoundError( # f"Draft workflow not found, snippet_id={snippet.id}", # ) # variable = _ensure_variable_access( # variable=draft_var_srv.get_variable(variable_id=variable_id), # app_id=snippet.id, # variable_id=variable_id, # ) # _ensure_snippet_draft_variable_row_allowed(variable=variable, variable_id=variable_id) # resetted = draft_var_srv.reset_variable(draft_workflow, variable) # db.session.commit() # if resetted is None: # return Response("", 204) # return marshal(resetted, workflow_draft_variable_model) # @console_ns.route("/snippets//workflows/draft/conversation-variables") # class SnippetConversationVariableCollectionApi(Resource): # @console_ns.doc("get_snippet_conversation_variables") # @console_ns.doc( # description="Conversation variables are not used in snippet workflows; returns an empty list for API parity" # ) # @console_ns.response(200, "Conversation variables retrieved successfully", workflow_draft_variable_list_model) # @_snippet_draft_var_prerequisite # @marshal_with(workflow_draft_variable_list_model) # def get(self, snippet: CustomizedSnippet) -> WorkflowDraftVariableList: # return WorkflowDraftVariableList(variables=[]) # @console_ns.route("/snippets//workflows/draft/system-variables") # class SnippetSystemVariableCollectionApi(Resource): # @console_ns.doc("get_snippet_system_variables") # @console_ns.doc( # description="System variables are not used in snippet workflows; returns an empty list for API parity" # ) # @console_ns.response(200, "System variables retrieved successfully", workflow_draft_variable_list_model) # @_snippet_draft_var_prerequisite # @marshal_with(workflow_draft_variable_list_model) # def get(self, snippet: CustomizedSnippet) -> WorkflowDraftVariableList: # return WorkflowDraftVariableList(variables=[]) # @console_ns.route("/snippets//workflows/draft/environment-variables") # class SnippetEnvironmentVariableCollectionApi(Resource): # @console_ns.doc("get_snippet_environment_variables") # @console_ns.doc(description="Get environment variables from snippet draft workflow graph") # @console_ns.response(200, "Environment variables retrieved successfully") # @console_ns.response(404, "Draft workflow not found") # @_snippet_draft_var_prerequisite # def get(self, snippet: CustomizedSnippet) -> dict[str, list[dict[str, Any]]]: # snippet_service = SnippetService() # workflow = snippet_service.get_draft_workflow(snippet=snippet) # if workflow is None: # raise DraftWorkflowNotExist() # env_vars_list: list[dict[str, Any]] = [] # for v in workflow.environment_variables: # env_vars_list.append( # { # "id": v.id, # "type": "env", # "name": v.name, # "description": v.description, # "selector": v.selector, # "value_type": v.value_type.exposed_type().value, # "value": v.value, # "edited": False, # "visible": True, # "editable": True, # } # ) # return {"items": env_vars_list}