Skip to content

Commit

Permalink
Refactor _Utils.py to improve module import handling
Browse files Browse the repository at this point in the history
  • Loading branch information
grongierisc committed Aug 6, 2024
1 parent 34a6614 commit 0c6fc28
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 28 deletions.
41 changes: 25 additions & 16 deletions src/iop/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ def filename_to_module(filename) -> str:
return module

@staticmethod
def migrate(filename=None,root_path=None):
def migrate(filename=None):
"""
Read the settings.py file and register all the components
settings.py file has two dictionaries:
Expand All @@ -188,17 +188,23 @@ def migrate(filename=None,root_path=None):
* key: the name of the production
* value: a dictionary containing the settings for the production
"""
try:
# if the filename is not provided
if filename is None:
settings = importlib.import_module('settings')
path = None
# try to load the settings file
if filename:
# check if the filename is absolute or relative
if os.path.isabs(filename):
path = os.path.dirname(filename)
else:
# import the settings file
settings = _Utils.import_module_from_path('settings',filename)
# get the path of the settings file
path = os.path.dirname(inspect.getfile(settings))
except ModuleNotFoundError as e:
raise ModuleNotFoundError("settings.py not found") from e
raise ValueError("The filename must be absolute")
# add the path to the system path to the beginning
sys.path.insert(0,os.path.normpath(path))
# import settings from the specified file
settings = _Utils.import_module_from_path('settings',filename)
else:
# import settings from the settings module
import settings
# get the path of the settings file
path = os.path.dirname(inspect.getfile(settings))
try:
# set the classes settings
_Utils.set_classes_settings(settings.CLASSES,path)
Expand All @@ -209,22 +215,25 @@ def migrate(filename=None,root_path=None):
_Utils.set_productions_settings(settings.PRODUCTIONS,path)
except AttributeError:
print("No productions to register")
try:
# remove the path from the system path (with or without the trailing slash)
sys.path.remove(path+'/')
sys.path.remove(path)
except ValueError:
pass

@staticmethod
def import_module_from_path(module_name, file_path):
if not os.path.isabs(file_path):
file_path = os.path.abspath(file_path)
# check is a file is persent at the path
if not os.path.isfile(file_path):
# append settings.py to the path
file_path = os.path.join(file_path,'settings.py')
raise ValueError("The file path must be absolute")

spec = importlib.util.spec_from_file_location(module_name, file_path)
if spec is None:
raise ImportError(f"Cannot find module named {module_name} at {file_path}")

module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
return module

@staticmethod
Expand Down
15 changes: 9 additions & 6 deletions src/iop/cls/IOP/Utils.cls
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,10 @@ ClassMethod GetRemoteClassInfo(
pModule As %String,
pClasspaths As %String,
ByRef pClassDetails,
ByRef pRemoteSettings) As %Status [ Internal, Private ]
ByRef pRemoteSettings) As %Status [ Internal ]
{
#dim tSC As %Status = $$$OK
#dim ex As %Exception.AbstractException
#dim tGateway As %External.Gateway
#dim tGatewayProxy As %Net.Remote.Object

Try {
if pClasspaths '="" {
Expand All @@ -120,14 +118,19 @@ ClassMethod GetRemoteClassInfo(
set onePath = $p(extraClasspaths,"|",i)
set onePath = ##class(%File).NormalizeDirectory(onePath)
if onePath?1"$$IRISHOME"1P.E set onePath = $e($system.Util.InstallDirectory(),1,*-1)_$e(onePath,11,*)
if onePath'="" && '$FIND(sys.path,onePath) do sys.path.insert(0, onePath)
if onePath'="" do ##class(IOP.Common).SetPythonPath(onePath)
}
}
;

set importlib = ##class(%SYS.Python).Import("importlib")
set builtins = ##class(%SYS.Python).Import("builtins")
set module = importlib."import_module"(pModule)
set sys = ##class(%SYS.Python).Import("sys")
// Load the module form a specific path
set spec = importlib.util."spec_from_file_location"(pModule, onePath_pModule_".py")
set module = importlib.util."module_from_spec"(spec)
do sys.modules."__setitem__"(pModule, module)
do spec.loader."exec_module"(module)
// Get the class
set class = builtins.getattr(module, pRemoteClassname)
set tClass = class."__new__"(class)

Expand Down
7 changes: 3 additions & 4 deletions src/tests/test_bench.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
import timeit
import os

import sys

class TestBenchIoP:

#before all tests
@classmethod
def setup_class(cls):
# get abspath of 'src/tests/bench'
path = os.path.abspath('src/tests/bench/settings.py')
# migrate the production
_Utils.migrate('src/tests/bench')
_Utils.migrate(path)
# stop all productions
_Director.stop_production()
# set the default production
Expand Down Expand Up @@ -43,4 +43,3 @@ def teardown_class(cls):
_Director.stop_production()
# set the default production
_Director.set_default_production('test')

19 changes: 18 additions & 1 deletion src/tests/test_iop_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,23 @@ def test_setup():
except RuntimeError as e:
assert False

def test_migrate_then_register():
# get the path of the current file
path = os.path.dirname(os.path.realpath(__file__))
# get abspath of 'src/tests/bench'
path_migrate = os.path.join(path, 'bench/settings.py')
# migrate the production
_Utils.migrate(path_migrate)
# register the component
module = 'bo'
classname = 'EmailOperation'
# join the registerFolder to the path
path = os.path.join(path, 'registerFilesIop')
overwrite = 1
iris_classname = 'UnitTest.EmailOperation'
result = _Utils.register_component(module, classname, path, overwrite, iris_classname)


def test_register_component():
module = 'bo'
classname = 'EmailOperation'
Expand Down Expand Up @@ -253,6 +270,6 @@ def test_migrate_only_classes():
# add mock_settings to sys.modules and __file__ to mock_settings
with patch.dict('sys.modules', {'settings': mock_settings}):
# Act
_Utils.migrate('/path/to/settings/settings.py')
_Utils.migrate()
# Assert
assert True # if no exception is raised, the test is ok
2 changes: 1 addition & 1 deletion src/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,6 @@ def test_migrate_only_classes():
# add mock_settings to sys.modules and __file__ to mock_settings
with patch.dict('sys.modules', {'settings': mock_settings}):
# Act
_Utils.migrate('/path/to/settings/settings.py')
_Utils.migrate()
# Assert
assert True # if no exception is raised, the test is ok

0 comments on commit 0c6fc28

Please sign in to comment.