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.
@@ -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
- manifest: Manifest,
34
+ resolved: 'ManifestLoader._ResolvedManifest',
35
35
  ) -> None:
36
36
  super().__init__()
37
37
 
38
38
  self._package = package
39
- self._manifest = manifest
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._manifest
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._instantiate_loaded_manifest(self) # noqa
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._value_instantiator = value_instantiator
104
- self._module_remap = module_remap or {}
172
+ self._config = config
105
173
 
106
174
  self._lock = threading.RLock()
107
175
 
108
- self._module_reverse_remap = {v: k for k, v in self._module_remap.items()}
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 kwargs_from_entry_point(
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 dict(module_remap=rm, **kwargs)
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 Exception(f'Bad key: {key}')
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
- def _deserialize_raw_manifests(self, obj: ta.Any, pkg_name: str) -> ta.Sequence[Manifest]:
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
- m = Manifest(**e)
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(pkg_name)
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
- t = importlib.resources.files(pkg_name).joinpath(file_name)
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
- def _load_package_uncached(self, pkg_name: str) -> ta.Optional[LoadedPackage]:
225
- src = self._read_package_file_text(pkg_name, self.MANIFESTS_FILE_NAME)
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
- raw_lst = self._deserialize_raw_manifests(obj, pkg_name)
449
+ return cls._deserialize_raw_manifests(obj)
234
450
 
235
- ld_pkg = ManifestLoader.LoadedPackage(self, pkg_name)
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
- ld_man = ManifestLoader.LoadedManifest(ld_pkg, raw)
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, pkg_name: str) -> ta.Optional[LoadedPackage]:
471
+ def _load_package_locked(self, package_name: str) -> ta.Optional[LoadedPackage]:
248
472
  try:
249
- return self._loaded_packages[pkg_name]
473
+ return self._loaded_packages[package_name]
250
474
  except KeyError:
251
475
  pass
252
476
 
253
- pkg = self._load_package_uncached(pkg_name)
254
- self._loaded_packages[pkg_name] = pkg
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, pkg_name: str) -> ta.Optional[LoadedPackage]:
481
+ def load_package(self, package_name: str) -> ta.Optional[LoadedPackage]:
258
482
  with self._lock:
259
- return self._load_package_locked(pkg_name)
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._value_instantiator is not None:
265
- return self._value_instantiator(cls, **kwargs)
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 _instantiate_loaded_manifest(self, ld_man: LoadedManifest) -> ta.Any:
270
- [(cls_key, value_dct)] = ld_man.manifest.value.items()
271
- cls = self._load_class(cls_key)
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 _load(
500
+ def _load_initialized(
286
501
  self,
287
- *pkg_names: str,
288
- only: ta.Optional[ta.Iterable[type]] = None,
502
+ *,
503
+ packages: ta.Optional[ta.Collection[str]] = None,
504
+ classes: ta.Optional[ta.Collection[type]] = None,
289
505
  ) -> ta.Sequence[LoadedManifest]:
290
- only_keys: ta.Optional[ta.Set]
291
- if only is not None:
292
- only_keys = set()
293
- for cls in only:
294
- if not (isinstance(cls, type) and dc.is_dataclass(cls)):
295
- raise TypeError(cls)
296
- mod_name = cls.__module__
297
- mod_name = self._module_reverse_remap.get(mod_name, mod_name)
298
- only_keys.add(f'${mod_name}.{cls.__qualname__}')
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 pkg_names:
304
- lp = self.load_package(pn)
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
- for m in lp.manifests:
309
- if only_keys is not None and m.class_key not in only_keys:
310
- continue
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
- lst.append(m)
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
- *pkg_names: str,
319
- only: ta.Optional[ta.Iterable[type]] = None,
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._load(
323
- *pkg_names,
324
- only=only,
552
+ return self._load_locked(
553
+ packages=packages,
554
+ classes=classes,
325
555
  )
326
556
 
327
- ##
328
-
329
- ENTRY_POINT_GROUP: ta.ClassVar[str] = 'omlish.manifests'
330
-
331
- _discovered_packages: ta.ClassVar[ta.Optional[ta.Sequence[str]]] = None
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
- ep.value
340
- for ep in importlib_metadata.entry_points(group=cls.ENTRY_POINT_GROUP)
564
+ lm.value()
565
+ for lm in self.load(
566
+ packages=packages,
567
+ classes=classes,
568
+ )
341
569
  ]
342
570
 
343
- @classmethod
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
- specified_root_dirs: ta.Optional[ta.Sequence[str]] = None,
393
- fallback_root_dir: ta.Optional[str] = None,
394
- ) -> ta.Sequence[str]:
395
- pkgs: list[str] = []
396
-
397
- if specified_root_dirs is not None:
398
- if isinstance(specified_root_dirs, str):
399
- raise TypeError(specified_root_dirs)
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
+ ]