From e572b2f5d9e5646b61bcf6899dfcad1afa175d15 Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Fri, 5 Sep 2025 13:08:03 -0700 Subject: [PATCH 1/6] new clawutil.util module containing fullpath_import --- src/python/clawutil/util.py | 44 +++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/python/clawutil/util.py diff --git a/src/python/clawutil/util.py b/src/python/clawutil/util.py new file mode 100644 index 0000000..e97b6fc --- /dev/null +++ b/src/python/clawutil/util.py @@ -0,0 +1,44 @@ +r""" +clawutil.util Module `$CLAW/clawutil/src/python/clawutil/util.py` + +Provides general utility functions. + +:Functions: + + - fullpath_import: import a module using its full path + +""" + +def fullpath_import(fullpath, verbose=True): + """ + Return a module imported from a full path name, e.g. if you have + a personal enhanced version of the geoclaw topotools module at + /full/path/to/topotools.py then instead of: + + from clawpack.geoclaw import topotools + + use: + + from clawpack.clawutil.util import fullpath_import + topotools = fullpath_import('/full/path/to/topotools.py') + + Relative imports also work, e.g. + + topotools = fullpath_import('../topotools.py') + + To reload the module if you make changes to it, use this function again + (rather than using importlib.reload). + """ + + import os, sys, importlib + fname = os.path.split(fullpath)[1] + modname = os.path.splitext(fname)[0] + spec = importlib.util.spec_from_file_location(modname, fullpath) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + sys.modules[modname] = module + if verbose: + print('loaded module from file: ',module.__file__) + return module + + From ba9750153f9b33cad949119ae7533b46eae486a7 Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Fri, 5 Sep 2025 14:40:17 -0700 Subject: [PATCH 2/6] move import statement out of function --- src/python/clawutil/util.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/python/clawutil/util.py b/src/python/clawutil/util.py index e97b6fc..499f260 100644 --- a/src/python/clawutil/util.py +++ b/src/python/clawutil/util.py @@ -9,6 +9,8 @@ """ +import os, sys, importlib + def fullpath_import(fullpath, verbose=True): """ Return a module imported from a full path name, e.g. if you have @@ -30,7 +32,6 @@ def fullpath_import(fullpath, verbose=True): (rather than using importlib.reload). """ - import os, sys, importlib fname = os.path.split(fullpath)[1] modname = os.path.splitext(fname)[0] spec = importlib.util.spec_from_file_location(modname, fullpath) From e749ddc2c186d8cb6deb29593df1b7e3e28b0ee3 Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Sun, 7 Sep 2025 11:44:12 -0700 Subject: [PATCH 3/6] switch to using pathlib in fullpath_import, so fullpath input can be a Path object --- src/python/clawutil/util.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/python/clawutil/util.py b/src/python/clawutil/util.py index 499f260..67a21a4 100644 --- a/src/python/clawutil/util.py +++ b/src/python/clawutil/util.py @@ -24,22 +24,28 @@ def fullpath_import(fullpath, verbose=True): from clawpack.clawutil.util import fullpath_import topotools = fullpath_import('/full/path/to/topotools.py') - Relative imports also work, e.g. + Relative imports also work, e.g. topotools = fullpath_import('../topotools.py') - + To reload the module if you make changes to it, use this function again (rather than using importlib.reload). + + Input `fullpath` can also be a `pathlib.Path` object instead of a string. """ - fname = os.path.split(fullpath)[1] - modname = os.path.splitext(fname)[0] - spec = importlib.util.spec_from_file_location(modname, fullpath) + from pathlib import Path + fullPath = Path(fullpath) # convert to a pathlib.Path object if not already + fullPath = fullPath.resolve() # replace relative path by absolute + assert fullPath.suffix == '.py', '*** Expecting path to .py file' + + fname = fullPath.name # should be modname.py + modname = fullPath.stem # without extension + + spec = importlib.util.spec_from_file_location(modname, fullPath) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) sys.modules[modname] = module if verbose: print('loaded module from file: ',module.__file__) return module - - From 95f997ced7f1f045def887eae54892a219250d42 Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Sun, 7 Sep 2025 12:17:06 -0700 Subject: [PATCH 4/6] allow fullpath to start with environment variable and resolve if possible --- src/python/clawutil/util.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/python/clawutil/util.py b/src/python/clawutil/util.py index 67a21a4..b98218b 100644 --- a/src/python/clawutil/util.py +++ b/src/python/clawutil/util.py @@ -10,6 +10,8 @@ """ import os, sys, importlib +from pathlib import Path + def fullpath_import(fullpath, verbose=True): """ @@ -32,9 +34,28 @@ def fullpath_import(fullpath, verbose=True): (rather than using importlib.reload). Input `fullpath` can also be a `pathlib.Path` object instead of a string. + + If `fullpath` is a string that starts with `$`, then the path is assumed + to start with an environment variable and this is resolved, if possible, + from `os.environ`. For example, this works: + + setrun_file = '$CLAW/amrclaw/examples/advection_2d_swirl/setrun.py' + setrun = util.fullpath_import(setrun_file) + """ - from pathlib import Path + if type(fullpath) is str and fullpath[0] == '$': + # path appears to be relative to an environment variable: + env_var = fullpath.split('/')[0][1:] + try: + path1 = os.environ[env_var] + except: + #raise + raise ValueError('fullpath appears to start with environment ' \ + + 'variable, but %s not in os.environ' % env_var) + fullpath = fullpath.replace('$%s' % env_var, path1) + + fullPath = Path(fullpath) # convert to a pathlib.Path object if not already fullPath = fullPath.resolve() # replace relative path by absolute assert fullPath.suffix == '.py', '*** Expecting path to .py file' From 589b181d2995591937fb3743b3e5c649ce080e5e Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Tue, 9 Sep 2025 15:57:13 -0700 Subject: [PATCH 5/6] improved fullpath_import using expandvars and expanduser --- src/python/clawutil/util.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/python/clawutil/util.py b/src/python/clawutil/util.py index b98218b..2a1dc5d 100644 --- a/src/python/clawutil/util.py +++ b/src/python/clawutil/util.py @@ -42,22 +42,16 @@ def fullpath_import(fullpath, verbose=True): setrun_file = '$CLAW/amrclaw/examples/advection_2d_swirl/setrun.py' setrun = util.fullpath_import(setrun_file) + It also expands `~` and `~user` constructs and resolves symlinks. """ - if type(fullpath) is str and fullpath[0] == '$': - # path appears to be relative to an environment variable: - env_var = fullpath.split('/')[0][1:] - try: - path1 = os.environ[env_var] - except: - #raise - raise ValueError('fullpath appears to start with environment ' \ - + 'variable, but %s not in os.environ' % env_var) - fullpath = fullpath.replace('$%s' % env_var, path1) + # expand any environment variables: + fullpath = os.path.expandvars(fullpath) + # convert to a Path object if necessary, expand user prefix `~`, + # and convert to absolute path, resolving any symlinks: + fullPath = Path(fullpath).expanduser().resolve() - fullPath = Path(fullpath) # convert to a pathlib.Path object if not already - fullPath = fullPath.resolve() # replace relative path by absolute assert fullPath.suffix == '.py', '*** Expecting path to .py file' fname = fullPath.name # should be modname.py From c3be67e7a134dc89f2ff63bafbc8a90e734194a6 Mon Sep 17 00:00:00 2001 From: Randy LeVeque Date: Tue, 9 Sep 2025 16:08:15 -0700 Subject: [PATCH 6/6] change default verbose to False --- src/python/clawutil/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/clawutil/util.py b/src/python/clawutil/util.py index 2a1dc5d..38c2eff 100644 --- a/src/python/clawutil/util.py +++ b/src/python/clawutil/util.py @@ -13,7 +13,7 @@ from pathlib import Path -def fullpath_import(fullpath, verbose=True): +def fullpath_import(fullpath, verbose=False): """ Return a module imported from a full path name, e.g. if you have a personal enhanced version of the geoclaw topotools module at