Coverage for gws-app/gws/lib/importer/__init__.py: 21%
47 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-17 01:37 +0200
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-17 01:37 +0200
1"""Handle dynamic imports"""
3import sys
4import os
5import importlib
7import gws
10class Error(gws.Error):
11 pass
14def import_from_path(path, base_dir=gws.c.APP_DIR):
15 abs_path = _abs_path(path, base_dir)
16 if not os.path.isfile(abs_path):
17 raise Error(f'{abs_path!r}: not found')
19 if abs_path.startswith(base_dir):
20 # our own module, import relatively to base_dir
21 return _do_import(abs_path, base_dir)
23 # plugin module, import relatively to the bottom-most "namespace" dir (without __init__)
25 dirs = abs_path.strip('/').split('/')
26 dirs.pop()
28 for n in range(len(dirs), 0, -1):
29 ns_dir = '/' + '/'.join(dirs[:n])
30 if not os.path.isfile(ns_dir + '/__init__.py'):
31 return _do_import(abs_path, ns_dir)
33 raise Error(f'{abs_path!r}: cannot locate a base directory')
36def _abs_path(path, base_dir):
37 if not os.path.isabs(path):
38 path = os.path.join(base_dir, path)
39 path = os.path.normpath(path)
40 if os.path.isdir(path):
41 path += '/__init__.py'
42 return path
45def _do_import(abs_path, base_dir):
46 mod_name = _module_name(abs_path[len(base_dir):])
48 if mod_name in sys.modules:
49 mpath = getattr(sys.modules[mod_name], '__file__', None)
50 if mpath != abs_path:
51 raise Error(f'{abs_path!r}: overwriting {mod_name!r} from {mpath!r}')
52 return sys.modules[mod_name]
54 gws.log.debug(f'import: {abs_path=} {mod_name=} {base_dir=}')
56 if base_dir not in sys.path:
57 sys.path.insert(0, base_dir)
59 try:
60 return importlib.import_module(mod_name)
61 except Exception as exc:
62 raise Error(f'{abs_path!r}: import failed') from exc
65def _module_name(path):
66 parts = path.strip('/').split('/')
67 if parts[-1] == '__init__.py':
68 parts.pop()
69 elif parts[-1].endswith('.py'):
70 parts[-1] = parts[-1][:-3]
71 return '.'.join(parts)