99import traceback
1010from contextlib import contextmanager
1111from os import path
12+ from pathlib import Path
13+ from typing import Optional
1214
1315from getgauge import logger
1416from getgauge .registry import registry
1921env_dir = os .path .join (project_root , 'env' , 'default' )
2022requirements_file = os .path .join (project_root , 'requirements.txt' )
2123sys .path .append (project_root )
22- temporary_sys_path = []
24+
2325PLUGIN_JSON = 'python.json'
2426VERSION = 'version'
2527PYTHON_PROPERTIES = 'python.properties'
2628SKEL = 'skel'
2729
2830
29- def load_impls (step_impl_dirs = impl_dirs ):
31+ def load_impls (step_impl_dirs = impl_dirs , project_root = project_root ):
32+ """ project_root can be overwritten in tests! """
33+
3034 os .chdir (project_root )
35+
3136 for impl_dir in step_impl_dirs :
32- if not os .path .isdir (impl_dir ):
33- logger .error ('Cannot import step implementations. Error: {} does not exist.' .format (step_impl_dirs ))
37+
38+ resolved_impl_dir = Path (impl_dir ).resolve ()
39+ if not resolved_impl_dir .is_dir ():
40+ logger .error ('Cannot import step implementations. Error: {} does not exist.' .format (impl_dir ))
3441 logger .error ('Make sure `STEP_IMPL_DIR` env var is set to a valid directory path.' )
3542 return
36- base_dir = project_root if impl_dir .startswith (project_root ) else os .path .dirname (impl_dir )
37- # Handle multi-level relative imports
38- for _ in range (impl_dir .count ('..' )):
39- base_dir = os .path .dirname (base_dir ).replace ("/" , os .path .sep ).replace ("\\ " , os .path .sep )
40- # Add temporary sys path for relative imports that is not already added
41- if '..' in impl_dir and base_dir not in temporary_sys_path :
42- temporary_sys_path .append (base_dir )
43- _import_impl (base_dir , impl_dir )
43+
44+ base_dir = os .path .commonpath ([project_root , f"{ resolved_impl_dir } " ])
45+ logger .debug ("Base directory '{}' of '{}'" .format (base_dir , resolved_impl_dir ))
46+
47+ temporary_sys_path = None
48+ if project_root != base_dir :
49+ temporary_sys_path = base_dir
50+
51+ _import_impl (base_dir , resolved_impl_dir , temporary_sys_path )
4452
4553
4654def copy_skel_files ():
@@ -58,29 +66,33 @@ def copy_skel_files():
5866 logger .fatal ('Exception occurred while copying skel files.\n {}.' .format (traceback .format_exc ()))
5967
6068
61- def _import_impl (base_dir , step_impl_dir ):
62- for python_file in glob .glob (f"{ step_impl_dir } /**/*.py" , recursive = True ):
63- _import_file (base_dir , python_file )
69+ def _import_impl (base_dir : str , absolute_step_impl_dir : str , temporary_sys_path : Optional [str ]):
70+ for python_file in glob .glob (f"{ absolute_step_impl_dir } /**/*.py" , recursive = True ):
71+ relative_path = Path (python_file ).relative_to (base_dir )
72+ module_name = "." .join (relative_path .parts ).replace (".py" , "" )
73+ _import_file (module_name , python_file , temporary_sys_path )
6474
6575@contextmanager
66- def use_temporary_sys_path ():
76+ def use_temporary_sys_path (temporary_sys_path : str ):
6777 original_sys_path = sys .path [:]
68- sys .path .extend (temporary_sys_path )
78+ sys .path .append (temporary_sys_path )
6979 try :
7080 yield
7181 finally :
7282 sys .path = original_sys_path
7383
74- def _import_file (base_dir , file_path ):
75- rel_path = os .path .normpath (file_path .replace (base_dir + os .path .sep , '' ))
84+ def _import_file (module_name : str , file_path : str , temporary_sys_path : Optional [str ]):
7685 try :
77- module_name = os .path .splitext (rel_path .replace (os .path .sep , '.' ))[0 ]
86+ logger .debug ('Import module {} with path {}' .format (module_name , file_path ))
87+
7888 # Use temporary sys path for relative imports
79- if '..' in file_path :
80- with use_temporary_sys_path ():
89+ if temporary_sys_path is not None :
90+ logger .debug ('Import module {} using temporary sys path {}' .format (module_name , temporary_sys_path ))
91+ with use_temporary_sys_path (temporary_sys_path ):
8192 m = importlib .import_module (module_name )
8293 else :
8394 m = importlib .import_module (module_name )
95+
8496 # Get all classes in the imported module
8597 classes = inspect .getmembers (m , lambda member : inspect .isclass (member ) and member .__module__ == module_name )
8698 if len (classes ) > 0 :
@@ -92,13 +104,10 @@ def _import_file(base_dir, file_path):
92104 file_path = file_path
93105 )
94106 except :
95- logger .fatal ('Exception occurred while loading step implementations from file: {}.\n {}' .format (rel_path , traceback .format_exc ()))
107+ logger .fatal ('Exception occurred while loading step implementations from file: {}.\n {}' .format (file_path , traceback .format_exc ()))
96108
97- def update_step_registry_with_class (instance , file_path ):
109+ def update_step_registry_with_class (instance , file_path : str ):
98110 """ Inject instance in each class method (hook/step) """
99- # Resolve the absolute path from relative path
100- # Note: relative path syntax ".." can appear in between the file_path too like "<Project_Root>/../../Other_Project/src/step_impl/file.py"
101- file_path = os .path .abspath (file_path ) if ".." in str (file_path ) else file_path
102111 method_list = registry .get_all_methods_in (file_path )
103112 for info in method_list :
104113 class_methods = [x [0 ] for x in inspect .getmembers (instance , inspect .ismethod )]
0 commit comments