python-misc-utils 0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. py_misc_utils/__init__.py +0 -0
  2. py_misc_utils/abs_timeout.py +12 -0
  3. py_misc_utils/alog.py +311 -0
  4. py_misc_utils/app_main.py +179 -0
  5. py_misc_utils/archive_streamer.py +112 -0
  6. py_misc_utils/assert_checks.py +118 -0
  7. py_misc_utils/ast_utils.py +121 -0
  8. py_misc_utils/async_manager.py +189 -0
  9. py_misc_utils/break_control.py +63 -0
  10. py_misc_utils/buffered_iterator.py +35 -0
  11. py_misc_utils/cached_file.py +507 -0
  12. py_misc_utils/call_limiter.py +26 -0
  13. py_misc_utils/call_result_selector.py +13 -0
  14. py_misc_utils/cleanups.py +85 -0
  15. py_misc_utils/cmd.py +97 -0
  16. py_misc_utils/compression.py +116 -0
  17. py_misc_utils/cond_waiter.py +13 -0
  18. py_misc_utils/context_base.py +18 -0
  19. py_misc_utils/context_managers.py +67 -0
  20. py_misc_utils/core_utils.py +577 -0
  21. py_misc_utils/daemon_process.py +252 -0
  22. py_misc_utils/data_cache.py +46 -0
  23. py_misc_utils/date_utils.py +90 -0
  24. py_misc_utils/debug.py +24 -0
  25. py_misc_utils/dyn_modules.py +50 -0
  26. py_misc_utils/dynamod.py +103 -0
  27. py_misc_utils/env_config.py +35 -0
  28. py_misc_utils/executor.py +239 -0
  29. py_misc_utils/file_overwrite.py +29 -0
  30. py_misc_utils/fin_wrap.py +77 -0
  31. py_misc_utils/fp_utils.py +47 -0
  32. py_misc_utils/fs/__init__.py +0 -0
  33. py_misc_utils/fs/file_fs.py +127 -0
  34. py_misc_utils/fs/ftp_fs.py +242 -0
  35. py_misc_utils/fs/gcs_fs.py +196 -0
  36. py_misc_utils/fs/http_fs.py +241 -0
  37. py_misc_utils/fs/s3_fs.py +417 -0
  38. py_misc_utils/fs_base.py +133 -0
  39. py_misc_utils/fs_utils.py +207 -0
  40. py_misc_utils/gcs_fs.py +169 -0
  41. py_misc_utils/gen_indices.py +54 -0
  42. py_misc_utils/gfs.py +371 -0
  43. py_misc_utils/git_repo.py +77 -0
  44. py_misc_utils/global_namespace.py +110 -0
  45. py_misc_utils/http_async_fetcher.py +139 -0
  46. py_misc_utils/http_server.py +196 -0
  47. py_misc_utils/http_utils.py +143 -0
  48. py_misc_utils/img_utils.py +20 -0
  49. py_misc_utils/infix_op.py +20 -0
  50. py_misc_utils/inspect_utils.py +205 -0
  51. py_misc_utils/iostream.py +21 -0
  52. py_misc_utils/iter_file.py +117 -0
  53. py_misc_utils/key_wrap.py +46 -0
  54. py_misc_utils/lazy_import.py +25 -0
  55. py_misc_utils/lockfile.py +164 -0
  56. py_misc_utils/mem_size.py +64 -0
  57. py_misc_utils/mirror_from.py +72 -0
  58. py_misc_utils/mmap.py +16 -0
  59. py_misc_utils/module_utils.py +196 -0
  60. py_misc_utils/moving_average.py +19 -0
  61. py_misc_utils/msgpack_streamer.py +26 -0
  62. py_misc_utils/multi_wait.py +24 -0
  63. py_misc_utils/multiprocessing.py +102 -0
  64. py_misc_utils/named_array.py +224 -0
  65. py_misc_utils/no_break.py +46 -0
  66. py_misc_utils/no_except.py +32 -0
  67. py_misc_utils/np_ml_framework.py +184 -0
  68. py_misc_utils/np_utils.py +346 -0
  69. py_misc_utils/ntuple_utils.py +38 -0
  70. py_misc_utils/num_utils.py +54 -0
  71. py_misc_utils/obj.py +73 -0
  72. py_misc_utils/object_cache.py +100 -0
  73. py_misc_utils/object_tracker.py +88 -0
  74. py_misc_utils/ordered_set.py +71 -0
  75. py_misc_utils/osfd.py +27 -0
  76. py_misc_utils/packet.py +22 -0
  77. py_misc_utils/parquet_streamer.py +69 -0
  78. py_misc_utils/pd_utils.py +254 -0
  79. py_misc_utils/periodic_task.py +61 -0
  80. py_misc_utils/pickle_wrap.py +121 -0
  81. py_misc_utils/pipeline.py +98 -0
  82. py_misc_utils/remap_pickle.py +50 -0
  83. py_misc_utils/resource_manager.py +155 -0
  84. py_misc_utils/rnd_utils.py +56 -0
  85. py_misc_utils/run_once.py +19 -0
  86. py_misc_utils/scheduler.py +135 -0
  87. py_misc_utils/select_params.py +300 -0
  88. py_misc_utils/signal.py +141 -0
  89. py_misc_utils/skl_utils.py +270 -0
  90. py_misc_utils/split.py +147 -0
  91. py_misc_utils/state.py +53 -0
  92. py_misc_utils/std_module.py +56 -0
  93. py_misc_utils/stream_dataframe.py +176 -0
  94. py_misc_utils/streamed_file.py +144 -0
  95. py_misc_utils/tempdir.py +79 -0
  96. py_misc_utils/template_replace.py +51 -0
  97. py_misc_utils/tensor_stream.py +269 -0
  98. py_misc_utils/thread_context.py +33 -0
  99. py_misc_utils/throttle.py +30 -0
  100. py_misc_utils/time_trigger.py +18 -0
  101. py_misc_utils/timegen.py +11 -0
  102. py_misc_utils/traceback.py +49 -0
  103. py_misc_utils/tracking_executor.py +91 -0
  104. py_misc_utils/transform_array.py +42 -0
  105. py_misc_utils/uncompress.py +35 -0
  106. py_misc_utils/url_fetcher.py +157 -0
  107. py_misc_utils/utils.py +538 -0
  108. py_misc_utils/varint.py +50 -0
  109. py_misc_utils/virt_array.py +52 -0
  110. py_misc_utils/weak_call.py +33 -0
  111. py_misc_utils/work_results.py +100 -0
  112. py_misc_utils/writeback_file.py +43 -0
  113. python_misc_utils-0.2.dist-info/METADATA +36 -0
  114. python_misc_utils-0.2.dist-info/RECORD +117 -0
  115. python_misc_utils-0.2.dist-info/WHEEL +5 -0
  116. python_misc_utils-0.2.dist-info/licenses/LICENSE +13 -0
  117. python_misc_utils-0.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,117 @@
