omlish 0.0.0.dev403__py3-none-any.whl → 0.0.0.dev405__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/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev403'
2
- __revision__ = '5403fd726d8c05e6b9edf0c3b0c86c6600809211'
1
+ __version__ = '0.0.0.dev405'
2
+ __revision__ = '2845f7bb7c8df0a75acb7e7dece1fc712a54aa05'
3
3
 
4
4
 
5
5
  #
@@ -65,7 +65,7 @@ class Project(ProjectBase):
65
65
 
66
66
  'formats': [
67
67
  'orjson ~= 3.11',
68
- 'ujson ~= 5.10',
68
+ 'ujson ~= 5.11',
69
69
 
70
70
  'pyyaml ~= 6.0',
71
71
 
omlish/codecs/registry.py CHANGED
@@ -106,10 +106,7 @@ def _install_standard_codecs(registry: CodecRegistry) -> None:
106
106
 
107
107
  @cached.function
108
108
  def _build_manifest_lazy_loaded_codecs() -> ta.Sequence[LazyLoadedCodec]:
109
- ldr = manifest_globals.GlobalManifestLoader.instance()
110
- pkgs = {__package__.split('.')[0], *ldr.discover_packages()}
111
- mns = ldr.load(*pkgs, only=[LazyLoadedCodec])
112
- return [m.value() for m in mns]
109
+ return manifest_globals.GlobalManifestLoader.load_values_of(LazyLoadedCodec)
113
110
 
114
111
 
115
112
  def _install_manifest_lazy_loaded_codecs(registry: CodecRegistry) -> None:
@@ -102,6 +102,9 @@ class IdentityWeakKeyDictionary(ta.MutableMapping[K, V]):
102
102
 
103
103
  See also:
104
104
  https://github.com/python-trio/trio/blob/efd785a20721707b52a6e2289a65e25722b30c96/src/trio/_core/_ki.py#L81
105
+
106
+ TODO:
107
+ - audit for freethreaded
105
108
  """
106
109
 
107
110
  def __init__(self, *args: ta.Any, **kwargs: ta.Any) -> None:
@@ -4,8 +4,6 @@ import functools
4
4
  import sys
5
5
  import typing as ta
6
6
 
7
- from .check import check
8
-
9
7
 
10
8
  T = ta.TypeVar('T')
11
9
  ExitStackedT = ta.TypeVar('ExitStackedT', bound='ExitStacked')
@@ -50,7 +48,8 @@ class ExitStacked:
50
48
  """
51
49
 
52
50
  with self._exit_stacked_init_wrapper():
53
- check.state(self._exit_stack is None)
51
+ if self._exit_stack is not None:
52
+ raise RuntimeError
54
53
  es = self._exit_stack = contextlib.ExitStack()
55
54
  es.__enter__()
56
55
  try:
@@ -78,7 +77,8 @@ class ExitStacked:
78
77
  pass
79
78
 
80
79
  def _enter_context(self, cm: ta.ContextManager[T]) -> T:
81
- es = check.not_none(self._exit_stack)
80
+ if (es := self._exit_stack) is None:
81
+ raise RuntimeError
82
82
  return es.enter_context(cm)
83
83
 
84
84
 
@@ -107,7 +107,8 @@ class AsyncExitStacked:
107
107
  @ta.final
108
108
  async def __aenter__(self: AsyncExitStackedT) -> AsyncExitStackedT:
109
109
  async with self._async_exit_stacked_init_wrapper():
110
- check.state(self._exit_stack is None)
110
+ if self._exit_stack is not None:
111
+ raise RuntimeError
111
112
  es = self._exit_stack = contextlib.AsyncExitStack()
112
113
  await es.__aenter__()
113
114
  try:
@@ -135,11 +136,13 @@ class AsyncExitStacked:
135
136
  pass
136
137
 
137
138
  def _enter_context(self, cm: ta.ContextManager[T]) -> T:
138
- es = check.not_none(self._exit_stack)
139
+ if (es := self._exit_stack) is None:
140
+ raise RuntimeError
139
141
  return es.enter_context(cm)
140
142
 
141
143
  async def _enter_async_context(self, cm: ta.AsyncContextManager[T]) -> T:
142
- es = check.not_none(self._exit_stack)
144
+ if (es := self._exit_stack) is None:
145
+ raise RuntimeError
143
146
  return await es.enter_async_context(cm)
144
147
 
145
148
 
@@ -1,5 +1,6 @@
1
- # ruff: noqa: UP045
1
+ # ruff: noqa: UP006 UP045
2
2
  # @omlish-lite
3
+ import os
3
4
  import threading
4
5
  import typing as ta
5
6
 
@@ -7,6 +8,9 @@ from ..lite.marshal import unmarshal_obj
7
8
  from .loading import ManifestLoader
8
9
 
9
10
 
11
+ T = ta.TypeVar('T')
12
+
13
+
10
14
  ##
11
15
 
12
16
 
@@ -28,17 +32,20 @@ class GlobalManifestLoader:
28
32
  if (inst := cls._instance) is None:
29
33
  with cls._lock:
30
34
  if (inst := cls._instance) is None:
31
- inst = cls._instance = ManifestLoader(**cls.default_kwargs())
35
+ inst = cls._instance = ManifestLoader(cls.default_config())
32
36
 
33
37
  return inst
34
38
 
35
39
  @classmethod
36
- def initialize(cls, **kwargs: ta.Any) -> ManifestLoader:
40
+ def initialize(cls, config: ta.Optional[ManifestLoader.Config] = None) -> ManifestLoader:
37
41
  with cls._lock:
38
42
  if cls._instance is not None:
39
- raise Exception(f'{cls.__name__} already initialized')
43
+ raise RuntimeError(f'{cls.__name__} already initialized')
44
+
45
+ if config is None:
46
+ config = cls.default_config()
40
47
 
41
- inst = cls._instance = ManifestLoader(**kwargs)
48
+ inst = cls._instance = ManifestLoader(config)
42
49
 
43
50
  return inst
44
51
 
@@ -49,8 +56,11 @@ class GlobalManifestLoader:
49
56
  return unmarshal_obj(kwargs, obj_cls)
50
57
 
51
58
  @classmethod
52
- def default_kwargs(cls) -> ta.Mapping[str, ta.Any]:
53
- return dict(
59
+ def default_config(cls) -> ManifestLoader.Config:
60
+ return ManifestLoader.Config(
61
+ discover_packages=True,
62
+ discover_packages_fallback_scan_root_dirs=[os.getcwd()],
63
+
54
64
  value_instantiator=cls.default_value_instantiator,
55
65
  )
56
66
 
@@ -59,10 +69,35 @@ class GlobalManifestLoader:
59
69
  @classmethod
60
70
  def load(
61
71
  cls,
62
- *pkg_names: str,
63
- only: ta.Optional[ta.Iterable[type]] = None,
72
+ *,
73
+ packages: ta.Optional[ta.Collection[str]] = None,
74
+ classes: ta.Optional[ta.Collection[type]] = None,
64
75
  ) -> ta.Sequence[ManifestLoader.LoadedManifest]:
65
76
  return cls.instance().load(
66
- *pkg_names,
67
- only=only,
77
+ packages=packages,
78
+ classes=classes,
79
+ )
80
+
81
+ @classmethod
82
+ def load_values(
83
+ cls,
84
+ *,
85
+ packages: ta.Optional[ta.Collection[str]] = None,
86
+ classes: ta.Optional[ta.Collection[type]] = None,
87
+ ) -> ta.Sequence[ta.Any]:
88
+ return cls.instance().load_values(
89
+ packages=packages,
90
+ classes=classes,
91
+ )
92
+
93
+ @classmethod
94
+ def load_values_of(
95
+ cls,
96
+ clz: ta.Type[T],
97
+ *,
98
+ packages: ta.Optional[ta.Collection[str]] = None,
99
+ ) -> ta.Sequence[T]:
100
+ return cls.instance().load_values_of(
101
+ clz,
102
+ packages=packages,
68
103
  )
@@ -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
 
@@ -38,6 +38,12 @@ class ManifestLoader:
38
38
  self._package = package
39
39
  self._manifest = manifest
40
40
 
41
+ [(cls_key, value_dct)] = self._manifest.value.items() # noqa
42
+ self._cls_key = cls_key
43
+
44
+ def __repr__(self) -> str:
45
+ return f'{self.__class__.__name__}@{id(self):x}(package={self._package!r}, class_key={self._cls_key!r})'
46
+
41
47
  @property
42
48
  def package(self) -> 'ManifestLoader.LoadedPackage':
43
49
  return self._package
@@ -52,8 +58,7 @@ class ManifestLoader:
52
58
 
53
59
  @property
54
60
  def class_key(self) -> str:
55
- [(cls_key, value_dct)] = self._manifest.value.items()
56
- return cls_key
61
+ return self._cls_key
57
62
 
58
63
  _value: ta.Any
59
64
 
@@ -80,6 +85,9 @@ class ManifestLoader:
80
85
 
81
86
  _manifests: ta.Sequence['ManifestLoader.LoadedManifest']
82
87
 
88
+ def __repr__(self) -> str:
89
+ return f'{self.__class__.__name__}(name={self._name!r})'
90
+
83
91
  @property
84
92
  def loader(self) -> 'ManifestLoader':
85
93
  return self._loader
@@ -92,54 +100,199 @@ class ManifestLoader:
92
100
  def manifests(self) -> ta.Sequence['ManifestLoader.LoadedManifest']:
93
101
  return self._manifests
94
102
 
103
+ _manifests_by_class_key: ta.Mapping[str, ta.Sequence['ManifestLoader.LoadedManifest']]
104
+
105
+ @property
106
+ def manifests_by_class_key(self) -> ta.Mapping[str, ta.Sequence['ManifestLoader.LoadedManifest']]:
107
+ try:
108
+ return self._manifests_by_class_key
109
+ except AttributeError:
110
+ pass
111
+
112
+ dct: dict = {}
113
+ for m in self._manifests:
114
+ try:
115
+ lst = dct[m.class_key]
116
+ except KeyError:
117
+ lst = dct[m.class_key] = []
118
+ lst.append(m)
119
+ self._manifests_by_class_key = dct
120
+ return dct
121
+
122
+ ##
123
+
124
+ @dc.dataclass(frozen=True)
125
+ class Config:
126
+ package_scan_root_dirs: ta.Optional[ta.Collection[str]] = None
127
+
128
+ discover_packages: ta.Optional[bool] = None
129
+ discover_packages_fallback_scan_root_dirs: ta.Optional[ta.Collection[str]] = None
130
+
131
+ module_remap: ta.Optional[ta.Mapping[str, str]] = None
132
+
133
+ value_instantiator: ta.Optional[ta.Callable[..., ta.Any]] = None
134
+
135
+ def __post_init__(self) -> None:
136
+ if isinstance(self.package_scan_root_dirs, str):
137
+ raise TypeError(self.package_scan_root_dirs)
138
+ if isinstance(self.discover_packages_fallback_scan_root_dirs, bool):
139
+ raise TypeError(self.discover_packages_fallback_scan_root_dirs)
140
+
141
+ @classmethod
142
+ def merge(cls, *configs: 'ManifestLoader.Config') -> 'ManifestLoader.Config':
143
+ kw: dict = {}
144
+ for c in configs:
145
+ for k, v in dc.asdict(c).items():
146
+ if v is None:
147
+ continue
148
+ elif k in ('package_scan_root_dirs', 'discover_packages_fallback_scan_root_dirs'): # noqa
149
+ kw[k] = [*kw.get(k, []), *v]
150
+ elif k == 'module_remap':
151
+ kw[k] = {**kw.get(k, {}), **v}
152
+ else:
153
+ kw[k] = v
154
+ return cls(**kw)
155
+
156
+ def __or__(self, other: 'ManifestLoader.Config') -> 'ManifestLoader.Config':
157
+ return ManifestLoader.Config.merge(self, other)
158
+
95
159
  def __init__(
96
160
  self,
97
- *,
98
- module_remap: ta.Optional[ta.Mapping[str, str]] = None,
99
- value_instantiator: ta.Optional[ta.Callable[..., ta.Any]] = None,
161
+ config: Config,
100
162
  ) -> None:
101
163
  super().__init__()
102
164
 
103
- self._value_instantiator = value_instantiator
104
- self._module_remap = module_remap or {}
165
+ self._config = config
105
166
 
106
167
  self._lock = threading.RLock()
107
168
 
108
- self._module_reverse_remap = {v: k for k, v in self._module_remap.items()}
169
+ self._module_remap = config.module_remap or {}
170
+ self._module_reverse_remap = {v: k for k, v in (self._module_remap or {}).items()}
109
171
 
110
172
  self._loaded_classes: ta.Dict[str, type] = {}
111
173
  self._loaded_packages: ta.Dict[str, ta.Optional[ManifestLoader.LoadedPackage]] = {}
112
174
 
113
175
  self._scanned_package_root_dirs: ta.Dict[str, ta.Sequence[str]] = {}
114
176
 
115
- #
177
+ @property
178
+ def config(self) -> Config:
179
+ return self._config
116
180
 
117
181
  @classmethod
118
- def kwargs_from_entry_point(
182
+ def config_from_entry_point(
119
183
  cls,
120
184
  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]:
185
+ ) -> Config:
125
186
  rm: ta.Dict[str, str] = {}
126
187
 
127
- if module_remap:
128
- rm.update(module_remap)
129
-
130
188
  if '__name__' in globals and '__spec__' in globals:
131
189
  name: str = globals['__name__']
132
190
  spec: importlib.machinery.ModuleSpec = globals['__spec__']
133
191
  if '__main__' not in rm and name == '__main__':
134
192
  rm[spec.name] = '__main__'
135
193
 
136
- return dict(module_remap=rm, **kwargs)
194
+ return ManifestLoader.Config(module_remap=rm)
137
195
 
138
- #
196
+ ##
197
+
198
+ ENTRY_POINT_GROUP: ta.ClassVar[str] = 'omlish.manifests'
199
+
200
+ _discovered_packages: ta.ClassVar[ta.Optional[ta.Sequence[str]]] = None
201
+
202
+ @classmethod
203
+ def _discover_packages_uncached(cls) -> ta.Sequence[str]:
204
+ from importlib import metadata as importlib_metadata # noqa
205
+ return [
206
+ ep.value
207
+ for ep in importlib_metadata.entry_points(group=cls.ENTRY_POINT_GROUP)
208
+ ]
209
+
210
+ @classmethod
211
+ def discover_packages(cls) -> ta.Sequence[str]:
212
+ if (x := cls._discovered_packages) is not None:
213
+ return x
214
+
215
+ x = cls._discover_packages_uncached()
216
+ cls._discovered_packages = x
217
+ return x
218
+
219
+ ##
220
+
221
+ def _scan_package_root_dir_uncached(
222
+ self,
223
+ root_dir: str,
224
+ ) -> ta.Sequence[str]:
225
+ pkgs: ta.List[str] = []
226
+
227
+ for n in os.listdir(root_dir):
228
+ if (
229
+ os.path.isdir(p := os.path.join(root_dir, n)) and
230
+ os.path.exists(os.path.join(p, '__init__.py'))
231
+ ):
232
+ pkgs.append(n)
233
+
234
+ return pkgs
235
+
236
+ def _scan_package_root_dir_locked(
237
+ self,
238
+ root_dir: str,
239
+ ) -> ta.Sequence[str]:
240
+ try:
241
+ return self._scanned_package_root_dirs[root_dir]
242
+ except KeyError:
243
+ pass
244
+
245
+ ret = self._scan_package_root_dir_uncached(root_dir)
246
+ self._scanned_package_root_dirs[root_dir] = ret
247
+ return ret
248
+
249
+ def _scan_package_root_dir(
250
+ self,
251
+ root_dir: str,
252
+ ) -> ta.Sequence[str]:
253
+ with self._lock:
254
+ return self._scan_package_root_dir_locked(root_dir)
255
+
256
+ ##
257
+
258
+ _detected_packages: ta.Set[str]
259
+
260
+ def _do_initialize(self) -> None:
261
+ self._detected_packages = set()
262
+
263
+ for r in self._config.package_scan_root_dirs or []:
264
+ self._detected_packages.update(self._scan_package_root_dir_locked(r))
265
+
266
+ if self._config.discover_packages:
267
+ self._detected_packages.update(dps := self.discover_packages())
268
+ if not dps:
269
+ for r in self._config.discover_packages_fallback_scan_root_dirs or []:
270
+ self._detected_packages.update(self._scan_package_root_dir_locked(r))
271
+
272
+ _has_initialized = False
273
+
274
+ def _initialize_locked(self) -> None:
275
+ if not self._has_initialized:
276
+ self._do_initialize()
277
+ self._has_initialized = True
278
+
279
+ def has_initialized(self) -> bool:
280
+ with self._lock:
281
+ return self._has_initialized
282
+
283
+ def initialize(self) -> None:
284
+ if not self._has_initialized:
285
+ with self._lock:
286
+ self._initialize_locked()
287
+
288
+ ##
289
+
290
+ class ClassKeyError(Exception):
291
+ pass
139
292
 
140
293
  def _load_class_uncached(self, key: str) -> type:
141
294
  if not key.startswith('$'):
142
- raise Exception(f'Bad key: {key}')
295
+ raise ManifestLoader.ClassKeyError(key)
143
296
 
144
297
  parts = key[1:].split('.')
145
298
  pos = next(i for i, p in enumerate(parts) if p[0].isupper())
@@ -172,7 +325,14 @@ class ManifestLoader:
172
325
  with self._lock:
173
326
  return self._load_class_locked(key)
174
327
 
175
- #
328
+ def get_class_key(self, cls: type) -> str:
329
+ if not (isinstance(cls, type) and dc.is_dataclass(cls)):
330
+ raise TypeError(cls)
331
+ mod_name = cls.__module__
332
+ mod_name = self._module_reverse_remap.get(mod_name, mod_name)
333
+ return f'${mod_name}.{cls.__qualname__}'
334
+
335
+ ##
176
336
 
177
337
  def _deserialize_raw_manifests(self, obj: ta.Any, pkg_name: str) -> ta.Sequence[Manifest]:
178
338
  if not isinstance(obj, (list, tuple)):
@@ -186,7 +346,7 @@ class ManifestLoader:
186
346
 
187
347
  [(key, value_dct)] = m.value.items()
188
348
  if not key.startswith('$'):
189
- raise Exception(f'Bad key: {key}')
349
+ raise ManifestLoader.ClassKeyError(key)
190
350
  if key.startswith('$.'):
191
351
  key = f'${pkg_name}{key[1:]}'
192
352
  m = dc.replace(m, value={key: value_dct})
@@ -214,11 +374,14 @@ class ManifestLoader:
214
374
  with open(file_path, encoding='utf-8') as f:
215
375
  return f.read()
216
376
 
217
- t = importlib.resources.files(pkg_name).joinpath(file_name)
377
+ from importlib import resources as importlib_resources # noqa
378
+ t = importlib_resources.files(pkg_name).joinpath(file_name)
218
379
  if not t.is_file():
219
380
  return None
220
381
  return t.read_text('utf-8')
221
382
 
383
+ #
384
+
222
385
  MANIFESTS_FILE_NAME: ta.ClassVar[str] = '.manifests.json'
223
386
 
224
387
  def _load_package_uncached(self, pkg_name: str) -> ta.Optional[LoadedPackage]:
@@ -258,11 +421,11 @@ class ManifestLoader:
258
421
  with self._lock:
259
422
  return self._load_package_locked(pkg_name)
260
423
 
261
- #
424
+ ##
262
425
 
263
426
  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)
427
+ if self._config.value_instantiator is not None:
428
+ return self._config.value_instantiator(cls, **kwargs)
266
429
  else:
267
430
  return cls(**kwargs)
268
431
 
@@ -272,136 +435,89 @@ class ManifestLoader:
272
435
  value = self._instantiate_value(cls, **value_dct)
273
436
  return value
274
437
 
275
- #
438
+ ##
276
439
 
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
284
-
285
- def _load(
440
+ def _load_initialized(
286
441
  self,
287
- *pkg_names: str,
288
- only: ta.Optional[ta.Iterable[type]] = None,
442
+ *,
443
+ packages: ta.Optional[ta.Collection[str]] = None,
444
+ classes: ta.Optional[ta.Collection[type]] = None,
289
445
  ) -> 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
446
+ if isinstance(packages, str):
447
+ raise TypeError(packages)
448
+
449
+ class_keys: ta.Optional[ta.Set[str]] = None
450
+ if classes is not None:
451
+ class_keys = {self.get_class_key(cls) for cls in classes}
452
+
453
+ if packages is None:
454
+ packages = self._detected_packages
301
455
 
302
456
  lst: ta.List[ManifestLoader.LoadedManifest] = []
303
- for pn in pkg_names:
304
- lp = self.load_package(pn)
305
- if lp is None:
457
+ for pn in packages:
458
+ if (lp := self._load_package_locked(pn)) is None:
306
459
  continue
307
460
 
308
- for m in lp.manifests:
309
- if only_keys is not None and m.class_key not in only_keys:
310
- continue
461
+ if class_keys is not None:
462
+ for ck in class_keys:
463
+ lst.extend(lp.manifests_by_class_key.get(ck, []))
311
464
 
312
- lst.append(m)
465
+ else:
466
+ lst.extend(lp.manifests)
313
467
 
314
468
  return lst
315
469
 
316
- def load(
470
+ def _load_locked(
317
471
  self,
318
- *pkg_names: str,
319
- only: ta.Optional[ta.Iterable[type]] = None,
472
+ *,
473
+ packages: ta.Optional[ta.Collection[str]] = None,
474
+ classes: ta.Optional[ta.Collection[type]] = None,
320
475
  ) -> ta.Sequence[LoadedManifest]:
321
- with self._lock:
322
- return self._load(
323
- *pkg_names,
324
- only=only,
325
- )
326
-
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(cls) -> ta.Sequence[str]:
335
- if (x := cls._discovered_packages) is not None:
336
- return x
337
-
338
- # This is a fat dep so do it late.
339
- from importlib import metadata as importlib_metadata # noqa
340
-
341
- x = [
342
- ep.value
343
- for ep in importlib_metadata.entry_points(group=cls.ENTRY_POINT_GROUP)
344
- ]
345
-
346
- cls._discovered_packages = x
347
- return x
348
-
349
- #
350
-
351
- def _scan_package_root_dir_uncached(
352
- self,
353
- root_dir: str,
354
- ) -> ta.Sequence[str]:
355
- pkgs: ta.List[str] = []
476
+ self._initialize_locked()
477
+ return self._load_initialized(
478
+ packages=packages,
479
+ classes=classes,
480
+ )
356
481
 
357
- for n in os.listdir(root_dir):
358
- if (
359
- os.path.isdir(p := os.path.join(root_dir, n)) and
360
- os.path.exists(os.path.join(p, '__init__.py'))
361
- ):
362
- pkgs.append(n)
363
-
364
- return pkgs
365
-
366
- def _scan_package_root_dir_locked(
482
+ def load(
367
483
  self,
368
- root_dir: str,
369
- ) -> ta.Sequence[str]:
370
- try:
371
- return self._scanned_package_root_dirs[root_dir]
372
- except KeyError:
373
- pass
374
-
375
- ret = self._scan_package_root_dir_uncached(root_dir)
376
- self._scanned_package_root_dirs[root_dir] = ret
377
- return ret
484
+ *,
485
+ packages: ta.Optional[ta.Collection[str]] = None,
486
+ classes: ta.Optional[ta.Collection[type]] = None,
487
+ ) -> ta.Sequence[LoadedManifest]:
488
+ if isinstance(packages, str):
489
+ raise TypeError(packages)
378
490
 
379
- def _scan_package_root_dir(
380
- self,
381
- root_dir: str,
382
- ) -> ta.Sequence[str]:
383
491
  with self._lock:
384
- return self._scan_package_root_dir_locked(root_dir)
492
+ return self._load_locked(
493
+ packages=packages,
494
+ classes=classes,
495
+ )
385
496
 
386
- def scan_or_discover_packages(
497
+ def load_values(
387
498
  self,
388
499
  *,
389
- specified_root_dirs: ta.Optional[ta.Sequence[str]] = None,
390
- fallback_root_dir: ta.Optional[str] = None,
391
- ) -> ta.Sequence[str]:
392
- pkgs: list[str] = []
393
-
394
- if specified_root_dirs is not None:
395
- if isinstance(specified_root_dirs, str):
396
- raise TypeError(specified_root_dirs)
397
-
398
- for r in specified_root_dirs:
399
- pkgs.extend(self._scan_package_root_dir(r))
400
-
401
- else:
402
- pkgs.extend(self.discover_packages())
403
-
404
- if not pkgs and fallback_root_dir is not None:
405
- pkgs.extend(self._scan_package_root_dir(fallback_root_dir))
500
+ packages: ta.Optional[ta.Collection[str]] = None,
501
+ classes: ta.Optional[ta.Collection[type]] = None,
502
+ ) -> ta.Sequence[ta.Any]:
503
+ return [
504
+ lm.value()
505
+ for lm in self.load(
506
+ packages=packages,
507
+ classes=classes,
508
+ )
509
+ ]
406
510
 
407
- return pkgs
511
+ def load_values_of(
512
+ self,
513
+ cls: ta.Type[T],
514
+ *,
515
+ packages: ta.Optional[ta.Collection[str]] = None,
516
+ ) -> ta.Sequence[T]:
517
+ return [
518
+ ta.cast(T, lm.value())
519
+ for lm in self.load(
520
+ packages=packages,
521
+ classes=[cls],
522
+ )
523
+ ]
@@ -17,20 +17,35 @@ from ... import lang
17
17
 
18
18
  def if_cant_import(*modules: str, **kwargs):
19
19
  missing = [m for m in modules if not lang.can_import(m, **kwargs)]
20
- return pytest.mark.skipif(bool(missing), reason=f'requires import {", ".join(missing)}')
20
+ return pytest.mark.skipif(
21
+ bool(missing),
22
+ reason=f'requires import {", ".join(missing)}',
23
+ )
21
24
 
22
25
 
23
26
  def if_not_on_path(exe: str):
24
- return pytest.mark.skipif(shutil.which(exe) is None, reason=f'requires exe on path {exe}')
27
+ return pytest.mark.skipif(
28
+ shutil.which(exe) is None,
29
+ reason=f'requires exe on path {exe}',
30
+ )
25
31
 
26
32
 
27
33
  def if_python_version_less_than(num: ta.Sequence[int]):
28
- return pytest.mark.skipif(sys.version_info < tuple(num), reason=f'python version {tuple(sys.version_info)} < {tuple(num)}') # noqa
34
+ return pytest.mark.skipif(
35
+ sys.version_info < tuple(num),
36
+ reason=f'python version {tuple(sys.version_info)} < {tuple(num)}',
37
+ )
29
38
 
30
39
 
31
40
  def if_not_platform(*platforms: str):
32
- return pytest.mark.skipif(sys.platform not in platforms, reason=f'requires platform in {platforms}')
41
+ return pytest.mark.skipif(
42
+ sys.platform not in platforms,
43
+ reason=f'requires platform in {platforms}',
44
+ )
33
45
 
34
46
 
35
47
  def if_nogil():
36
- return pytest.mark.skipif(sysconfig.get_config_var('Py_GIL_DISABLED'), reason='requires gil build')
48
+ return pytest.mark.skipif(
49
+ sysconfig.get_config_var('Py_GIL_DISABLED'),
50
+ reason='requires gil build',
51
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omlish
3
- Version: 0.0.0.dev403
3
+ Version: 0.0.0.dev405
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License-Expression: BSD-3-Clause
@@ -28,7 +28,7 @@ Requires-Dist: asttokens~=3.0; extra == "all"
28
28
  Requires-Dist: executing~=2.2; extra == "all"
29
29
  Requires-Dist: psutil~=7.0; extra == "all"
30
30
  Requires-Dist: orjson~=3.11; extra == "all"
31
- Requires-Dist: ujson~=5.10; extra == "all"
31
+ Requires-Dist: ujson~=5.11; extra == "all"
32
32
  Requires-Dist: pyyaml~=6.0; extra == "all"
33
33
  Requires-Dist: cbor2~=5.7; extra == "all"
34
34
  Requires-Dist: cloudpickle~=3.1; extra == "all"
@@ -71,7 +71,7 @@ Requires-Dist: executing~=2.2; extra == "diag"
71
71
  Requires-Dist: psutil~=7.0; extra == "diag"
72
72
  Provides-Extra: formats
73
73
  Requires-Dist: orjson~=3.11; extra == "formats"
74
- Requires-Dist: ujson~=5.10; extra == "formats"
74
+ Requires-Dist: ujson~=5.11; extra == "formats"
75
75
  Requires-Dist: pyyaml~=6.0; extra == "formats"
76
76
  Requires-Dist: cbor2~=5.7; extra == "formats"
77
77
  Requires-Dist: cloudpickle~=3.1; extra == "formats"
@@ -1,5 +1,5 @@
1
1
  omlish/.manifests.json,sha256=ftpXGlXqsqDmBh0eWXtndZBLj8Y6QVhS5g5lKgEAf3U,8587
2
- omlish/__about__.py,sha256=sS7mjsxVPH8xRx_p0z5qBpHSL6REnABss-XCt5y8_Pk,3601
2
+ omlish/__about__.py,sha256=Zu2-AzTYNiCm8KVyeTpwqEU8oCTBUlyG8PBMsAwgeQI,3601
3
3
  omlish/__init__.py,sha256=SsyiITTuK0v74XpKV8dqNaCmjOlan1JZKrHQv5rWKPA,253
4
4
  omlish/c3.py,sha256=rer-TPOFDU6fYq_AWio_AmA-ckZ8JDY5shIzQ_yXfzA,8414
5
5
  omlish/cached.py,sha256=MLap_p0rdGoDIMVhXVHm1tsbcWobJF0OanoodV03Ju8,542
@@ -73,7 +73,7 @@ omlish/codecs/base.py,sha256=IVnJlduvhiH1imul4DPhl2gHBWS76774AV5h86dX0ls,2214
73
73
  omlish/codecs/bytes.py,sha256=3DxyQQCvFcP3mQ5G93f_mygXEb-7I8buM8EyzUcx2Is,2155
74
74
  omlish/codecs/chain.py,sha256=nbkL2nz0ZqT2lxYwSXktHh1YFTQ4Iii1Hp-fWjis6rc,532
75
75
  omlish/codecs/funcs.py,sha256=or0Jogczuzk7csDTRl-HURMEjl8LXXqxxXYK45xcM5w,855
76
- omlish/codecs/registry.py,sha256=DxdfV1Ays5kPB-3fG_s3_ptNVbyUfY8BgjmvEmH2JSI,4132
76
+ omlish/codecs/registry.py,sha256=PDfo0UtVZ96SILe5w4piPpWR-cjh1NiixKeSS7LgQKk,4003
77
77
  omlish/codecs/standard.py,sha256=eiZ4u9ep0XrA4Z_D1zJI0vmWyuN8HLrX4Se_r_Cq_ZM,60
78
78
  omlish/codecs/text.py,sha256=uHhV8jBgH0iZgcrV0nl4-0a_9ofln4iFH4OXoVm2CW4,5709
79
79
  omlish/collections/__init__.py,sha256=BIc806ri5Eq-kR03Ya2YfYTRI0g1rn_0haQPUqxXqys,2816
@@ -82,7 +82,7 @@ omlish/collections/bimap.py,sha256=3szDCscPJlFRtkpyVQNWneg4s50mr6Rd0jdTzVEIcnE,1
82
82
  omlish/collections/coerce.py,sha256=tAls15v_7p5bUN33R7Zbko87KW5toWHl9fRialCqyNY,7030
83
83
  omlish/collections/frozen.py,sha256=LMbAHYDENIQk1hvjCTvpnx66m1TalrHa4CSn8n_tsXQ,4142
84
84
  omlish/collections/hasheq.py,sha256=uHypfZlHhicQPvx9OOlpT9MSLwfc_mFil-WaxF9dTOo,3732
85
- omlish/collections/identity.py,sha256=S_W508EkU-AW0TZ7cv1wWUc6EG54vww4XbcfGjDFTgg,4793
85
+ omlish/collections/identity.py,sha256=00UAYIjCRhyj73Es0jVNvOloXjRxHcjRxGQeWo75OEo,4834
86
86
  omlish/collections/mappings.py,sha256=lAzKP9RONLjFH7emIDoY-6jukuD-Sa7fdxzP7u6qrqU,3261
87
87
  omlish/collections/multimaps.py,sha256=8Sqi1iVxUObnL47s8b1esa2TERoiEyyY4RBWF1VjDiw,3820
88
88
  omlish/collections/ordered.py,sha256=XV_ufr3QycjQ3nIy9L3BufdBosWD-3wb5xVZEHpZocQ,2467
@@ -481,7 +481,7 @@ omlish/lite/args.py,sha256=ILJXAiN3KjIoJwY42aKpYPngUdxHIy9ssVIExFVz3fE,978
481
481
  omlish/lite/cached.py,sha256=O7ozcoDNFm1Hg2wtpHEqYSp_i_nCLNOP6Ueq_Uk-7mU,1300
482
482
  omlish/lite/check.py,sha256=ytCkwZoKfOlJqylL-AGm8C2WfsWJd2q3kFbnZCzX3_M,13844
483
483
  omlish/lite/configs.py,sha256=4-1uVxo-aNV7vMKa7PVNhM610eejG1WepB42-Dw2xQI,914
484
- omlish/lite/contextmanagers.py,sha256=jpMxp5xwooRQJxsQ6J2ll4AJP9O7a5_YrLCGgwUFfD0,5703
484
+ omlish/lite/contextmanagers.py,sha256=QEqVxUtmr9aJJ_03A-YC4vVUy8jkiIDCYIGIOlXvn8U,5827
485
485
  omlish/lite/dataclasses.py,sha256=aRSCZz1jN_UI-CWJhN0SJeKxa-79vXNUZ6YOMgG31SE,3610
486
486
  omlish/lite/imports.py,sha256=GyEDKL-WuHtdOKIL-cc8aFd0-bHwZFDEjAB52ItabX0,1341
487
487
  omlish/lite/inject.py,sha256=xvmLmtD3_2INnkurJQv76_Rkh9usbApEQrXJ4cvuVAk,29019
@@ -518,8 +518,8 @@ omlish/logs/timing.py,sha256=qsQ3DB6swts1pxrFlmLWQzhH-3nzDrq1MUu7PxjjUyU,1519
518
518
  omlish/logs/utils.py,sha256=OkFWf1exmWImmT7BaSiIC7c0Fk9tAis-PRqo8H4ny3c,398
519
519
  omlish/manifests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
520
520
  omlish/manifests/base.py,sha256=Z5afzBJgI0tyTS8mPbYY4pYvoZu_xtdhRBOtZ3IIwzA,929
521
- omlish/manifests/globals.py,sha256=nrA85o8zFTEYv440Y1wgr7SmtW6AE_3jF_Dls6RB_nE,1661
522
- omlish/manifests/loading.py,sha256=kzJwESneZB1mAK6PsVPXvfNjr-MOXL7bbKQ8gEmbVkU,11984
521
+ omlish/manifests/globals.py,sha256=kVqQ-fT4kc7xWzLHoI731GviitFPv2v2yqw-p7t7Exs,2628
522
+ omlish/manifests/loading.py,sha256=tnAxvyJRAncyKihaBH8oqcJmHKaunPXm1Y1OpAi3iQc,15870
523
523
  omlish/manifests/static.py,sha256=7YwOVh_Ek9_aTrWsWNO8kWS10_j4K7yv3TpXZSHsvDY,501
524
524
  omlish/manifests/types.py,sha256=5hQuY-WZ9VMqHZXr-9Dayg380JsnX2vJzXyw6vC6UDs,317
525
525
  omlish/marshal/.dataclasses.json,sha256=wXWUy_IR8AolAa2RQnqn_mo2QnmVcvUJmayIykdVl0I,22
@@ -783,7 +783,7 @@ omlish/testing/testing.py,sha256=tlBeqVmzGhjRbWygJt3mZljq662qR4nAaGNE2VoAey4,361
783
783
  omlish/testing/pytest/__init__.py,sha256=i4ti6Q2rVYJ-XBk9UYDfUUagCrEDTC5jOeSykBjYYZQ,234
784
784
  omlish/testing/pytest/helpers.py,sha256=HxiKvpJQ4b6WCiQXOqQTqKbmr7CMAgCF6hViT6pfIuw,202
785
785
  omlish/testing/pytest/marks.py,sha256=g0RgdvFAd3xog3kKYyb9QtiUVPrEG3JX_abhDNrHJy0,285
786
- omlish/testing/pytest/skip.py,sha256=tra8FM5CZTh4M7ZWVf9YPmKUX4yhesf61XRoIkO4s9c,954
786
+ omlish/testing/pytest/skip.py,sha256=d4ivXMnnCXVxiRfIN17RzweodExTeWY0qeFf8gmEJhM,1061
787
787
  omlish/testing/pytest/inject/__init__.py,sha256=pdRKv1HcDmJ_yArKJbYITPXXZthRSGgBJWqITr0Er38,117
788
788
  omlish/testing/pytest/inject/harness.py,sha256=uaFDli1ovhue-kfXV5WXyLn6cVOWNQf_2vFD13r-JyQ,5700
789
789
  omlish/testing/pytest/plugins/__init__.py,sha256=ys1zXrYrNm7Uo6YOIVJ6Bd3dQo6kv387k7MbTYlqZSI,467
@@ -908,9 +908,9 @@ omlish/typedvalues/marshal.py,sha256=AtBz7Jq-BfW8vwM7HSxSpR85JAXmxK2T0xDblmm1HI0
908
908
  omlish/typedvalues/of_.py,sha256=UXkxSj504WI2UrFlqdZJbu2hyDwBhL7XVrc2qdR02GQ,1309
909
909
  omlish/typedvalues/reflect.py,sha256=PAvKW6T4cW7u--iX80w3HWwZUS3SmIZ2_lQjT65uAyk,1026
910
910
  omlish/typedvalues/values.py,sha256=ym46I-q2QJ_6l4UlERqv3yj87R-kp8nCKMRph0xQ3UA,1307
911
- omlish-0.0.0.dev403.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
912
- omlish-0.0.0.dev403.dist-info/METADATA,sha256=AFfMDWuU93a0pAwqEi7fa2ctw0_KONoRYd-MEI5u9NI,18881
913
- omlish-0.0.0.dev403.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
914
- omlish-0.0.0.dev403.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
915
- omlish-0.0.0.dev403.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
916
- omlish-0.0.0.dev403.dist-info/RECORD,,
911
+ omlish-0.0.0.dev405.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
912
+ omlish-0.0.0.dev405.dist-info/METADATA,sha256=y4Y668MaUQry-Qf4i3hWdu6-nugPJgTmu-hknnshYZI,18881
913
+ omlish-0.0.0.dev405.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
914
+ omlish-0.0.0.dev405.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
915
+ omlish-0.0.0.dev405.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
916
+ omlish-0.0.0.dev405.dist-info/RECORD,,