1212# License for the specific language governing permissions and limitations
1313# under the License.
1414
15+ import dill
1516import functools
1617import os
1718import requests
18-
19+ import types
1920
2021from fdk .application import errors
2122
@@ -69,10 +70,124 @@ def wrapper(*args, **kwargs):
6970 return wrapper
7071
7172
72- def fn_route (fn_image = None , fn_type = None ,
73- fn_memory = 256 , fn_format = None ,
74- fn_timeout = 60 , fn_idle_timeout = 200 ,
75- fn_method = "GET" ):
73+ def fn (fn_type = None , fn_timeout = 60 , fn_idle_timeout = 200 , fn_memory = 256 ,
74+ dependencies = None ):
75+ """
76+ Runs Python's function on general purpose Fn function
77+
78+
79+ What it does?
80+
81+ Decorator does following:
82+ - collects dat for Fn route and creates it
83+ - when function called, that function transforms
84+ into byte array (Pickle) then gets sent to general
85+ purpose Fn Python3 function
86+ - each external dependency (3rd-party libs)
87+ that are required for func gets transformed
88+ into byte array (Pickle)
89+
90+ It means that functions does't run locally but on Fn.
91+
92+ How is it different from other Python FDK functions?
93+
94+ - This function works with serialized Python callable objects via wire.
95+ Each function supplied with set of external dependencies that are
96+ represented as serialized functions, no matter if they are module-level,
97+ class-level Python objects
98+
99+ :param fn_type: Fn function call type
100+ :type fn_type: str
101+ :param fn_timeout: Fn function call timeout
102+ :type fn_timeout: int
103+ :param fn_idle_timeout: Fn function call idle timeout
104+ :type fn_idle_timeout: int
105+ :param fn_memory: Fn function memory limit
106+ :type fn_memory: int
107+ :param dependencies: Python's function 3rd-party callable dependencies
108+ :type dependencies: dict
109+ :return:
110+ """
111+ fn_method = "POST"
112+ fn_image = "denismakogon/python3-fn-gpi:0.0.1"
113+ fn_format = "http"
114+ dependencies = dependencies if dependencies else {}
115+
116+ def ext_wrapper (action ):
117+ @functools .wraps (action )
118+ def inner_wrapper (* f_args , ** f_kwargs ):
119+ fn_api_url = os .environ .get ("API_URL" )
120+ requests .get (fn_api_url ).raise_for_status ()
121+ self = f_args [0 ]
122+ fn_path = action .__name__ .lower ()
123+ if not hasattr (action , "__path_created" ):
124+ fn_routes_url = "{}/v1/apps/{}/routes" .format (
125+ fn_api_url , self .__class__ .__name__ .lower ())
126+ resp = requests .post (fn_routes_url , json = {
127+ "route" : {
128+ "path" : "/{}" .format (fn_path ),
129+ "image" : fn_image ,
130+ "memory" : fn_memory if fn_memory else 256 ,
131+ "type" : fn_type if fn_type else "sync" ,
132+ "format" : fn_format if fn_format else "default" ,
133+ "timeout" : fn_timeout if fn_timeout else 60 ,
134+ "idle_timeout" : (fn_idle_timeout if
135+ fn_idle_timeout else 120 ),
136+ },
137+ })
138+
139+ try :
140+ resp .raise_for_status ()
141+ except requests .HTTPError :
142+ resp .close ()
143+ return Exception (resp .content )
144+
145+ setattr (action , "__path_created" , True )
146+
147+ fn_path = action .__name__ .lower ()
148+ fn_exec_url = "{}/r/{}/{}" .format (
149+ fn_api_url , self .__class__ .__name__ .lower (), fn_path )
150+
151+ action_in_bytes = dill .dumps (action , recurse = True )
152+ self_in_bytes = dill .dumps (self , recurse = True )
153+
154+ for name , method in dependencies .items ():
155+ dependencies [name ] = list (dill .dumps (method , recurse = True ))
156+
157+ f_kwargs .update (dependencies = dependencies )
158+ req = requests .Request (
159+ method = fn_method , url = fn_exec_url ,
160+ json = {
161+ "is_coroutine" : isinstance (action , types .CoroutineType ),
162+ "action" : list (action_in_bytes ),
163+ "self" : list (self_in_bytes ),
164+ "args" : f_args [1 :],
165+ "kwargs" : f_kwargs ,
166+ }
167+ )
168+ session = requests .Session ()
169+ resp = session .send (req .prepare ())
170+
171+ try :
172+ resp .raise_for_status ()
173+ except requests .HTTPError :
174+ resp .close ()
175+ return None , errors .FnError (
176+ "{}/{}" .format (self .__class__ .__name__ .lower (), fn_path ),
177+ resp .content )
178+
179+ resp .close ()
180+ return dill .loads (resp .content ), None
181+
182+ return inner_wrapper
183+
184+ return ext_wrapper
185+
186+
187+ def with_fn (fn_image = None , fn_type = None ,
188+ fn_memory = 256 , fn_format = None ,
189+ fn_timeout = 60 , fn_idle_timeout = 200 ,
190+ fn_method = "GET" ):
76191 """
77192 Sets up Fn app route based on parameters given above
78193 :param fn_image: Docker image
@@ -99,6 +214,9 @@ def inner_wrapper(*f_args, **f_kwargs):
99214 requests .get (fn_api_url ).raise_for_status ()
100215 self = f_args [0 ]
101216 fn_path = action .__name__ .lower ()
217+ # TODO(xxx): hate code duplicates but extracting common function
218+ # to create a route breaks dill routine somehow
219+ # need to figure out how and fix that!
102220 if not hasattr (action , "__path_created" ):
103221 fn_routes_url = "{}/v1/apps/{}/routes" .format (
104222 fn_api_url , self .__class__ .__name__ .lower ())
@@ -119,10 +237,11 @@ def inner_wrapper(*f_args, **f_kwargs):
119237 resp .raise_for_status ()
120238 except requests .HTTPError :
121239 resp .close ()
122- return None , Exception (resp .content )
240+ return Exception (resp .content )
123241
124242 setattr (action , "__path_created" , True )
125243
244+ fn_path = action .__name__ .lower ()
126245 fn_exec_url = "{}/r/{}/{}" .format (
127246 fn_api_url , self .__class__ .__name__ .lower (), fn_path )
128247 req = requests .Request (method = fn_method ,
0 commit comments