exonware-xwlazy 0.1.0.23__py3-none-any.whl → 1.0.1.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 (96) hide show
  1. exonware/__init__.py +85 -34
  2. exonware/xwlazy/version.py +5 -5
  3. exonware/xwlazy.py +2546 -0
  4. exonware/xwlazy_external_libs.toml +716 -0
  5. {exonware_xwlazy-0.1.0.23.dist-info → exonware_xwlazy-1.0.1.2.dist-info}/METADATA +5 -1
  6. exonware_xwlazy-1.0.1.2.dist-info/RECORD +8 -0
  7. exonware/xwlazy/__init__.py +0 -379
  8. exonware/xwlazy/common/__init__.py +0 -55
  9. exonware/xwlazy/common/base.py +0 -65
  10. exonware/xwlazy/common/cache.py +0 -504
  11. exonware/xwlazy/common/logger.py +0 -257
  12. exonware/xwlazy/common/services/__init__.py +0 -72
  13. exonware/xwlazy/common/services/dependency_mapper.py +0 -250
  14. exonware/xwlazy/common/services/install_async_utils.py +0 -170
  15. exonware/xwlazy/common/services/install_cache_utils.py +0 -245
  16. exonware/xwlazy/common/services/keyword_detection.py +0 -283
  17. exonware/xwlazy/common/services/spec_cache.py +0 -165
  18. exonware/xwlazy/common/services/state_manager.py +0 -84
  19. exonware/xwlazy/common/strategies/__init__.py +0 -28
  20. exonware/xwlazy/common/strategies/caching_dict.py +0 -44
  21. exonware/xwlazy/common/strategies/caching_installation.py +0 -88
  22. exonware/xwlazy/common/strategies/caching_lfu.py +0 -66
  23. exonware/xwlazy/common/strategies/caching_lru.py +0 -63
  24. exonware/xwlazy/common/strategies/caching_multitier.py +0 -59
  25. exonware/xwlazy/common/strategies/caching_ttl.py +0 -59
  26. exonware/xwlazy/common/utils.py +0 -142
  27. exonware/xwlazy/config.py +0 -193
  28. exonware/xwlazy/contracts.py +0 -1533
  29. exonware/xwlazy/defs.py +0 -378
  30. exonware/xwlazy/errors.py +0 -276
  31. exonware/xwlazy/facade.py +0 -1137
  32. exonware/xwlazy/host/__init__.py +0 -8
  33. exonware/xwlazy/host/conf.py +0 -16
  34. exonware/xwlazy/module/__init__.py +0 -18
  35. exonware/xwlazy/module/base.py +0 -622
  36. exonware/xwlazy/module/data.py +0 -17
  37. exonware/xwlazy/module/facade.py +0 -246
  38. exonware/xwlazy/module/importer_engine.py +0 -2964
  39. exonware/xwlazy/module/partial_module_detector.py +0 -275
  40. exonware/xwlazy/module/strategies/__init__.py +0 -22
  41. exonware/xwlazy/module/strategies/module_helper_lazy.py +0 -93
  42. exonware/xwlazy/module/strategies/module_helper_simple.py +0 -65
  43. exonware/xwlazy/module/strategies/module_manager_advanced.py +0 -111
  44. exonware/xwlazy/module/strategies/module_manager_simple.py +0 -95
  45. exonware/xwlazy/package/__init__.py +0 -18
  46. exonware/xwlazy/package/base.py +0 -863
  47. exonware/xwlazy/package/conf.py +0 -324
  48. exonware/xwlazy/package/data.py +0 -17
  49. exonware/xwlazy/package/facade.py +0 -480
  50. exonware/xwlazy/package/services/__init__.py +0 -84
  51. exonware/xwlazy/package/services/async_install_handle.py +0 -87
  52. exonware/xwlazy/package/services/config_manager.py +0 -249
  53. exonware/xwlazy/package/services/discovery.py +0 -435
  54. exonware/xwlazy/package/services/host_packages.py +0 -180
  55. exonware/xwlazy/package/services/install_async.py +0 -291
  56. exonware/xwlazy/package/services/install_cache.py +0 -145
  57. exonware/xwlazy/package/services/install_interactive.py +0 -59
  58. exonware/xwlazy/package/services/install_policy.py +0 -156
  59. exonware/xwlazy/package/services/install_registry.py +0 -54
  60. exonware/xwlazy/package/services/install_result.py +0 -17
  61. exonware/xwlazy/package/services/install_sbom.py +0 -153
  62. exonware/xwlazy/package/services/install_utils.py +0 -79
  63. exonware/xwlazy/package/services/installer_engine.py +0 -406
  64. exonware/xwlazy/package/services/lazy_installer.py +0 -803
  65. exonware/xwlazy/package/services/manifest.py +0 -503
  66. exonware/xwlazy/package/services/strategy_registry.py +0 -324
  67. exonware/xwlazy/package/strategies/__init__.py +0 -57
  68. exonware/xwlazy/package/strategies/package_discovery_file.py +0 -129
  69. exonware/xwlazy/package/strategies/package_discovery_hybrid.py +0 -84
  70. exonware/xwlazy/package/strategies/package_discovery_manifest.py +0 -101
  71. exonware/xwlazy/package/strategies/package_execution_async.py +0 -113
  72. exonware/xwlazy/package/strategies/package_execution_cached.py +0 -90
  73. exonware/xwlazy/package/strategies/package_execution_pip.py +0 -99
  74. exonware/xwlazy/package/strategies/package_execution_wheel.py +0 -106
  75. exonware/xwlazy/package/strategies/package_mapping_discovery_first.py +0 -100
  76. exonware/xwlazy/package/strategies/package_mapping_hybrid.py +0 -105
  77. exonware/xwlazy/package/strategies/package_mapping_manifest_first.py +0 -100
  78. exonware/xwlazy/package/strategies/package_policy_allow_list.py +0 -57
  79. exonware/xwlazy/package/strategies/package_policy_deny_list.py +0 -57
  80. exonware/xwlazy/package/strategies/package_policy_permissive.py +0 -46
  81. exonware/xwlazy/package/strategies/package_timing_clean.py +0 -67
  82. exonware/xwlazy/package/strategies/package_timing_full.py +0 -66
  83. exonware/xwlazy/package/strategies/package_timing_smart.py +0 -68
  84. exonware/xwlazy/package/strategies/package_timing_temporary.py +0 -66
  85. exonware/xwlazy/runtime/__init__.py +0 -18
  86. exonware/xwlazy/runtime/adaptive_learner.py +0 -129
  87. exonware/xwlazy/runtime/base.py +0 -274
  88. exonware/xwlazy/runtime/facade.py +0 -94
  89. exonware/xwlazy/runtime/intelligent_selector.py +0 -170
  90. exonware/xwlazy/runtime/metrics.py +0 -60
  91. exonware/xwlazy/runtime/performance.py +0 -37
  92. exonware_xwlazy-0.1.0.23.dist-info/RECORD +0 -93
  93. xwlazy/__init__.py +0 -14
  94. xwlazy/lazy.py +0 -30
  95. {exonware_xwlazy-0.1.0.23.dist-info → exonware_xwlazy-1.0.1.2.dist-info}/WHEEL +0 -0
  96. {exonware_xwlazy-0.1.0.23.dist-info → exonware_xwlazy-1.0.1.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,503 +0,0 @@
1
- from __future__ import annotations
2
-
3
- """
4
- xwlazy.common.utils.manifest
5
- ----------------------------
6
-
7
- Centralized loader for per-package dependency manifests. A manifest can be
8
- declared either as a JSON file located in the target project's root directory
9
- or inline inside ``pyproject.toml`` under the ``[tool.xwlazy]`` namespace.
10
-
11
- The loader consolidates the following pieces of information:
12
-
13
- * Explicit import -> package mappings
14
- * Serialization/watch prefixes that should be handled by the import hook
15
- * Async installation preferences (queue enabled + worker count)
16
-
17
- It also keeps lightweight caches with file-modification tracking so repeated
18
- lookups do not hit the filesystem unnecessarily.
19
- """
20
-
21
- from dataclasses import field
22
- import importlib.util
23
- import json
24
- import os
25
- from pathlib import Path
26
- from threading import RLock
27
- from typing import Any, Iterable, Optional
28
-
29
- try: # Python 3.11+
30
- import tomllib # type: ignore[attr-defined]
31
- except Exception: # pragma: no cover - fallback for <=3.10
32
- try:
33
- import tomli as tomllib # type: ignore[attr-defined]
34
- except ImportError: # pragma: no cover
35
- tomllib = None # type: ignore
36
-
37
- DEFAULT_MANIFEST_FILENAMES: tuple[str, ...] = (
38
- "xwlazy.manifest.json",
39
- "lazy.manifest.json",
40
- ".xwlazy.manifest.json",
41
- )
42
-
43
- ENV_MANIFEST_PATH = "XWLAZY_MANIFEST_PATH"
44
-
45
- def _normalize_package_name(package_name: Optional[str]) -> str:
46
- return (package_name or "global").strip().lower()
47
-
48
- def _normalize_prefix(prefix: str) -> str:
49
- prefix = prefix.strip()
50
- if not prefix:
51
- return ""
52
- if not prefix.endswith("."):
53
- prefix = f"{prefix}."
54
- return prefix
55
-
56
- def _normalize_wrap_hints(values: Iterable[Any]) -> list[str]:
57
- hints: list[str] = []
58
- for value in values:
59
- if value is None:
60
- continue
61
- hint = str(value).strip().lower()
62
- if hint:
63
- hints.append(hint)
64
- return hints
65
-
66
- # PackageManifest moved to defs.py - import it from there
67
- from ...defs import PackageManifest
68
-
69
- class LazyManifestLoader:
70
- """
71
- Loads and caches manifest data per package.
72
-
73
- Args:
74
- default_root: Optional fallback root directory used when a package
75
- root cannot be auto-detected.
76
- package_roots: Optional explicit mapping used mainly for tests.
77
- """
78
-
79
- def __init__(
80
- self,
81
- default_root: Optional[Path] = None,
82
- package_roots: Optional[dict[str, Path]] = None,
83
- ) -> None:
84
- self._default_root = default_root
85
- self._provided_roots = {
86
- _normalize_package_name(name): Path(path)
87
- for name, path in (package_roots or {}).items()
88
- }
89
- self._manifest_cache: dict[str, PackageManifest] = {}
90
- self._source_signatures: dict[str, tuple[str, float, float]] = {}
91
- self._pyproject_cache: dict[Path, tuple[float, dict[str, Any]]] = {}
92
- self._shared_dependency_maps: dict[
93
- tuple[str, float, float], dict[str, dict[str, str]]
94
- ] = {}
95
- self._lock = RLock()
96
- self._generation = 0
97
-
98
- @property
99
- def generation(self) -> int:
100
- """Incremented whenever any manifest content changes."""
101
- return self._generation
102
-
103
- def clear_cache(self) -> None:
104
- """Forcefully clear cached manifests."""
105
- with self._lock:
106
- self._manifest_cache.clear()
107
- self._source_signatures.clear()
108
- self._pyproject_cache.clear()
109
- self._shared_dependency_maps.clear()
110
- self._generation += 1
111
-
112
- def sync_manifest_configuration(self, package_name: str) -> None:
113
- """
114
- Sync configuration from manifest for a specific package.
115
-
116
- This method forces a reload of the manifest and clears caches
117
- to ensure the latest configuration is used.
118
-
119
- Args:
120
- package_name: The package name to sync configuration for
121
- """
122
- with self._lock:
123
- # Clear cache for this package
124
- package_key = _normalize_package_name(package_name)
125
- if package_key in self._manifest_cache:
126
- del self._manifest_cache[package_key]
127
- if package_key in self._source_signatures:
128
- del self._source_signatures[package_key]
129
- # Increment generation to invalidate shared caches
130
- self._generation += 1
131
- # Force reload by getting manifest
132
- self.get_manifest(package_key)
133
-
134
- # --------------------------------------------------------------------- #
135
- # Public API
136
- # --------------------------------------------------------------------- #
137
- def get_manifest(self, package_name: Optional[str]) -> Optional[PackageManifest]:
138
- """Return manifest for the provided package."""
139
- key = _normalize_package_name(package_name)
140
- with self._lock:
141
- signature = self._compute_signature(key)
142
- cached_signature = self._source_signatures.get(key)
143
- if (
144
- cached_signature is not None
145
- and signature is not None
146
- and cached_signature == signature
147
- and key in self._manifest_cache
148
- ):
149
- return self._manifest_cache[key]
150
-
151
- manifest = self._load_manifest(key)
152
- if manifest is None:
153
- # Cache miss is still tracked so we don't re-read files
154
- self._manifest_cache.pop(key, None)
155
- self._source_signatures[key] = signature or ("", 0.0, 0.0)
156
- return None
157
-
158
- self._manifest_cache[key] = manifest
159
- if signature is not None:
160
- self._source_signatures[key] = signature
161
- per_signature = self._shared_dependency_maps.setdefault(signature, {})
162
- per_signature[manifest.package] = manifest.dependencies.copy()
163
- self._generation += 1
164
- return manifest
165
-
166
- def get_manifest_signature(self, package_name: Optional[str]) -> Optional[tuple[str, float, float]]:
167
- key = _normalize_package_name(package_name)
168
- with self._lock:
169
- signature = self._source_signatures.get(key)
170
- if signature is not None:
171
- return signature
172
- signature = self._compute_signature(key)
173
- if signature is not None:
174
- self._source_signatures[key] = signature
175
- return signature
176
-
177
- def get_shared_dependencies(
178
- self,
179
- package_name: Optional[str],
180
- signature: Optional[tuple[str, float, float]],
181
- ) -> Optional[dict[str, str]]:
182
- if signature is None:
183
- return None
184
- with self._lock:
185
- package_maps = self._shared_dependency_maps.get(signature)
186
- if not package_maps:
187
- return None
188
- key = _normalize_package_name(package_name)
189
- return package_maps.get(key)
190
-
191
- # ------------------------------------------------------------------ #
192
- # Internal helpers
193
- # ------------------------------------------------------------------ #
194
- def _load_manifest(self, package_key: str) -> Optional[PackageManifest]:
195
- root = self._resolve_project_root(package_key)
196
- pyproject_path = root / "pyproject.toml"
197
- pyproject_data = self._load_pyproject(pyproject_path)
198
- json_data, manifest_path = self._load_json_manifest(root, pyproject_data)
199
-
200
- data = self._merge_sources(package_key, pyproject_data, json_data)
201
- if not data["dependencies"] and not data["watched_prefixes"] and not data["async_installs"]:
202
- return None
203
-
204
- wrap_prefixes = tuple(data.get("wrap_class_prefixes", ()))
205
-
206
- manifest = PackageManifest(
207
- package=package_key,
208
- dependencies=data["dependencies"],
209
- watched_prefixes=tuple(
210
- _normalize_prefix(prefix)
211
- for prefix in data["watched_prefixes"]
212
- if _normalize_prefix(prefix)
213
- ),
214
- async_installs=bool(data.get("async_installs")),
215
- async_workers=max(1, int(data.get("async_workers", 1))),
216
- class_wrap_prefixes=wrap_prefixes,
217
- metadata={
218
- "root": str(root),
219
- "manifest_path": str(manifest_path) if manifest_path else None,
220
- "wrap_class_prefixes": wrap_prefixes,
221
- },
222
- )
223
- return manifest
224
-
225
- def _compute_signature(self, package_key: str) -> Optional[tuple[str, float, float]]:
226
- root = self._resolve_project_root(package_key)
227
- pyproject_path = root / "pyproject.toml"
228
- pyproject_mtime = pyproject_path.stat().st_mtime if pyproject_path.exists() else 0.0
229
- manifest_path = self._resolve_manifest_path(root, pyproject_path)
230
- json_mtime = manifest_path.stat().st_mtime if manifest_path and manifest_path.exists() else 0.0
231
- env_token = os.environ.get(ENV_MANIFEST_PATH, "")
232
- if not manifest_path and not pyproject_path.exists() and not env_token:
233
- return None
234
- return (env_token + str(manifest_path), pyproject_mtime, json_mtime)
235
-
236
- def _resolve_project_root(self, package_key: str) -> Path:
237
- if package_key in self._provided_roots:
238
- return self._provided_roots[package_key]
239
-
240
- module_candidates: Iterable[str]
241
- if package_key == "global":
242
- module_candidates = ()
243
- else:
244
- module_candidates = (f"exonware.{package_key}", package_key)
245
-
246
- for module_name in module_candidates:
247
- spec = importlib.util.find_spec(module_name)
248
- if spec and spec.origin:
249
- origin_path = Path(spec.origin).resolve()
250
- root = self._walk_to_project_root(origin_path.parent)
251
- if root:
252
- self._provided_roots[package_key] = root
253
- return root
254
-
255
- if self._default_root:
256
- return self._default_root
257
- return Path.cwd()
258
-
259
- @staticmethod
260
- def _walk_to_project_root(start: Path) -> Optional[Path]:
261
- """Walk up from start path to find project root."""
262
- from ...common.utils import find_project_root
263
- # Use utility function, but start from the provided path
264
- try:
265
- return find_project_root(start)
266
- except Exception:
267
- # Fallback: simple walk-up logic
268
- current = start
269
- while True:
270
- if (current / "pyproject.toml").exists():
271
- return current
272
- parent = current.parent
273
- if parent == current:
274
- break
275
- current = parent
276
- return None
277
-
278
- # ------------------------------- #
279
- # Pyproject helpers
280
- # ------------------------------- #
281
- def _load_pyproject(self, path: Path) -> dict[str, Any]:
282
- if not path.exists() or tomllib is None:
283
- return {}
284
-
285
- cached = self._pyproject_cache.get(path)
286
- current_mtime = path.stat().st_mtime
287
- if cached and cached[0] == current_mtime:
288
- return cached[1]
289
-
290
- try:
291
- with path.open("rb") as handle:
292
- data = tomllib.load(handle)
293
- except Exception:
294
- data = {}
295
-
296
- self._pyproject_cache[path] = (current_mtime, data)
297
- return data
298
-
299
- def _extract_pyproject_entry(
300
- self,
301
- pyproject_data: dict[str, Any],
302
- package_key: str,
303
- ) -> dict[str, Any]:
304
- tool_section = pyproject_data.get("tool", {})
305
- lazy_section = tool_section.get("xwlazy", {})
306
- packages = lazy_section.get("packages", {})
307
- entry = packages.get(package_key, {}) or packages.get(package_key.upper(), {})
308
-
309
- dependencies = {}
310
- watched = []
311
- async_installs = lazy_section.get("async_installs") or entry.get("async_installs")
312
- async_workers = entry.get("async_workers") or lazy_section.get("async_workers")
313
- wrap_hints = []
314
-
315
- global_deps = lazy_section.get("dependencies", {})
316
- if isinstance(global_deps, dict):
317
- dependencies.update({str(k): str(v) for k, v in global_deps.items()})
318
-
319
- if "dependencies" in entry and isinstance(entry["dependencies"], dict):
320
- dependencies.update({str(k): str(v) for k, v in entry["dependencies"].items()})
321
-
322
- for key in ("watched-prefixes", "watched_prefixes", "watch"):
323
- values = entry.get(key) or lazy_section.get(key)
324
- if isinstance(values, list):
325
- watched.extend(str(v) for v in values)
326
-
327
- global_wrap = lazy_section.get("wrap_class_prefixes") or lazy_section.get("wrap_classes")
328
- if isinstance(global_wrap, list):
329
- wrap_hints.extend(_normalize_wrap_hints(global_wrap))
330
- entry_wrap = entry.get("wrap_class_prefixes") or entry.get("wrap_classes")
331
- if isinstance(entry_wrap, list):
332
- wrap_hints.extend(_normalize_wrap_hints(entry_wrap))
333
-
334
- return {
335
- "dependencies": dependencies,
336
- "watched_prefixes": watched,
337
- "async_installs": bool(async_installs),
338
- "async_workers": async_workers or 1,
339
- "wrap_class_prefixes": wrap_hints,
340
- }
341
-
342
- # ------------------------------- #
343
- # Manifest helpers
344
- # ------------------------------- #
345
- def _resolve_manifest_path(self, root: Path, pyproject_path: Path) -> Optional[Path]:
346
- env_value = os.environ.get(ENV_MANIFEST_PATH)
347
- if env_value:
348
- for raw in env_value.split(os.pathsep):
349
- candidate = Path(raw).expanduser()
350
- if candidate.exists():
351
- return candidate
352
-
353
- if pyproject_path.exists() and tomllib is not None:
354
- py_data = self._load_pyproject(pyproject_path)
355
- tool_section = py_data.get("tool", {}).get("xwlazy", {})
356
- manifest_path = tool_section.get("manifest") or tool_section.get("manifest_path")
357
- if manifest_path:
358
- candidate = (root / manifest_path).resolve()
359
- if candidate.exists():
360
- return candidate
361
-
362
- for filename in DEFAULT_MANIFEST_FILENAMES:
363
- candidate = root / filename
364
- if candidate.exists():
365
- return candidate
366
-
367
- return None
368
-
369
- def _load_json_manifest(
370
- self,
371
- root: Path,
372
- pyproject_data: dict[str, Any],
373
- ) -> tuple[dict[str, Any], Optional[Path]]:
374
- manifest_path = self._resolve_manifest_path(root, root / "pyproject.toml")
375
- if not manifest_path:
376
- return {}, None
377
-
378
- try:
379
- with manifest_path.open("r", encoding="utf-8") as handle:
380
- data = json.load(handle)
381
- if isinstance(data, dict):
382
- return data, manifest_path
383
- except Exception:
384
- pass
385
-
386
- return {}, manifest_path
387
-
388
- def _merge_sources(
389
- self,
390
- package_key: str,
391
- pyproject_data: dict[str, Any],
392
- json_data: dict[str, Any],
393
- ) -> dict[str, Any]:
394
- merged_dependencies: dict[str, str] = {}
395
- merged_watched: list[str] = []
396
- merged_wrap_hints: list[str] = []
397
-
398
- # Pyproject first (acts as baseline)
399
- py_entry = self._extract_pyproject_entry(pyproject_data, package_key)
400
- merged_dependencies.update({k.lower(): v for k, v in py_entry["dependencies"].items()})
401
- merged_watched.extend(py_entry["watched_prefixes"])
402
- merged_wrap_hints.extend(_normalize_wrap_hints(py_entry.get("wrap_class_prefixes", [])))
403
-
404
- async_installs = py_entry.get("async_installs", False)
405
- async_workers = py_entry.get("async_workers", 1)
406
-
407
- # JSON global settings
408
- global_deps = json_data.get("dependencies", {})
409
- if isinstance(global_deps, dict):
410
- merged_dependencies.update({str(k).lower(): str(v) for k, v in global_deps.items()})
411
-
412
- global_watch = json_data.get("watch") or json_data.get("watched_prefixes")
413
- if isinstance(global_watch, list):
414
- merged_watched.extend(str(item) for item in global_watch)
415
- global_wrap = json_data.get("wrap_class_prefixes") or json_data.get("wrap_classes")
416
- if isinstance(global_wrap, list):
417
- merged_wrap_hints.extend(_normalize_wrap_hints(global_wrap))
418
-
419
- global_async = json_data.get("async_installs")
420
- if global_async is not None:
421
- async_installs = bool(global_async)
422
- global_workers = json_data.get("async_workers")
423
- if global_workers is not None:
424
- async_workers = global_workers
425
-
426
- packages_section = json_data.get("packages", {})
427
- if isinstance(packages_section, dict):
428
- entry = packages_section.get(package_key) or packages_section.get(package_key.upper())
429
- if isinstance(entry, dict):
430
- entry_deps = entry.get("dependencies", {})
431
- if isinstance(entry_deps, dict):
432
- merged_dependencies.update({str(k).lower(): str(v) for k, v in entry_deps.items()})
433
-
434
- entry_watch = entry.get("watched_prefixes") or entry.get("watch")
435
- if isinstance(entry_watch, list):
436
- merged_watched.extend(str(item) for item in entry_watch)
437
- entry_wrap = entry.get("wrap_class_prefixes") or entry.get("wrap_classes")
438
- if isinstance(entry_wrap, list):
439
- merged_wrap_hints.extend(_normalize_wrap_hints(entry_wrap))
440
-
441
- if "async_installs" in entry:
442
- async_installs = bool(entry["async_installs"])
443
- if "async_workers" in entry:
444
- async_workers = entry.get("async_workers", async_workers)
445
-
446
- seen_wrap: set[str] = set()
447
- ordered_wrap_hints: list[str] = []
448
- for hint in merged_wrap_hints:
449
- if hint not in seen_wrap:
450
- seen_wrap.add(hint)
451
- ordered_wrap_hints.append(hint)
452
-
453
- return {
454
- "dependencies": merged_dependencies,
455
- "watched_prefixes": merged_watched,
456
- "async_installs": async_installs,
457
- "async_workers": async_workers,
458
- "wrap_class_prefixes": ordered_wrap_hints,
459
- }
460
-
461
- _manifest_loader: Optional[LazyManifestLoader] = None
462
- _manifest_loader_lock = RLock()
463
-
464
- def get_manifest_loader() -> LazyManifestLoader:
465
- """
466
- Return the process-wide manifest loader instance.
467
-
468
- Calling this function does not force any manifest to be loaded, but the
469
- loader keeps shared caches that allow multiple subsystems (dependency mapper,
470
- installer, hook configuration) to observe manifest changes consistently.
471
- """
472
- global _manifest_loader
473
- with _manifest_loader_lock:
474
- if _manifest_loader is None:
475
- _manifest_loader = LazyManifestLoader()
476
- return _manifest_loader
477
-
478
- def refresh_manifest_cache() -> None:
479
- """Forcefully clear the shared manifest loader cache."""
480
- loader = get_manifest_loader()
481
- loader.clear_cache()
482
-
483
- def sync_manifest_configuration(package_name: str) -> None:
484
- """
485
- Sync configuration from manifest for a specific package.
486
-
487
- This is a convenience function that calls the manifest loader's
488
- sync_manifest_configuration method.
489
-
490
- Args:
491
- package_name: The package name to sync configuration for
492
- """
493
- loader = get_manifest_loader()
494
- loader.sync_manifest_configuration(package_name)
495
-
496
- __all__ = [
497
- "PackageManifest",
498
- "LazyManifestLoader",
499
- "get_manifest_loader",
500
- "refresh_manifest_cache",
501
- "sync_manifest_configuration",
502
- ]
503
-