1
+ import bisect
2
+ import os
3
+
4
+ from . import alog
5
+ from . import assert_checks as tas
6
+
7
+
8
+ class IterFile:
9
+
10
+ def __init__(self, data_gen):
11
+ offset, blocks, offsets = 0, [], []
12
+ for block in data_gen:
13
+ blocks.append(block)
14
+ offsets.append(offset)
15
+ offset += len(block)
16
+
17
+ offsets.append(offset)
18
+
19
+ self._blocks = tuple(blocks)
20
+ self._offsets = tuple(offsets)
21
+ self._size = offset
22
+ self._offset = 0
23
+ self._block = None
24
+ self._block_offset = 0
25
+
26
+ def close(self):
27
+ self._blocks = None
28
+
29
+ @property
30
+ def closed(self):
31
+ return self._blocks is None
32
+
33
+ def seek(self, pos, whence=os.SEEK_SET):
34
+ if whence == os.SEEK_SET:
35
+ offset = pos
36
+ elif whence == os.SEEK_CUR:
37
+ offset = self._offset + pos
38
+ elif whence == os.SEEK_END:
39
+ offset = self._size + pos
40
+ else:
41
+ alog.xraise(ValueError, f'Invalid seek mode: {whence}')
42
+
43
+ tas.check_le(offset, self._size, msg=f'Offset out of range')
44
+ tas.check_ge(offset, 0, msg=f'Offset out of range')
45
+
46
+ self._offset = offset
47
+
48
+ return offset
49
+
50
+ def tell(self):
51
+ return self._offset
52
+
53
+ def _ensure_buffer(self, offset):
54
+ boffset = offset - self._block_offset
55
+ if self._block is None or boffset < 0 or boffset >= len(self._block):
56
+ pos = bisect.bisect(self._offsets, offset) - 1
57
+ if pos >= len(self._blocks):
58
+ self._block_offset = self._size
59
+ self._block = b''
60
+ else:
61
+ self._block_offset = self._offsets[pos]
62
+ self._block = memoryview(self._blocks[pos])
63
+
64
+ boffset = offset - self._block_offset
65
+
66
+ return boffset
67
+
68
+ def read(self, size=-1):
69
+ if size < 0:
70
+ rsize = self._size - self._offset
71
+ else:
72
+ rsize = min(size, self._size - self._offset)
73
+
74
+ parts = []
75
+ while rsize > 0:
76
+ boffset = self._ensure_buffer(self._offset)
77
+
78
+ csize = min(rsize, len(self._block) - boffset)
79
+ parts.append(self._block[boffset: boffset + csize])
80
+ self._offset += csize
81
+ rsize -= csize
82
+
83
+ return b''.join(parts)
84
+
85
+ def read1(self, size=-1):
86
+ return self.read(size=size)
87
+
88
+ def peek(self, size=0):
89
+ if size > 0:
90
+ boffset = self._ensure_buffer(self._offset)
91
+ csize = min(size, len(self._block) - boffset)
92
+
93
+ return self._block[boffset: boffset + csize].tobytes()
94
+
95
+ return b''
96
+
97
+ def flush(self):
98
+ pass
99
+
100
+ def readable(self):
101
+ return not self.closed
102
+
103
+ def seekable(self):
104
+ return not self.closed
105
+
106
+ def writable(self):
107
+ return False
108
+
109
+ def __enter__(self):
110
+ return self
111
+
112
+ def __exit__(self, *exc):
113
+ self.close()
114
+
115
+ return False
116
+
117
+
@@ -0,0 +1,46 @@
1
+ from . import utils as ut
2
+
3
+ _KW_TEMPLATE = '''
4
+ class $CLASS:
5
+
6
+ def __init__(self, $KEY, **kwargs):
7
+ self.$KEY = $KEY
8
+ for k, v in kwargs.items():
9
+ setattr(self, k, v)
10
+
11
+ def __lt__(self, other):
12
+ return self.$KEY < other.$KEY
13
+
14
+ def __le__(self, other):
15
+ return self.$KEY <= other.$KEY
16
+
17
+ def __gt__(self, other):
18
+ return self.$KEY > other.$KEY
19
+
20
+ def __ge__(self, other):
21
+ return self.$KEY >= other.$KEY
22
+
23
+ def __eq__(self, other):
24
+ return self.$KEY == other.$KEY
25
+
26
+ def __ne__(self, other):
27
+ return self.$KEY != other.$KEY
28
+
29
+ def __hash__(self):
30
+ return hash(self.$KEY)
31
+
32
+ def __repr__(self):
33
+ args = self.__dict__.copy()
34
+ args.pop('$KEY', None)
35
+
36
+ return f'$KEY=[{self.$KEY}] : {args}'
37
+ '''
38
+
39
+
40
+ def key_wrap(cname, key_name):
41
+ replaces = dict(CLASS=cname, KEY=key_name)
42
+
43
+ results, = ut.compile(_KW_TEMPLATE, cname, vals=replaces)
44
+
45
+ return results
46
+
@@ -0,0 +1,25 @@
1
+ import importlib.util
2
+ import inspect
3
+ import re
4
+ import sys
5
+
6
+ from . import traceback as tb
7
+
8
+
9
+ def lazy_import(modname, package=None):
10
+ if package is not None and re.match(r'\.+$', package):
11
+ parent_frame = tb.get_frame(1)
12
+ parent_packages = inspect.getmodule(parent_frame).__package__.split('.')
13
+ package_path = parent_packages[: len(parent_packages) - len(package) + 1]
14
+ modname = '.'.join(package_path + [modname])
15
+ package = None
16
+
17
+ spec = importlib.util.find_spec(modname, package=package)
18
+ loader = importlib.util.LazyLoader(spec.loader)
19
+ spec.loader = loader
20
+ module = importlib.util.module_from_spec(spec)
21
+ sys.modules[modname] = module
22
+ loader.exec_module(module)
23
+
24
+ return module
25
+
@@ -0,0 +1,164 @@
1
+ import hashlib
2
+ import os
3
+ import psutil
4
+ import time
5
+ import yaml
6
+
7
+ from . import alog
8
+ from . import assert_checks as tas
9
+ from . import fs_utils as fsu
10
+ from . import obj
11
+ from . import osfd
12
+ from . import rnd_utils as rngu
13
+ from . import tempdir as tmpd
14
+
15
+
16
+ _LOCKDIR = tmpd.fastfs_dir(name='.locks')
17
+
18
+ def _lockfile(name):
19
+ lhash = hashlib.sha1(name.encode()).hexdigest()
20
+
21
+ return os.path.join(_LOCKDIR, lhash)
22
+
23
+
24
+ class Meta(obj.Obj):
25
+ pass
26
+
27
+
28
+ _CMDLINE = list(psutil.Process().cmdline())
29
+ _ACQUIRE_TIMEOUT = float(os.getenv('LOCKF_AQTIMEO', 0.5))
30
+ _CHECK_TIMEOUT = float(os.getenv('LOCKF_CKTIMEO', 5.0))
31
+
32
+ class LockFile:
33
+
34
+ def __init__(self, name, acquire_timeout=None, check_timeout=None):
35
+ acquire_timeout = _ACQUIRE_TIMEOUT if acquire_timeout is None else acquire_timeout
36
+ check_timeout = _CHECK_TIMEOUT if check_timeout is None else check_timeout
37
+
38
+ self._name = name
39
+ self._lockfile = _lockfile(name)
40
+ self._acquire_timeout = rngu.uniform(acquire_timeout, pct=0.2)
41
+ self._check_timeout = rngu.uniform(check_timeout, pct=0.2)
42
+
43
+ def _mkmeta(self):
44
+ tag = dict(pid=os.getpid(), cmdline=_CMDLINE, time=time.time())
45
+ stag = yaml.dump(tag, default_flow_style=False)
46
+
47
+ return stag.encode()
48
+
49
+ def _parse_meta(self, data):
50
+ if data:
51
+ try:
52
+ tag = yaml.safe_load(data.decode())
53
+
54
+ return Meta(**tag)
55
+ except:
56
+ pass
57
+
58
+ def _alive_pid(self, meta):
59
+ is_alive = False
60
+ if psutil.pid_exists(meta.pid):
61
+ try:
62
+ proc = psutil.Process(meta.pid)
63
+ cmdline = list(proc.cmdline())
64
+
65
+ is_alive = cmdline == meta.cmdline and proc.create_time() <= meta.time
66
+ except psutil.NoSuchProcess:
67
+ pass
68
+
69
+ if not is_alive:
70
+ alog.warning(f'Process {meta.pid} ({meta.cmdline}) holding lock on ' \
71
+ f'{self._name} seems vanished')
72
+
73
+ return is_alive
74
+
75
+ def acquire(self, timeout=None):
76
+ quit_time = timeout + time.time() if timeout is not None else None
77
+ check_time = time.time() + self._check_timeout
78
+ while True:
79
+ try:
80
+ with osfd.OsFd(self._lockfile, os.O_WRONLY | os.O_CREAT | os.O_EXCL) as fd:
81
+ os.write(fd, self._mkmeta())
82
+
83
+ return True
84
+ except OSError:
85
+ time.sleep(self._acquire_timeout)
86
+
87
+ now = time.time()
88
+ if now >= check_time:
89
+ check_time = now + self._check_timeout
90
+ meta = self._locking_meta()
91
+ if meta is None or not self._alive_pid(meta):
92
+ if self._try_force_lock(meta):
93
+ return True
94
+
95
+ if quit_time is not None and now >= quit_time:
96
+ alog.debug(f'Giving up on lock {self._name} after {timeout} seconds')
97
+ return False
98
+
99
+ def release(self):
100
+ meta = self._locking_meta()
101
+ if meta is not None:
102
+ if meta.pid == os.getpid() and meta.cmdline == _CMDLINE:
103
+ try:
104
+ os.remove(self._lockfile)
105
+ return True
106
+ except OSError:
107
+ pass
108
+ else:
109
+ alog.warning(f'Trying to release lock on {self._name} by pid {os.getpid()} but ' \
110
+ f'it was held by {meta.pid} ({meta.cmdline})')
111
+ else:
112
+ alog.warning(f'Trying to release lock on {self._name} from pid {os.getpid()} but ' \
113
+ f'no lock metadata was found')
114
+
115
+ return False
116
+
117
+ def _try_force_lock(self, meta):
118
+ if meta is not None:
119
+ alog.warning(f'Trying to override gone process {meta.pid} on {self._name}')
120
+
121
+ upath = f'{self._lockfile}.ENFORCER'
122
+ created = result = False
123
+ try:
124
+ with osfd.OsFd(upath, os.O_WRONLY | os.O_CREAT | os.O_EXCL) as fd:
125
+ created = True
126
+
127
+ with osfd.OsFd(self._lockfile, os.O_RDWR) as fd:
128
+ data = fsu.readall(fd)
129
+
130
+ lmeta = self._parse_meta(data)
131
+ if lmeta is None or (meta is not None and lmeta == meta):
132
+ os.lseek(fd, 0, os.SEEK_SET)
133
+ os.truncate(fd, 0)
134
+ os.write(fd, self._mkmeta())
135
+ result = True
136
+
137
+ alog.info(f'Successfull override on {self._name}')
138
+ except OSError:
139
+ pass
140
+ finally:
141
+ if created:
142
+ os.remove(upath)
143
+
144
+ return result
145
+
146
+ def _locking_meta(self):
147
+ try:
148
+ with osfd.OsFd(self._lockfile, os.O_RDONLY) as fd:
149
+ data = fsu.readall(fd)
150
+
151
+ return self._parse_meta(data)
152
+ except OSError:
153
+ pass
154
+
155
+ def __enter__(self):
156
+ self.acquire()
157
+
158
+ return self
159
+
160
+ def __exit__(self, *exc):
161
+ self.release()
162
+
163
+ return False
164
+
@@ -0,0 +1,64 @@
1
+ import array
2
+ import sys
3
+ import threading
4
+
5
+ import numpy as np
6
+
7
+ from . import core_utils as cu
8
+
9
+
10
+ _LOCK = threading.Lock()
11
+ _TYPES_SIZE = dict()
12
+
13
+ def register(otype, sizefn):
14
+ with _LOCK:
15
+ _TYPES_SIZE[otype] = sizefn
16
+
17
+
18
+ def _get_sizefn(otype):
19
+ with _LOCK:
20
+ return _TYPES_SIZE.get(otype)
21
+
22
+
23
+ def std_sizefn(obj):
24
+ return sys.getsizeof(obj)
25
+
26
+
27
+ _SIZE_AWARE = {
28
+ float,
29
+ int,
30
+ str,
31
+ bytes,
32
+ bytearray,
33
+ array.array,
34
+ np.ndarray,
35
+ }
36
+
37
+ def _get_size(obj, seen):
38
+ oid = id(obj)
39
+ if oid in seen:
40
+ return 0
41
+ seen.add(oid)
42
+
43
+ otype = type(obj)
44
+ if sizefn := _get_sizefn(otype):
45
+ size = sizefn(obj)
46
+ else:
47
+ size = sys.getsizeof(obj)
48
+ if otype not in _SIZE_AWARE:
49
+ if cu.isdict(obj):
50
+ size += sum(_get_size(v, seen) + _get_size(k, seen) for k, v in obj.items())
51
+ elif ustg := getattr(obj, 'untyped_storage', None):
52
+ # Handle PyTorch tensors.
53
+ size += sys.getsizeof(ustg())
54
+ elif hasattr(obj, '__dict__'):
55
+ size += _get_size(obj.__dict__, seen)
56
+ elif hasattr(obj, '__iter__'):
57
+ size += sum(_get_size(x, seen) for x in obj)
58
+
59
+ return size
60
+
61
+
62
+ def get_size(obj):
63
+ return _get_size(obj, set())
64
+
@@ -0,0 +1,72 @@
1
+ import functools
2
+
3
+
4
+ # Copied from fsstat.
5
+ # Used to forward calls from a class, to a memeber of the class itself.
6
+ # Example:
7
+ # @mirror_from(
8
+ # 'stream',
9
+ # [
10
+ # 'read',
11
+ # 'seek',
12
+ # 'write',
13
+ # ],
14
+ # )
15
+ # class MyStream:
16
+ # def __init__(self, stream):
17
+ # self.stream = stream
18
+ #
19
+ def mirror_from(origin_name, methods):
20
+
21
+ def origin_getter(method, obj):
22
+ origin = getattr(obj, origin_name)
23
+
24
+ return getattr(origin, method)
25
+
26
+ def wrapper(cls):
27
+ for method in methods:
28
+ wrapped_method = functools.partial(origin_getter, method)
29
+ setattr(cls, method, property(wrapped_method))
30
+
31
+ return cls
32
+
33
+ return wrapper
34
+
35
+
36
+ def mirror_attributes(src, dest, attributes):
37
+ for attr in attributes:
38
+ setattr(dest, attr, getattr(src, attr))
39
+
40
+
41
+ def _mirrored_field(name):
42
+ return f'_mirrored_{name}'
43
+
44
+
45
+ def mirror_all(src, dest, excludes=(), includes=(), name=None):
46
+ excludes = set(excludes)
47
+ for attr in dir(dest):
48
+ if not attr.startswith('_'):
49
+ excludes.add(attr)
50
+
51
+ includes = set(includes)
52
+ mirrored = []
53
+ for attr in dir(src):
54
+ if (not attr.startswith('_') or attr in includes) and attr not in excludes:
55
+ setattr(dest, attr, getattr(src, attr))
56
+ mirrored.append(attr)
57
+
58
+ if name is not None:
59
+ setattr(dest, _mirrored_field(name), tuple(mirrored))
60
+
61
+ return tuple(mirrored)
62
+
63
+
64
+ def unmirror(dest, attributes=None, name=None):
65
+ if name is not None:
66
+ mfield = _mirrored_field(name)
67
+ attributes = getattr(dest, mfield, None)
68
+ delattr(dest, mfield)
69
+
70
+ for attr in attributes or ():
71
+ delattr(dest, attr)
72
+
py_misc_utils/mmap.py ADDED
@@ -0,0 +1,16 @@
1
+ import mmap
2
+ import os
3
+
4
+ from . import alog
5
+ from . import osfd
6
+
7
+
8
+ def file_view(path):
9
+ with osfd.OsFd(path, os.O_RDONLY) as fd:
10
+ mm = mmap.mmap(fd, 0, access=mmap.ACCESS_READ)
11
+
12
+ # We can close the fd, but we cannot close the mmap. When the memoryview
13
+ # will be garbage collected, the buffer protocol used by memoryview will
14
+ # decref the mmap object which will be automatically released.
15
+ return memoryview(mm)
16
+
@@ -0,0 +1,196 @@
1
+ import functools
2
+ import importlib
3
+ import importlib.util
4
+ import inspect
5
+ import os
6
+ import sys
7
+
8
+ from . import alog
9
+ from . import assert_checks as tas
10
+ from . import core_utils as cu
11
+ from . import fs_utils as fsu
12
+ from . import utils as ut
13
+
14
+
15
+ _PYINIT = '__init__.py'
16
+
17
+
18
+ def split_module_name(name):
19
+ pos = name.rfind('.')
20
+ if pos < 0:
21
+ return '', name
22
+
23
+ return name[: pos], name[pos + 1:]
24
+
25
+
26
+ def add_sys_path(path):
27
+ cu.append_if_missing(sys.path, path)
28
+
29
+
30
+ def find_module_parent(path):
31
+ parts = fsu.path_split(path)
32
+ modname = fsu.drop_ext(parts.pop(), '.py')
33
+
34
+ for i in range(len(parts)):
35
+ ipath = os.path.join(*parts[: i + 1], _PYINIT)
36
+ if os.path.isfile(ipath):
37
+ return ipath, parts[i + 1:] + [modname]
38
+
39
+
40
+ def find_module(path):
41
+ apath = os.path.abspath(path)
42
+
43
+ for modname, module in sys.modules.items():
44
+ mpath = module_file(module)
45
+ if mpath is not None and mpath == apath:
46
+ return modname, module
47
+
48
+
49
+ def install_module(modname, module):
50
+ xmodule = sys.modules.get(modname)
51
+ if xmodule is not None:
52
+ modfile, xmodfile = module_file(module), module_file(xmodule)
53
+ tas.check_eq(modfile, xmodfile,
54
+ msg=f'Module "{modname}" already defined at "{xmodfile}"')
55
+ else:
56
+ alog.debug(f'Installing module at "{module_file(module)}" with "{modname}" name')
57
+ sys.modules[modname] = module
58
+
59
+
60
+ def load_module(path, modname=None, install=None, add_syspath=None):
61
+ install = install or True
62
+ add_syspath = add_syspath or False
63
+
64
+ name_and_module = find_module(path)
65
+ if name_and_module is not None:
66
+ fmodname, module = name_and_module
67
+ alog.debug(f'Found existing module "{fmodname}" for "{path}"')
68
+ if install and modname is not None and modname != fmodname:
69
+ install_module(modname, module)
70
+
71
+ return module
72
+
73
+ apath = os.path.abspath(path)
74
+
75
+ parent = find_module_parent(apath) if os.path.basename(apath) != _PYINIT else None
76
+ if parent is not None:
77
+ init_path, mod_path = parent
78
+
79
+ parent_module = load_module(init_path,
80
+ install=True,
81
+ add_syspath=add_syspath)
82
+
83
+ for i in range(len(mod_path) - 1):
84
+ partial = mod_path[: i + 1]
85
+ ipath = os.path.join(os.path.dirname(parent_module.__file__), *partial, _PYINIT)
86
+ if os.path.isfile(ipath):
87
+ imodname = parent_module.__name__ + '.' + '.'.join(partial)
88
+ load_module(ipath, modname=imodname, install=True)
89
+
90
+ imodname = parent_module.__name__ + '.' + '.'.join(mod_path)
91
+ alog.debug(f'Importing sub-module "{imodname}"')
92
+ module = importlib.import_module(imodname)
93
+ else:
94
+ pathdir = syspath = os.path.dirname(apath)
95
+
96
+ if modname is None:
97
+ modname = fsu.drop_ext(os.path.basename(apath), '.py')
98
+ if modname == '__init__':
99
+ syspath, modname = os.path.split(pathdir)
100
+
101
+ alog.debug(f'Installing module "{apath}" with name "{modname}" (syspath is "{syspath}")')
102
+
103
+ if add_syspath:
104
+ add_sys_path(syspath)
105
+
106
+ modspec = importlib.util.spec_from_file_location(
107
+ modname, apath,
108
+ submodule_search_locations=[pathdir])
109
+ module = importlib.util.module_from_spec(modspec)
110
+
111
+ if install:
112
+ install_module(modname, module)
113
+
114
+ modspec.loader.exec_module(module)
115
+
116
+ return module
117
+
118
+
119
+ def import_module(name_or_path,
120
+ modname=None,
121
+ install=None,
122
+ add_syspath=None,
123
+ package=None):
124
+ if os.path.isfile(name_or_path):
125
+ module = load_module(name_or_path,
126
+ modname=modname,
127
+ install=install,
128
+ add_syspath=add_syspath)
129
+ else:
130
+ alog.debug(f'Loading module "{name_or_path}')
131
+ module = importlib.import_module(name_or_path, package=package)
132
+
133
+ if modname is not None and install in (True, None):
134
+ install_module(modname, module)
135
+
136
+ return module
137
+
138
+
139
+ def _module_getter(dot_path, obj):
140
+ for name in dot_path.split('.'):
141
+ try:
142
+ obj = getattr(obj, name)
143
+ except AttributeError:
144
+ if inspect.ismodule(obj):
145
+ obj = importlib.import_module(obj.__name__ + '.' + name)
146
+ else:
147
+ raise
148
+
149
+ return obj
150
+
151
+
152
+ def module_getter(dot_path):
153
+ return functools.partial(_module_getter, dot_path)
154
+
155
+
156
+ def import_module_names(modname, names=None):
157
+ if names is None:
158
+ npos = modname.find('.')
159
+ tas.check_gt(npos, 0)
160
+ names = [modname[npos + 1:]]
161
+ modname = modname[: npos]
162
+ else:
163
+ names = ut.expand_strings(names)
164
+
165
+ module = importlib.import_module(modname)
166
+
167
+ return tuple(module_getter(name)(module) for name in names)
168
+
169
+
170
+ def rel_import_module(path, ppath,
171
+ modname=None,
172
+ install=None,
173
+ add_syspath=None):
174
+ ipath = os.path.abspath(os.path.join(os.path.dirname(ppath), f'{path}.py'))
175
+
176
+ return import_module(ipath, modname=modname, install=install, add_syspath=add_syspath)
177
+
178
+
179
+ def maybe_import(modname, package=None):
180
+ try:
181
+ return importlib.import_module(modname, package=package)
182
+ except:
183
+ alog.spam(f'Unable to load module "{modname}" in package "{package}"')
184
+
185
+
186
+ def module_file(module):
187
+ return getattr(module, '__file__', None)
188
+
189
+
190
+ def clone_module(modname):
191
+ specs = importlib.util.find_spec(modname)
192
+ module = importlib.util.module_from_spec(specs)
193
+ specs.loader.exec_module(module)
194
+
195
+ return module
196
+