33#
44# Copyright © 2019, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
55# SPDX-License-Identifier: Apache-2.0
6+ import datetime
67
8+ from ..core import PagedItemIterator
79from .service import Service
810
911
1012class Workflow (Service ):
1113 """The Workflow API provides basic resources for list, prompt details,
1214 and running workflow processes.
15+
16+ Warnings
17+ --------
18+ Note that this service is intentionally not included in the sasctl documentation.
19+ As of 2022.09 this service is not publicly documented on developer.sas.com and is
20+ only partially implemented here in order to provide functionality for the Model
21+ Manager service. All methods included in this service should be treated as
22+ experimental and are subject to change without notice.
23+
1324 """
1425
1526 _SERVICE_ROOT = "/workflow"
1627
17- def list_definitions (self , include_enabled = True , include_disabled = False ):
28+ @classmethod
29+ def list_definitions (cls , include_enabled = True , include_disabled = False ):
1830 """List workflow definitions.
1931
2032 Parameters
@@ -40,9 +52,16 @@ def list_definitions(self, include_enabled=True, include_disabled=False):
4052 return []
4153
4254 # Header required to prevent 400 ERROR Bad Request
43- return self .get (url , headers = {"Accept-Language" : "en-US" })
55+ results = cls .get (url , headers = {"Accept-Language" : "en-US" })
4456
45- def list_enabled_definitions (self ):
57+ if results is None :
58+ return []
59+ if isinstance (results , (list , PagedItemIterator )):
60+ return results
61+ return [results ]
62+
63+ @classmethod
64+ def list_enabled_definitions (cls ):
4665 """List process definitions that are currently enabled.
4766
4867 Returns
@@ -51,9 +70,10 @@ def list_enabled_definitions(self):
5170 The list of definitions.
5271
5372 """
54- return self .list_definitions (include_enabled = True , include_disabled = False )
73+ return cls .list_definitions (include_enabled = True , include_disabled = False )
5574
56- def list_workflow_prompt (self , name ):
75+ @classmethod
76+ def list_workflow_prompt (cls , name ):
5777 """List prompt Workflow Processes Definitions.
5878
5979 Parameters
@@ -68,7 +88,7 @@ def list_workflow_prompt(self, name):
6888
6989 """
7090
71- ret = self ._find_specific_workflow (name )
91+ ret = cls ._find_specific_workflow (name )
7292 if ret is None :
7393 raise ValueError ("No Workflow enabled for %s name or id." % name )
7494
@@ -78,44 +98,86 @@ def list_workflow_prompt(self, name):
7898 # No prompt inputs on workflow
7999 return None
80100
81- def run_workflow_definition (self , name , input = None ):
101+ @classmethod
102+ def run_workflow_definition (cls , name , prompts = None ):
82103 """Runs specific Workflow Processes Definitions.
83104
84105 Parameters
85106 ----------
86107 name : str
87108 Name or ID of an enabled workflow to execute
88- input : dict, optional
89- Input values for the workflow for initial workflow prompt
109+ prompts : dict, optional
110+ Input values to provide for the initial workflow prompts. Should be
111+ specified as name:value pairs.
90112
91113 Returns
92114 -------
93115 RestObj
94116 The executing workflow
95117
96118 """
97-
98- workflow = self ._find_specific_workflow (name )
119+ workflow = cls ._find_specific_workflow (name )
99120 if workflow is None :
100121 raise ValueError ("No Workflow enabled for %s name or id." % name )
101122
102- if input is None :
103- return self .post (
123+ if prompts is None :
124+ return cls .post (
104125 "/processes?definitionId=" + workflow ["id" ],
105126 headers = {"Content-Type" : "application/vnd.sas.workflow.variables+json" },
106127 )
107- if isinstance (input , dict ):
108- return self .post (
128+ if isinstance (prompts , dict ):
129+
130+ variables = []
131+
132+ # For each prompt defined in the workflow, check if a value was provided.
133+ for prompt in workflow .prompts :
134+ if prompt ["variableName" ] in prompts :
135+ name = prompt ["variableName" ]
136+ value = prompts [name ]
137+
138+ if type (value ) == datetime .datetime :
139+ # NOTE: do not use isinstance() to compare types as
140+ # datetime.date will also evaluate as True.
141+
142+ # Explicitly convert to local time zone if not set.
143+ if value .tzinfo is None :
144+ try :
145+ value = value .astimezone ()
146+ except OSError :
147+ # On Windows pre-1970 dates will cause an issue.
148+ # See https://bugs.python.org/issue36759
149+ pass
150+
151+ if value .tzinfo is None :
152+ # Failed to convert to local time. Have to just assume it's UTC.
153+ # Example: 2023-01-25T13:49:40.726162Z
154+ value = value .isoformat () + "Z"
155+ else :
156+ # Example: 2023-01-25T13:49:40.726162-05:00
157+ value = value .isoformat ()
158+
159+ variables .append (
160+ {
161+ "name" : name ,
162+ "value" : value ,
163+ "scope" : "local" ,
164+ "type" : prompt ["variableType" ],
165+ "version" : prompt ["version" ],
166+ }
167+ )
168+
169+ return cls .post (
109170 "/processes?definitionId=" + workflow ["id" ],
110- json = input ,
171+ json = { "variables" : variables } ,
111172 headers = {"Content-Type" : "application/vnd.sas.workflow.variables+json" },
112173 )
113174
114- def _find_specific_workflow (self , name ):
175+ @classmethod
176+ def _find_specific_workflow (cls , name ):
115177 # Internal helper method
116178 # Finds a workflow with the name (can be a name or id)
117179 # Returns a dict objects of the workflow
118- listendef = self .list_enabled_definitions ()
180+ listendef = cls .list_enabled_definitions ()
119181 for tmp in listendef :
120182 if tmp ["name" ] == name :
121183 return tmp
0 commit comments