omlish 0.0.0.dev404__py3-none-any.whl → 0.0.0.dev406__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.
- omlish/.manifests.json +78 -78
- omlish/__about__.py +3 -3
- omlish/bootstrap/__main__.py +3 -3
- omlish/codecs/base.py +4 -4
- omlish/codecs/registry.py +1 -4
- omlish/collections/identity.py +3 -0
- omlish/diag/procfs.py +3 -3
- omlish/formats/codecs.py +4 -4
- omlish/io/compress/codecs.py +4 -4
- omlish/lite/contextmanagers.py +10 -7
- omlish/manifests/base.py +4 -4
- omlish/manifests/globals.py +46 -11
- omlish/manifests/loading.py +356 -183
- omlish/manifests/static.py +2 -2
- omlish/manifests/types.py +2 -2
- omlish/os/pidfiles/__main__.py +3 -3
- omlish/secrets/pwgen.py +3 -3
- omlish/specs/jmespath/__main__.py +3 -3
- {omlish-0.0.0.dev404.dist-info → omlish-0.0.0.dev406.dist-info}/METADATA +3 -3
- {omlish-0.0.0.dev404.dist-info → omlish-0.0.0.dev406.dist-info}/RECORD +24 -24
- {omlish-0.0.0.dev404.dist-info → omlish-0.0.0.dev406.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev404.dist-info → omlish-0.0.0.dev406.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev404.dist-info → omlish-0.0.0.dev406.dist-info}/licenses/LICENSE +0 -0
- {omlish-0.0.0.dev404.dist-info → omlish-0.0.0.dev406.dist-info}/top_level.txt +0 -0
omlish/manifests/loading.py
CHANGED
@@ -4,16 +4,13 @@
|
|
4
4
|
Should be kept somewhat lightweight - used in cli entrypoints.
|
5
5
|
|
6
6
|
TODO:
|
7
|
-
- persisted caching support - {pkg_name: manifests}
|
8
7
|
- real relative cls names - shouldn't need parent package names
|
9
|
-
- *require* loaded class names - special All sentinel for explicit all
|
10
|
-
- ! late instantiation !
|
11
8
|
- TypeMap style weak cache of issubclass queries
|
12
9
|
- wait.. lazily load the class for virtual subclass queries? xor support virtual bases?
|
10
|
+
- weakref class dict keys?
|
13
11
|
"""
|
14
12
|
import dataclasses as dc
|
15
13
|
import importlib.machinery
|
16
|
-
import importlib.resources
|
17
14
|
import importlib.util
|
18
15
|
import json
|
19
16
|
import os.path
|
@@ -23,6 +20,9 @@ import typing as ta
|
|
23
20
|
from .types import Manifest
|
24
21
|
|
25
22
|
|
23
|
+
T = ta.TypeVar('T')
|
24
|
+
|
25
|
+
|
26
26
|
##
|
27
27
|
|
28
28
|
|
@@ -31,30 +31,42 @@ class ManifestLoader:
|
|
31
31
|
def __init__(
|
32
32
|
self,
|
33
33
|
package: 'ManifestLoader.LoadedPackage',
|
34
|
-
|
34
|
+
resolved: 'ManifestLoader._ResolvedManifest',
|
35
35
|
) -> None:
|
36
36
|
super().__init__()
|
37
37
|
|
38
38
|
self._package = package
|
39
|
-
self.
|
39
|
+
self._resolved = resolved
|
40
|
+
|
41
|
+
def __repr__(self) -> str:
|
42
|
+
return (
|
43
|
+
f'{self.__class__.__name__}@{id(self):x}('
|
44
|
+
f'package={self._package.name!r}, '
|
45
|
+
f'module={self._resolved.module!r}, '
|
46
|
+
f'class_key={self._resolved.class_key!r}'
|
47
|
+
f')'
|
48
|
+
)
|
40
49
|
|
41
50
|
@property
|
42
51
|
def package(self) -> 'ManifestLoader.LoadedPackage':
|
43
52
|
return self._package
|
44
53
|
|
54
|
+
@property
|
55
|
+
def module(self) -> str:
|
56
|
+
return self._resolved.module
|
57
|
+
|
58
|
+
@property
|
59
|
+
def class_key(self) -> str:
|
60
|
+
return self._resolved.class_key
|
61
|
+
|
45
62
|
@property
|
46
63
|
def manifest(self) -> Manifest:
|
47
|
-
return self.
|
64
|
+
return self._resolved.manifest
|
48
65
|
|
49
66
|
@property
|
50
67
|
def loader(self) -> 'ManifestLoader':
|
51
68
|
return self._package.loader
|
52
69
|
|
53
|
-
@property
|
54
|
-
def class_key(self) -> str:
|
55
|
-
[(cls_key, value_dct)] = self._manifest.value.items()
|
56
|
-
return cls_key
|
57
|
-
|
58
70
|
_value: ta.Any
|
59
71
|
|
60
72
|
def value(self) -> ta.Any:
|
@@ -63,7 +75,7 @@ class ManifestLoader:
|
|
63
75
|
except AttributeError:
|
64
76
|
pass
|
65
77
|
|
66
|
-
value = self.loader.
|
78
|
+
value = self.loader._instantiate_resolved_manifest(self._resolved) # noqa
|
67
79
|
self._value = value
|
68
80
|
return value
|
69
81
|
|
@@ -80,6 +92,9 @@ class ManifestLoader:
|
|
80
92
|
|
81
93
|
_manifests: ta.Sequence['ManifestLoader.LoadedManifest']
|
82
94
|
|
95
|
+
def __repr__(self) -> str:
|
96
|
+
return f'{self.__class__.__name__}@{id(self):x}(name={self._name!r})'
|
97
|
+
|
83
98
|
@property
|
84
99
|
def loader(self) -> 'ManifestLoader':
|
85
100
|
return self._loader
|
@@ -92,56 +107,206 @@ class ManifestLoader:
|
|
92
107
|
def manifests(self) -> ta.Sequence['ManifestLoader.LoadedManifest']:
|
93
108
|
return self._manifests
|
94
109
|
|
110
|
+
_manifests_by_class_key: ta.Mapping[str, ta.Sequence['ManifestLoader.LoadedManifest']]
|
111
|
+
|
112
|
+
@property
|
113
|
+
def manifests_by_class_key(self) -> ta.Mapping[str, ta.Sequence['ManifestLoader.LoadedManifest']]:
|
114
|
+
try:
|
115
|
+
return self._manifests_by_class_key
|
116
|
+
except AttributeError:
|
117
|
+
pass
|
118
|
+
|
119
|
+
dct: dict = {}
|
120
|
+
for m in self._manifests:
|
121
|
+
try:
|
122
|
+
lst = dct[m.class_key]
|
123
|
+
except KeyError:
|
124
|
+
lst = dct[m.class_key] = []
|
125
|
+
lst.append(m)
|
126
|
+
self._manifests_by_class_key = dct
|
127
|
+
return dct
|
128
|
+
|
129
|
+
##
|
130
|
+
|
131
|
+
@dc.dataclass(frozen=True)
|
132
|
+
class Config:
|
133
|
+
package_scan_root_dirs: ta.Optional[ta.Collection[str]] = None
|
134
|
+
|
135
|
+
discover_packages: ta.Optional[bool] = None
|
136
|
+
discover_packages_fallback_scan_root_dirs: ta.Optional[ta.Collection[str]] = None
|
137
|
+
|
138
|
+
module_remap: ta.Optional[ta.Mapping[str, str]] = None
|
139
|
+
|
140
|
+
value_instantiator: ta.Optional[ta.Callable[..., ta.Any]] = None
|
141
|
+
|
142
|
+
def __post_init__(self) -> None:
|
143
|
+
if isinstance(self.package_scan_root_dirs, str):
|
144
|
+
raise TypeError(self.package_scan_root_dirs)
|
145
|
+
if isinstance(self.discover_packages_fallback_scan_root_dirs, bool):
|
146
|
+
raise TypeError(self.discover_packages_fallback_scan_root_dirs)
|
147
|
+
|
148
|
+
@classmethod
|
149
|
+
def merge(cls, *configs: 'ManifestLoader.Config') -> 'ManifestLoader.Config':
|
150
|
+
kw: dict = {}
|
151
|
+
for c in configs:
|
152
|
+
for k, v in dc.asdict(c).items():
|
153
|
+
if v is None:
|
154
|
+
continue
|
155
|
+
elif k in ('package_scan_root_dirs', 'discover_packages_fallback_scan_root_dirs'): # noqa
|
156
|
+
kw[k] = [*kw.get(k, []), *v]
|
157
|
+
elif k == 'module_remap':
|
158
|
+
kw[k] = {**kw.get(k, {}), **v}
|
159
|
+
else:
|
160
|
+
kw[k] = v
|
161
|
+
return cls(**kw)
|
162
|
+
|
163
|
+
def __or__(self, other: 'ManifestLoader.Config') -> 'ManifestLoader.Config':
|
164
|
+
return ManifestLoader.Config.merge(self, other)
|
165
|
+
|
95
166
|
def __init__(
|
96
167
|
self,
|
97
|
-
|
98
|
-
module_remap: ta.Optional[ta.Mapping[str, str]] = None,
|
99
|
-
value_instantiator: ta.Optional[ta.Callable[..., ta.Any]] = None,
|
168
|
+
config: Config,
|
100
169
|
) -> None:
|
101
170
|
super().__init__()
|
102
171
|
|
103
|
-
self.
|
104
|
-
self._module_remap = module_remap or {}
|
172
|
+
self._config = config
|
105
173
|
|
106
174
|
self._lock = threading.RLock()
|
107
175
|
|
108
|
-
self.
|
176
|
+
self._module_remap = config.module_remap or {}
|
177
|
+
self._module_reverse_remap = {v: k for k, v in (self._module_remap or {}).items()}
|
109
178
|
|
110
179
|
self._loaded_classes: ta.Dict[str, type] = {}
|
111
180
|
self._loaded_packages: ta.Dict[str, ta.Optional[ManifestLoader.LoadedPackage]] = {}
|
112
181
|
|
113
182
|
self._scanned_package_root_dirs: ta.Dict[str, ta.Sequence[str]] = {}
|
114
183
|
|
115
|
-
|
184
|
+
@property
|
185
|
+
def config(self) -> Config:
|
186
|
+
return self._config
|
116
187
|
|
117
188
|
@classmethod
|
118
|
-
def
|
189
|
+
def config_from_entry_point(
|
119
190
|
cls,
|
120
191
|
globals: ta.Mapping[str, ta.Any], # noqa
|
121
|
-
|
122
|
-
module_remap: ta.Optional[ta.Mapping[str, str]] = None,
|
123
|
-
**kwargs: ta.Any,
|
124
|
-
) -> ta.Dict[str, ta.Any]:
|
192
|
+
) -> Config:
|
125
193
|
rm: ta.Dict[str, str] = {}
|
126
194
|
|
127
|
-
if module_remap:
|
128
|
-
rm.update(module_remap)
|
129
|
-
|
130
195
|
if '__name__' in globals and '__spec__' in globals:
|
131
196
|
name: str = globals['__name__']
|
132
197
|
spec: importlib.machinery.ModuleSpec = globals['__spec__']
|
133
198
|
if '__main__' not in rm and name == '__main__':
|
134
199
|
rm[spec.name] = '__main__'
|
135
200
|
|
136
|
-
return
|
201
|
+
return ManifestLoader.Config(module_remap=rm)
|
137
202
|
|
138
|
-
|
203
|
+
##
|
204
|
+
|
205
|
+
ENTRY_POINT_GROUP: ta.ClassVar[str] = 'omlish.manifests'
|
206
|
+
|
207
|
+
_discovered_packages: ta.ClassVar[ta.Optional[ta.Sequence[str]]] = None
|
208
|
+
|
209
|
+
@classmethod
|
210
|
+
def _discover_packages_uncached(cls) -> ta.Sequence[str]:
|
211
|
+
from importlib import metadata as importlib_metadata # noqa
|
212
|
+
return [
|
213
|
+
ep.value
|
214
|
+
for ep in importlib_metadata.entry_points(group=cls.ENTRY_POINT_GROUP)
|
215
|
+
]
|
216
|
+
|
217
|
+
@classmethod
|
218
|
+
def discover_packages(cls) -> ta.Sequence[str]:
|
219
|
+
if (x := cls._discovered_packages) is not None:
|
220
|
+
return x
|
221
|
+
|
222
|
+
x = cls._discover_packages_uncached()
|
223
|
+
cls._discovered_packages = x
|
224
|
+
return x
|
225
|
+
|
226
|
+
##
|
227
|
+
|
228
|
+
def _scan_package_root_dir_uncached(
|
229
|
+
self,
|
230
|
+
root_dir: str,
|
231
|
+
) -> ta.Sequence[str]:
|
232
|
+
pkgs: ta.List[str] = []
|
233
|
+
|
234
|
+
for n in os.listdir(root_dir):
|
235
|
+
if (
|
236
|
+
os.path.isdir(p := os.path.join(root_dir, n)) and
|
237
|
+
os.path.exists(os.path.join(p, '__init__.py'))
|
238
|
+
):
|
239
|
+
pkgs.append(n)
|
240
|
+
|
241
|
+
return pkgs
|
242
|
+
|
243
|
+
def _scan_package_root_dir_locked(
|
244
|
+
self,
|
245
|
+
root_dir: str,
|
246
|
+
) -> ta.Sequence[str]:
|
247
|
+
try:
|
248
|
+
return self._scanned_package_root_dirs[root_dir]
|
249
|
+
except KeyError:
|
250
|
+
pass
|
251
|
+
|
252
|
+
ret = self._scan_package_root_dir_uncached(root_dir)
|
253
|
+
self._scanned_package_root_dirs[root_dir] = ret
|
254
|
+
return ret
|
255
|
+
|
256
|
+
def _scan_package_root_dir(
|
257
|
+
self,
|
258
|
+
root_dir: str,
|
259
|
+
) -> ta.Sequence[str]:
|
260
|
+
with self._lock:
|
261
|
+
return self._scan_package_root_dir_locked(root_dir)
|
262
|
+
|
263
|
+
##
|
264
|
+
|
265
|
+
_detected_packages: ta.Set[str]
|
266
|
+
|
267
|
+
def _do_initialize(self) -> None:
|
268
|
+
self._detected_packages = set()
|
269
|
+
|
270
|
+
for r in self._config.package_scan_root_dirs or []:
|
271
|
+
self._detected_packages.update(self._scan_package_root_dir_locked(r))
|
272
|
+
|
273
|
+
if self._config.discover_packages:
|
274
|
+
self._detected_packages.update(dps := self.discover_packages())
|
275
|
+
if not dps:
|
276
|
+
for r in self._config.discover_packages_fallback_scan_root_dirs or []:
|
277
|
+
self._detected_packages.update(self._scan_package_root_dir_locked(r))
|
278
|
+
|
279
|
+
_has_initialized = False
|
280
|
+
|
281
|
+
def _initialize_locked(self) -> None:
|
282
|
+
if not self._has_initialized:
|
283
|
+
self._do_initialize()
|
284
|
+
self._has_initialized = True
|
285
|
+
|
286
|
+
def has_initialized(self) -> bool:
|
287
|
+
with self._lock:
|
288
|
+
return self._has_initialized
|
289
|
+
|
290
|
+
def initialize(self) -> None:
|
291
|
+
if not self._has_initialized:
|
292
|
+
with self._lock:
|
293
|
+
self._initialize_locked()
|
294
|
+
|
295
|
+
##
|
296
|
+
|
297
|
+
@dc.dataclass()
|
298
|
+
class ClassKeyError(Exception):
|
299
|
+
key: str
|
300
|
+
module: ta.Optional[str] = None
|
139
301
|
|
140
302
|
def _load_class_uncached(self, key: str) -> type:
|
141
|
-
if not key.startswith('
|
142
|
-
raise
|
303
|
+
if not key.startswith('!'):
|
304
|
+
raise ManifestLoader.ClassKeyError(key)
|
143
305
|
|
144
306
|
parts = key[1:].split('.')
|
307
|
+
if '' in parts:
|
308
|
+
raise ManifestLoader.ClassKeyError(key)
|
309
|
+
|
145
310
|
pos = next(i for i, p in enumerate(parts) if p[0].isupper())
|
146
311
|
|
147
312
|
mod_name = '.'.join(parts[:pos])
|
@@ -172,35 +337,84 @@ class ManifestLoader:
|
|
172
337
|
with self._lock:
|
173
338
|
return self._load_class_locked(key)
|
174
339
|
|
175
|
-
|
340
|
+
def get_class_key(self, cls: type) -> str:
|
341
|
+
if not (isinstance(cls, type) and dc.is_dataclass(cls)):
|
342
|
+
raise TypeError(cls)
|
343
|
+
mod_name = cls.__module__
|
344
|
+
mod_name = self._module_reverse_remap.get(mod_name, mod_name)
|
345
|
+
return f'!{mod_name}.{cls.__qualname__}'
|
346
|
+
|
347
|
+
##
|
348
|
+
|
349
|
+
class _ResolvedManifest(ta.NamedTuple):
|
350
|
+
manifest: Manifest
|
351
|
+
package: str
|
352
|
+
|
353
|
+
module: str
|
354
|
+
class_key: str
|
355
|
+
value_dct: ta.Any
|
176
356
|
|
177
|
-
|
357
|
+
@classmethod
|
358
|
+
def _resolve_raw_manifest(
|
359
|
+
cls,
|
360
|
+
m: Manifest,
|
361
|
+
*,
|
362
|
+
package_name: str,
|
363
|
+
) -> _ResolvedManifest:
|
364
|
+
# self._module = module
|
365
|
+
# self._class_key = class_key
|
366
|
+
if not m.module.startswith('.'):
|
367
|
+
raise NameError(m.module)
|
368
|
+
|
369
|
+
module = package_name + m.module
|
370
|
+
|
371
|
+
[(class_key, value_dct)] = m.value.items()
|
372
|
+
|
373
|
+
if not class_key.startswith('!'):
|
374
|
+
raise ManifestLoader.ClassKeyError(class_key, module=module)
|
375
|
+
|
376
|
+
if class_key.startswith('!.'):
|
377
|
+
class_key = f'!{package_name}{class_key[1:]}'
|
378
|
+
|
379
|
+
# FIXME: move to builder
|
380
|
+
# elif key.startswith('$.'):
|
381
|
+
# if module.startswith('.'):
|
382
|
+
# raise NameError(module)
|
383
|
+
# kl = key[1:].split('.')
|
384
|
+
# ml = module.split('.')
|
385
|
+
# lvl = len(kl) - kl[::-1].index('')
|
386
|
+
# if lvl >= len(ml):
|
387
|
+
# raise ManifestLoader.ClassKeyError(key, module=module)
|
388
|
+
# rn = '.'.join([*ml[:-lvl], *kl[lvl:]])
|
389
|
+
# return f'${rn}'
|
390
|
+
|
391
|
+
return ManifestLoader._ResolvedManifest(
|
392
|
+
manifest=m,
|
393
|
+
package=package_name,
|
394
|
+
|
395
|
+
module=module,
|
396
|
+
class_key=class_key,
|
397
|
+
value_dct=value_dct,
|
398
|
+
)
|
399
|
+
|
400
|
+
##
|
401
|
+
|
402
|
+
@classmethod
|
403
|
+
def _deserialize_raw_manifests(cls, obj: ta.Any) -> ta.Sequence[Manifest]:
|
178
404
|
if not isinstance(obj, (list, tuple)):
|
179
405
|
raise TypeError(obj)
|
180
406
|
|
181
407
|
lst: ta.List[Manifest] = []
|
182
408
|
for e in obj:
|
183
|
-
|
184
|
-
|
185
|
-
m = dc.replace(m, module=pkg_name + m.module)
|
186
|
-
|
187
|
-
[(key, value_dct)] = m.value.items()
|
188
|
-
if not key.startswith('$'):
|
189
|
-
raise Exception(f'Bad key: {key}')
|
190
|
-
if key.startswith('$.'):
|
191
|
-
key = f'${pkg_name}{key[1:]}'
|
192
|
-
m = dc.replace(m, value={key: value_dct})
|
193
|
-
|
194
|
-
lst.append(m)
|
409
|
+
lst.append(Manifest(**e))
|
195
410
|
|
196
411
|
return lst
|
197
412
|
|
198
|
-
|
199
|
-
|
200
|
-
def _read_package_file_text(self, pkg_name: str, file_name: str) -> ta.Optional[str]:
|
413
|
+
@classmethod
|
414
|
+
def _read_package_file_text(cls, package_name: str, file_name: str) -> ta.Optional[str]:
|
201
415
|
# importlib.resources.files actually imports the package - to avoid this, if possible, the file is read straight
|
202
416
|
# off the filesystem.
|
203
|
-
spec = importlib.util.find_spec(
|
417
|
+
spec = importlib.util.find_spec(package_name)
|
204
418
|
if (
|
205
419
|
spec is not None and
|
206
420
|
isinstance(spec.loader, importlib.machinery.SourceFileLoader) and
|
@@ -214,15 +428,17 @@ class ManifestLoader:
|
|
214
428
|
with open(file_path, encoding='utf-8') as f:
|
215
429
|
return f.read()
|
216
430
|
|
217
|
-
|
431
|
+
from importlib import resources as importlib_resources # noqa
|
432
|
+
t = importlib_resources.files(package_name).joinpath(file_name)
|
218
433
|
if not t.is_file():
|
219
434
|
return None
|
220
435
|
return t.read_text('utf-8')
|
221
436
|
|
222
437
|
MANIFESTS_FILE_NAME: ta.ClassVar[str] = '.manifests.json'
|
223
438
|
|
224
|
-
|
225
|
-
|
439
|
+
@classmethod
|
440
|
+
def _read_package_raw_manifests(cls, package_name: str) -> ta.Optional[ta.Sequence[Manifest]]:
|
441
|
+
src = cls._read_package_file_text(package_name, cls.MANIFESTS_FILE_NAME)
|
226
442
|
if src is None:
|
227
443
|
return None
|
228
444
|
|
@@ -230,13 +446,21 @@ class ManifestLoader:
|
|
230
446
|
if not isinstance(obj, (list, tuple)):
|
231
447
|
raise TypeError(obj)
|
232
448
|
|
233
|
-
|
449
|
+
return cls._deserialize_raw_manifests(obj)
|
234
450
|
|
235
|
-
|
451
|
+
##
|
452
|
+
|
453
|
+
def _load_package_uncached(self, package_name: str) -> ta.Optional[LoadedPackage]:
|
454
|
+
if (raw_lst := self._read_package_raw_manifests(package_name)) is None:
|
455
|
+
return None
|
456
|
+
|
457
|
+
ld_pkg = ManifestLoader.LoadedPackage(self, package_name)
|
236
458
|
|
237
459
|
ld_man_lst: ta.List[ManifestLoader.LoadedManifest] = []
|
238
460
|
for raw in raw_lst:
|
239
|
-
|
461
|
+
rsv = self._resolve_raw_manifest(raw, package_name=package_name)
|
462
|
+
|
463
|
+
ld_man = ManifestLoader.LoadedManifest(ld_pkg, rsv)
|
240
464
|
|
241
465
|
ld_man_lst.append(ld_man)
|
242
466
|
|
@@ -244,167 +468,116 @@ class ManifestLoader:
|
|
244
468
|
|
245
469
|
return ld_pkg
|
246
470
|
|
247
|
-
def _load_package_locked(self,
|
471
|
+
def _load_package_locked(self, package_name: str) -> ta.Optional[LoadedPackage]:
|
248
472
|
try:
|
249
|
-
return self._loaded_packages[
|
473
|
+
return self._loaded_packages[package_name]
|
250
474
|
except KeyError:
|
251
475
|
pass
|
252
476
|
|
253
|
-
pkg = self._load_package_uncached(
|
254
|
-
self._loaded_packages[
|
477
|
+
pkg = self._load_package_uncached(package_name)
|
478
|
+
self._loaded_packages[package_name] = pkg
|
255
479
|
return pkg
|
256
480
|
|
257
|
-
def load_package(self,
|
481
|
+
def load_package(self, package_name: str) -> ta.Optional[LoadedPackage]:
|
258
482
|
with self._lock:
|
259
|
-
return self._load_package_locked(
|
483
|
+
return self._load_package_locked(package_name)
|
260
484
|
|
261
|
-
|
485
|
+
##
|
262
486
|
|
263
487
|
def _instantiate_value(self, cls: type, **kwargs: ta.Any) -> ta.Any:
|
264
|
-
if self.
|
265
|
-
return self.
|
488
|
+
if self._config.value_instantiator is not None:
|
489
|
+
return self._config.value_instantiator(cls, **kwargs)
|
266
490
|
else:
|
267
491
|
return cls(**kwargs)
|
268
492
|
|
269
|
-
def
|
270
|
-
|
271
|
-
|
272
|
-
value = self._instantiate_value(cls, **value_dct)
|
493
|
+
def _instantiate_resolved_manifest(self, resolved: _ResolvedManifest) -> ta.Any:
|
494
|
+
cls = self._load_class(resolved.class_key)
|
495
|
+
value = self._instantiate_value(cls, **resolved.value_dct)
|
273
496
|
return value
|
274
497
|
|
275
|
-
|
276
|
-
|
277
|
-
# FIXME:
|
278
|
-
# class LOAD_ALL: # noqa
|
279
|
-
# def __new__(cls, *args, **kwargs): # noqa
|
280
|
-
# raise TypeError
|
281
|
-
#
|
282
|
-
# def __init_subclass__(cls, **kwargs): # noqa
|
283
|
-
# raise TypeError
|
498
|
+
##
|
284
499
|
|
285
|
-
def
|
500
|
+
def _load_initialized(
|
286
501
|
self,
|
287
|
-
|
288
|
-
|
502
|
+
*,
|
503
|
+
packages: ta.Optional[ta.Collection[str]] = None,
|
504
|
+
classes: ta.Optional[ta.Collection[type]] = None,
|
289
505
|
) -> ta.Sequence[LoadedManifest]:
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
else:
|
300
|
-
only_keys = None
|
506
|
+
if isinstance(packages, str):
|
507
|
+
raise TypeError(packages)
|
508
|
+
|
509
|
+
class_keys: ta.Optional[ta.Set[str]] = None
|
510
|
+
if classes is not None:
|
511
|
+
class_keys = {self.get_class_key(cls) for cls in classes}
|
512
|
+
|
513
|
+
if packages is None:
|
514
|
+
packages = self._detected_packages
|
301
515
|
|
302
516
|
lst: ta.List[ManifestLoader.LoadedManifest] = []
|
303
|
-
for pn in
|
304
|
-
lp
|
305
|
-
if lp is None:
|
517
|
+
for pn in packages:
|
518
|
+
if (lp := self._load_package_locked(pn)) is None:
|
306
519
|
continue
|
307
520
|
|
308
|
-
|
309
|
-
|
310
|
-
|
521
|
+
if class_keys is not None:
|
522
|
+
for ck in class_keys:
|
523
|
+
lst.extend(lp.manifests_by_class_key.get(ck, []))
|
311
524
|
|
312
|
-
|
525
|
+
else:
|
526
|
+
lst.extend(lp.manifests)
|
313
527
|
|
314
528
|
return lst
|
315
529
|
|
530
|
+
def _load_locked(
|
531
|
+
self,
|
532
|
+
*,
|
533
|
+
packages: ta.Optional[ta.Collection[str]] = None,
|
534
|
+
classes: ta.Optional[ta.Collection[type]] = None,
|
535
|
+
) -> ta.Sequence[LoadedManifest]:
|
536
|
+
self._initialize_locked()
|
537
|
+
return self._load_initialized(
|
538
|
+
packages=packages,
|
539
|
+
classes=classes,
|
540
|
+
)
|
541
|
+
|
316
542
|
def load(
|
317
543
|
self,
|
318
|
-
|
319
|
-
|
544
|
+
*,
|
545
|
+
packages: ta.Optional[ta.Collection[str]] = None,
|
546
|
+
classes: ta.Optional[ta.Collection[type]] = None,
|
320
547
|
) -> ta.Sequence[LoadedManifest]:
|
548
|
+
if isinstance(packages, str):
|
549
|
+
raise TypeError(packages)
|
550
|
+
|
321
551
|
with self._lock:
|
322
|
-
return self.
|
323
|
-
|
324
|
-
|
552
|
+
return self._load_locked(
|
553
|
+
packages=packages,
|
554
|
+
classes=classes,
|
325
555
|
)
|
326
556
|
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
@classmethod
|
334
|
-
def _discover_packages_uncached(cls) -> ta.Sequence[str]:
|
335
|
-
# This is a fat dep so do it late.
|
336
|
-
from importlib import metadata as importlib_metadata # noqa
|
337
|
-
|
557
|
+
def load_values(
|
558
|
+
self,
|
559
|
+
*,
|
560
|
+
packages: ta.Optional[ta.Collection[str]] = None,
|
561
|
+
classes: ta.Optional[ta.Collection[type]] = None,
|
562
|
+
) -> ta.Sequence[ta.Any]:
|
338
563
|
return [
|
339
|
-
|
340
|
-
for
|
564
|
+
lm.value()
|
565
|
+
for lm in self.load(
|
566
|
+
packages=packages,
|
567
|
+
classes=classes,
|
568
|
+
)
|
341
569
|
]
|
342
570
|
|
343
|
-
|
344
|
-
def discover_packages(cls) -> ta.Sequence[str]:
|
345
|
-
if (x := cls._discovered_packages) is not None:
|
346
|
-
return x
|
347
|
-
|
348
|
-
x = cls._discover_packages_uncached()
|
349
|
-
cls._discovered_packages = x
|
350
|
-
return x
|
351
|
-
|
352
|
-
##
|
353
|
-
|
354
|
-
def _scan_package_root_dir_uncached(
|
355
|
-
self,
|
356
|
-
root_dir: str,
|
357
|
-
) -> ta.Sequence[str]:
|
358
|
-
pkgs: ta.List[str] = []
|
359
|
-
|
360
|
-
for n in os.listdir(root_dir):
|
361
|
-
if (
|
362
|
-
os.path.isdir(p := os.path.join(root_dir, n)) and
|
363
|
-
os.path.exists(os.path.join(p, '__init__.py'))
|
364
|
-
):
|
365
|
-
pkgs.append(n)
|
366
|
-
|
367
|
-
return pkgs
|
368
|
-
|
369
|
-
def _scan_package_root_dir_locked(
|
370
|
-
self,
|
371
|
-
root_dir: str,
|
372
|
-
) -> ta.Sequence[str]:
|
373
|
-
try:
|
374
|
-
return self._scanned_package_root_dirs[root_dir]
|
375
|
-
except KeyError:
|
376
|
-
pass
|
377
|
-
|
378
|
-
ret = self._scan_package_root_dir_uncached(root_dir)
|
379
|
-
self._scanned_package_root_dirs[root_dir] = ret
|
380
|
-
return ret
|
381
|
-
|
382
|
-
def _scan_package_root_dir(
|
383
|
-
self,
|
384
|
-
root_dir: str,
|
385
|
-
) -> ta.Sequence[str]:
|
386
|
-
with self._lock:
|
387
|
-
return self._scan_package_root_dir_locked(root_dir)
|
388
|
-
|
389
|
-
def scan_or_discover_packages(
|
571
|
+
def load_values_of(
|
390
572
|
self,
|
573
|
+
cls: ta.Type[T],
|
391
574
|
*,
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
for r in specified_root_dirs:
|
402
|
-
pkgs.extend(self._scan_package_root_dir(r))
|
403
|
-
|
404
|
-
else:
|
405
|
-
pkgs.extend(self.discover_packages())
|
406
|
-
|
407
|
-
if not pkgs and fallback_root_dir is not None:
|
408
|
-
pkgs.extend(self._scan_package_root_dir(fallback_root_dir))
|
409
|
-
|
410
|
-
return pkgs
|
575
|
+
packages: ta.Optional[ta.Collection[str]] = None,
|
576
|
+
) -> ta.Sequence[T]:
|
577
|
+
return [
|
578
|
+
ta.cast(T, lm.value())
|
579
|
+
for lm in self.load(
|
580
|
+
packages=packages,
|
581
|
+
classes=[cls],
|
582
|
+
)
|
583
|
+
]
|