13
13
# See the License for the specific language governing permissions and
14
14
# limitations under the License.
15
15
16
- """Module for the calling proper action endpoints based on events received at action server endpoint """
16
+ """Module for the calling proper action endpoints based on events received at action server endpoint"""
17
17
18
18
import importlib .util
19
19
import inspect
20
20
import logging
21
21
import os
22
+ from pathlib import Path
22
23
from typing import Any , Dict , List , Optional , Tuple , Union
23
24
24
25
from langchain .chains .base import Chain
@@ -53,36 +54,39 @@ def __init__(
53
54
54
55
if load_all_actions :
55
56
# TODO: check for better way to find actions dir path or use constants.py
57
+ current_file_path = Path (__file__ ).resolve ()
58
+ parent_directory_path = current_file_path .parents [1 ]
56
59
57
60
# First, we load all actions from the actions folder
58
- self .load_actions_from_path (os .path .join (os .path .dirname (__file__ ), ".." ))
61
+ self .load_actions_from_path (parent_directory_path )
62
+ # self.load_actions_from_path(os.path.join(os.path.dirname(__file__), ".."))
59
63
60
64
# Next, we load all actions from the library folder
61
- library_path = os . path . join ( os . path . dirname ( __file__ ), "../ library")
65
+ library_path = parent_directory_path / " library"
62
66
63
67
for root , dirs , files in os .walk (library_path ):
64
68
# We only load the actions if there is an `actions` sub-folder or
65
69
# an `actions.py` file.
66
70
if "actions" in dirs or "actions.py" in files :
67
- self .load_actions_from_path (root )
71
+ self .load_actions_from_path (Path ( root ) )
68
72
69
73
# Next, we load all actions from the current working directory
70
74
# TODO: add support for an explicit ACTIONS_PATH
71
- self .load_actions_from_path (os . getcwd ())
75
+ self .load_actions_from_path (Path . cwd ())
72
76
73
77
# Last, but not least, if there was a config path, we try to load actions
74
78
# from there as well.
75
79
if config_path :
76
80
config_path = config_path .split ("," )
77
81
for path in config_path :
78
- self .load_actions_from_path (path )
82
+ self .load_actions_from_path (Path ( path . strip ()) )
79
83
80
84
# If there are any imported paths, we load the actions from there as well.
81
85
if import_paths :
82
86
for import_path in import_paths :
83
- self .load_actions_from_path (import_path )
87
+ self .load_actions_from_path (Path ( import_path . strip ()) )
84
88
85
- log .info (f"Registered Actions: { self ._registered_actions } " )
89
+ log .info (f"Registered Actions :: { sorted ( self ._registered_actions . keys ()) } " )
86
90
log .info ("Action dispatcher initialized" )
87
91
88
92
@property
@@ -94,16 +98,17 @@ def registered_actions(self):
94
98
"""
95
99
return self ._registered_actions
96
100
97
- def load_actions_from_path (self , path : str ):
101
+ def load_actions_from_path (self , path : Path ):
98
102
"""Loads all actions from the specified path.
99
103
100
104
This method loads all actions from the `actions.py` file if it exists and
101
105
all actions inside the `actions` folder if it exists.
102
106
103
107
Args:
104
108
path (str): A string representing the path from which to load actions.
109
+
105
110
"""
106
- actions_path = os . path . join ( path , "actions" )
111
+ actions_path = path / "actions"
107
112
if os .path .exists (actions_path ):
108
113
self ._registered_actions .update (self ._find_actions (actions_path ))
109
114
@@ -257,38 +262,45 @@ def _load_actions_from_module(filepath: str):
257
262
action_objects = {}
258
263
filename = os .path .basename (filepath )
259
264
265
+ if not os .path .isfile (filepath ):
266
+ log .error (f"{ filepath } does not exist or is not a file." )
267
+ log .error (f"Failed to load actions from { filename } ." )
268
+ return action_objects
269
+
260
270
try :
261
271
log .debug (f"Analyzing file { filename } " )
262
272
# Import the module from the file
263
273
264
274
spec = importlib .util .spec_from_file_location (filename , filepath )
275
+ if spec is None :
276
+ log .error (f"Failed to create a module spec from { filepath } ." )
277
+ return action_objects
278
+
265
279
module = importlib .util .module_from_spec (spec )
266
280
spec .loader .exec_module (module )
267
281
268
282
# Loop through all members in the module and check for the `@action` decorator
269
283
# If class has action decorator is_action class member is true
270
284
for name , obj in inspect .getmembers (module ):
271
- if inspect .isfunction (obj ) and hasattr (obj , "action_meta" ):
272
- log .info (f"Adding { obj .__name__ } to actions" )
273
- action_objects [obj .action_meta ["name" ]] = obj
274
-
275
- if inspect .isclass (obj ) and hasattr (obj , "action_meta" ):
285
+ if (inspect .isfunction (obj ) or inspect .isclass (obj )) and hasattr (
286
+ obj , "action_meta"
287
+ ):
276
288
try :
277
289
action_objects [obj .action_meta ["name" ]] = obj
278
290
log .info (f"Added { obj .action_meta ['name' ]} to actions" )
279
291
except Exception as e :
280
- log .debug (
292
+ log .error (
281
293
f"Failed to register { obj .action_meta ['name' ]} in action dispatcher due to exception { e } "
282
294
)
283
295
except Exception as e :
284
- log .debug (
285
- f"Failed to register { filename } in action dispatcher due to exception { e } "
296
+ relative_filepath = Path (module .__file__ ).relative_to (Path .cwd ())
297
+ log .error (
298
+ f"Failed to register { filename } from { relative_filepath } in action dispatcher due to exception: { e } "
286
299
)
287
300
288
301
return action_objects
289
302
290
- @staticmethod
291
- def _find_actions (directory ) -> Dict :
303
+ def _find_actions (self , directory ) -> Dict :
292
304
"""Loop through all the subdirectories and check for the class with @action
293
305
decorator and add in action_classes dict.
294
306
@@ -301,15 +313,31 @@ def _find_actions(directory) -> Dict:
301
313
action_objects = {}
302
314
303
315
if not os .path .exists (directory ):
316
+ log .debug (f"_find_actions: { directory } does not exist." )
304
317
return action_objects
305
318
306
319
# Loop through all files in the directory and its subdirectories
307
320
for root , dirs , files in os .walk (directory ):
308
321
for filename in files :
309
322
if filename .endswith (".py" ):
310
323
filepath = os .path .join (root , filename )
311
- action_objects .update (
312
- ActionDispatcher ._load_actions_from_module (filepath )
313
- )
324
+ if is_action_file (filepath ):
325
+ action_objects .update (
326
+ ActionDispatcher ._load_actions_from_module (filepath )
327
+ )
328
+ if not action_objects :
329
+ log .debug (f"No actions found in { directory } " )
330
+ log .exception (f"No actions found in the directory { directory } ." )
314
331
315
332
return action_objects
333
+
334
+
335
+ def is_action_file (filepath ):
336
+ """Heuristics for determining if a Python file can have actions or not.
337
+
338
+ Currently, it only excludes the `__init__.py files.
339
+ """
340
+ if "__init__.py" in filepath :
341
+ return False
342
+
343
+ return True
0 commit comments