exonware-xwlazy 0.1.0.11__py3-none-any.whl → 0.1.0.20__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 +26 -0
  2. exonware/xwlazy/__init__.py +0 -0
  3. exonware/xwlazy/common/__init__.py +47 -0
  4. exonware/xwlazy/common/base.py +56 -0
  5. exonware/xwlazy/common/cache.py +504 -0
  6. exonware/xwlazy/common/logger.py +257 -0
  7. exonware/xwlazy/common/services/__init__.py +72 -0
  8. exonware/xwlazy/common/services/dependency_mapper.py +232 -0
  9. exonware/xwlazy/common/services/install_async_utils.py +165 -0
  10. exonware/xwlazy/common/services/install_cache_utils.py +245 -0
  11. exonware/xwlazy/common/services/keyword_detection.py +283 -0
  12. exonware/xwlazy/common/services/spec_cache.py +165 -0
  13. xwlazy/lazy/lazy_state.py → exonware/xwlazy/common/services/state_manager.py +0 -2
  14. exonware/xwlazy/common/strategies/__init__.py +28 -0
  15. exonware/xwlazy/common/strategies/caching_dict.py +44 -0
  16. exonware/xwlazy/common/strategies/caching_installation.py +88 -0
  17. exonware/xwlazy/common/strategies/caching_lfu.py +66 -0
  18. exonware/xwlazy/common/strategies/caching_lru.py +63 -0
  19. exonware/xwlazy/common/strategies/caching_multitier.py +59 -0
  20. exonware/xwlazy/common/strategies/caching_ttl.py +59 -0
  21. {xwlazy/lazy → exonware/xwlazy}/config.py +51 -21
  22. exonware/xwlazy/contracts.py +1396 -0
  23. exonware/xwlazy/defs.py +378 -0
  24. xwlazy/lazy/lazy_errors.py → exonware/xwlazy/errors.py +21 -16
  25. exonware/xwlazy/facade.py +991 -0
  26. exonware/xwlazy/module/__init__.py +18 -0
  27. exonware/xwlazy/module/base.py +565 -0
  28. exonware/xwlazy/module/data.py +17 -0
  29. exonware/xwlazy/module/facade.py +246 -0
  30. exonware/xwlazy/module/importer_engine.py +2117 -0
  31. exonware/xwlazy/module/strategies/__init__.py +22 -0
  32. exonware/xwlazy/module/strategies/module_helper_lazy.py +93 -0
  33. exonware/xwlazy/module/strategies/module_helper_simple.py +65 -0
  34. exonware/xwlazy/module/strategies/module_manager_advanced.py +111 -0
  35. exonware/xwlazy/module/strategies/module_manager_simple.py +95 -0
  36. exonware/xwlazy/package/__init__.py +18 -0
  37. exonware/xwlazy/package/base.py +798 -0
  38. xwlazy/lazy/host_conf.py → exonware/xwlazy/package/conf.py +61 -16
  39. exonware/xwlazy/package/data.py +17 -0
  40. exonware/xwlazy/package/facade.py +480 -0
  41. exonware/xwlazy/package/services/__init__.py +84 -0
  42. exonware/xwlazy/package/services/async_install_handle.py +87 -0
  43. exonware/xwlazy/package/services/config_manager.py +245 -0
  44. exonware/xwlazy/package/services/discovery.py +370 -0
  45. {xwlazy/lazy → exonware/xwlazy/package/services}/host_packages.py +43 -20
  46. exonware/xwlazy/package/services/install_async.py +277 -0
  47. exonware/xwlazy/package/services/install_cache.py +145 -0
  48. exonware/xwlazy/package/services/install_interactive.py +59 -0
  49. exonware/xwlazy/package/services/install_policy.py +156 -0
  50. exonware/xwlazy/package/services/install_registry.py +54 -0
  51. exonware/xwlazy/package/services/install_result.py +17 -0
  52. exonware/xwlazy/package/services/install_sbom.py +153 -0
  53. exonware/xwlazy/package/services/install_utils.py +79 -0
  54. exonware/xwlazy/package/services/installer_engine.py +406 -0
  55. exonware/xwlazy/package/services/lazy_installer.py +718 -0
  56. {xwlazy/lazy → exonware/xwlazy/package/services}/manifest.py +40 -33
  57. exonware/xwlazy/package/services/strategy_registry.py +186 -0
  58. exonware/xwlazy/package/strategies/__init__.py +57 -0
  59. exonware/xwlazy/package/strategies/package_discovery_file.py +129 -0
  60. exonware/xwlazy/package/strategies/package_discovery_hybrid.py +84 -0
  61. exonware/xwlazy/package/strategies/package_discovery_manifest.py +101 -0
  62. exonware/xwlazy/package/strategies/package_execution_async.py +113 -0
  63. exonware/xwlazy/package/strategies/package_execution_cached.py +90 -0
  64. exonware/xwlazy/package/strategies/package_execution_pip.py +99 -0
  65. exonware/xwlazy/package/strategies/package_execution_wheel.py +106 -0
  66. exonware/xwlazy/package/strategies/package_mapping_discovery_first.py +100 -0
  67. exonware/xwlazy/package/strategies/package_mapping_hybrid.py +105 -0
  68. exonware/xwlazy/package/strategies/package_mapping_manifest_first.py +100 -0
  69. exonware/xwlazy/package/strategies/package_policy_allow_list.py +57 -0
  70. exonware/xwlazy/package/strategies/package_policy_deny_list.py +57 -0
  71. exonware/xwlazy/package/strategies/package_policy_permissive.py +46 -0
  72. exonware/xwlazy/package/strategies/package_timing_clean.py +67 -0
  73. exonware/xwlazy/package/strategies/package_timing_full.py +66 -0
  74. exonware/xwlazy/package/strategies/package_timing_smart.py +68 -0
  75. exonware/xwlazy/package/strategies/package_timing_temporary.py +66 -0
  76. exonware/xwlazy/runtime/__init__.py +18 -0
  77. exonware/xwlazy/runtime/adaptive_learner.py +129 -0
  78. exonware/xwlazy/runtime/base.py +274 -0
  79. exonware/xwlazy/runtime/facade.py +94 -0
  80. exonware/xwlazy/runtime/intelligent_selector.py +170 -0
  81. exonware/xwlazy/runtime/metrics.py +60 -0
  82. exonware/xwlazy/runtime/performance.py +37 -0
  83. exonware/xwlazy/version.py +2 -2
  84. {exonware_xwlazy-0.1.0.11.dist-info → exonware_xwlazy-0.1.0.20.dist-info}/METADATA +89 -11
  85. exonware_xwlazy-0.1.0.20.dist-info/RECORD +87 -0
  86. exonware_xwlazy-0.1.0.11.dist-info/RECORD +0 -20
  87. xwlazy/__init__.py +0 -34
  88. xwlazy/lazy/__init__.py +0 -301
  89. xwlazy/lazy/bootstrap.py +0 -106
  90. xwlazy/lazy/lazy_base.py +0 -465
  91. xwlazy/lazy/lazy_contracts.py +0 -290
  92. xwlazy/lazy/lazy_core.py +0 -3727
  93. xwlazy/lazy/logging_utils.py +0 -194
  94. xwlazy/version.py +0 -77
  95. {exonware_xwlazy-0.1.0.11.dist-info → exonware_xwlazy-0.1.0.20.dist-info}/WHEEL +0 -0
  96. {exonware_xwlazy-0.1.0.11.dist-info → exonware_xwlazy-0.1.0.20.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,370 @@
1
+ """
2
+ #exonware/xwlazy/src/exonware/xwlazy/discovery/discovery.py
3
+
4
+ Package discovery implementation.
5
+
6
+ Company: eXonware.com
7
+ Author: Eng. Muhammad AlShehri
8
+ Email: connect@exonware.com
9
+
10
+ Generation Date: 10-Oct-2025
11
+
12
+ This module provides LazyDiscovery class that discovers dependencies from
13
+ project configuration sources with caching support.
14
+ """
15
+
16
+ import json
17
+ import re
18
+ import subprocess
19
+ import sys
20
+ import threading
21
+ from pathlib import Path
22
+ from typing import Dict, List, Optional
23
+
24
+ from ..base import APackageHelper
25
+ from ...defs import DependencyInfo
26
+ from ...common.logger import get_logger, log_event as _log
27
+
28
+ logger = get_logger("xwlazy.discovery")
29
+
30
+ class LazyDiscovery(APackageHelper):
31
+ """
32
+ Discovers dependencies from project configuration sources.
33
+ Implements caching with file modification time checks.
34
+ """
35
+
36
+ # System/built-in modules that should NEVER be auto-installed
37
+ SYSTEM_MODULES_BLACKLIST = {
38
+ 'pwd', 'grp', 'spwd', 'crypt', 'nis', 'syslog', 'termios', 'tty', 'pty',
39
+ 'fcntl', 'resource', 'msvcrt', 'winreg', 'winsound', '_winapi',
40
+ 'rpython', 'rply', 'rnc2rng', '_dbm',
41
+ 'sys', 'os', 'io', 'time', 'datetime', 'json', 'csv', 'math',
42
+ 'random', 're', 'collections', 'itertools', 'functools', 'operator',
43
+ 'pathlib', 'shutil', 'glob', 'tempfile', 'pickle', 'copy', 'types',
44
+ 'typing', 'abc', 'enum', 'dataclasses', 'contextlib', 'warnings',
45
+ 'logging', 'threading', 'multiprocessing', 'subprocess', 'queue',
46
+ 'socket', 'select', 'signal', 'asyncio', 'concurrent', 'email',
47
+ 'http', 'urllib', 'xml', 'html', 'sqlite3', 'base64', 'hashlib',
48
+ 'hmac', 'secrets', 'ssl', 'binascii', 'struct', 'array', 'weakref',
49
+ 'gc', 'inspect', 'traceback', 'atexit', 'codecs', 'locale', 'gettext',
50
+ 'argparse', 'optparse', 'configparser', 'fileinput', 'stat', 'platform',
51
+ 'unittest', 'doctest', 'pdb', 'profile', 'cProfile', 'timeit', 'trace',
52
+ # Internal / optional modules that must never trigger auto-install
53
+ 'compression', 'socks', 'wimlib',
54
+ }
55
+
56
+ # Common import name to package name mappings
57
+ COMMON_MAPPINGS = {
58
+ 'cv2': 'opencv-python',
59
+ 'PIL': 'Pillow',
60
+ 'Pillow': 'Pillow',
61
+ 'yaml': 'PyYAML',
62
+ 'sklearn': 'scikit-learn',
63
+ 'bs4': 'beautifulsoup4',
64
+ 'dateutil': 'python-dateutil',
65
+ 'requests_oauthlib': 'requests-oauthlib',
66
+ 'google': 'google-api-python-client',
67
+ 'jwt': 'PyJWT',
68
+ 'crypto': 'pycrypto',
69
+ 'Crypto': 'pycrypto',
70
+ 'MySQLdb': 'mysqlclient',
71
+ 'psycopg2': 'psycopg2-binary',
72
+ 'bson': 'pymongo',
73
+ 'lxml': 'lxml',
74
+ 'numpy': 'numpy',
75
+ 'pandas': 'pandas',
76
+ 'matplotlib': 'matplotlib',
77
+ 'seaborn': 'seaborn',
78
+ 'plotly': 'plotly',
79
+ 'django': 'Django',
80
+ 'flask': 'Flask',
81
+ 'fastapi': 'fastapi',
82
+ 'uvicorn': 'uvicorn',
83
+ 'pytest': 'pytest',
84
+ 'black': 'black',
85
+ 'isort': 'isort',
86
+ 'mypy': 'mypy',
87
+ 'psutil': 'psutil',
88
+ 'colorama': 'colorama',
89
+ 'pytz': 'pytz',
90
+ 'aiofiles': 'aiofiles',
91
+ 'watchdog': 'watchdog',
92
+ 'wand': 'Wand',
93
+ 'exifread': 'ExifRead',
94
+ 'piexif': 'piexif',
95
+ 'rawpy': 'rawpy',
96
+ 'imageio': 'imageio',
97
+ 'scipy': 'scipy',
98
+ 'scikit-image': 'scikit-image',
99
+ 'opencv-python': 'opencv-python',
100
+ 'opencv-contrib-python': 'opencv-contrib-python',
101
+ 'opentelemetry': 'opentelemetry-api',
102
+ 'opentelemetry.trace': 'opentelemetry-api',
103
+ 'opentelemetry.sdk': 'opentelemetry-sdk',
104
+ }
105
+
106
+ def _discover_from_sources(self) -> None:
107
+ """Discover dependencies from all sources."""
108
+ self._discover_from_pyproject_toml()
109
+ self._discover_from_requirements_txt()
110
+ self._discover_from_setup_py()
111
+ self._discover_from_custom_config()
112
+ self._add_common_mappings() # Add well-known mappings (bson->pymongo, cv2->opencv-python, etc.)
113
+
114
+ def _is_cache_valid(self) -> bool:
115
+ """Check if cached dependencies are still valid."""
116
+ if not self._cache_valid or not self._cached_dependencies:
117
+ return False
118
+
119
+ config_files = [
120
+ self.project_root / 'pyproject.toml',
121
+ self.project_root / 'requirements.txt',
122
+ self.project_root / 'setup.py',
123
+ ]
124
+
125
+ for config_file in config_files:
126
+ if config_file.exists():
127
+ try:
128
+ current_mtime = config_file.stat().st_mtime
129
+ cached_mtime = self._file_mtimes.get(str(config_file), 0)
130
+ if current_mtime > cached_mtime:
131
+ return False
132
+ except Exception:
133
+ return False
134
+
135
+ return True
136
+
137
+ def _update_file_mtimes(self) -> None:
138
+ """Update file modification times for cache validation."""
139
+ config_files = [
140
+ self.project_root / 'pyproject.toml',
141
+ self.project_root / 'requirements.txt',
142
+ self.project_root / 'setup.py',
143
+ ]
144
+ for config_file in config_files:
145
+ if config_file.exists():
146
+ try:
147
+ self._file_mtimes[str(config_file)] = config_file.stat().st_mtime
148
+ except Exception:
149
+ pass
150
+
151
+ def _discover_from_pyproject_toml(self) -> None:
152
+ """Discover dependencies from pyproject.toml."""
153
+ pyproject_path = self.project_root / 'pyproject.toml'
154
+ if not pyproject_path.exists():
155
+ return
156
+
157
+ try:
158
+ try:
159
+ import tomllib # Python 3.11+
160
+ toml_parser = tomllib # type: ignore[assignment]
161
+ except ImportError:
162
+ try:
163
+ import tomli as tomllib # type: ignore[assignment]
164
+ toml_parser = tomllib
165
+ except ImportError:
166
+ _log(
167
+ "discovery",
168
+ "TOML parser not available; attempting to lazy-install 'tomli'...",
169
+ )
170
+ try:
171
+ subprocess.run(
172
+ [sys.executable, "-m", "pip", "install", "tomli"],
173
+ check=False,
174
+ capture_output=True,
175
+ )
176
+ import tomli as tomllib # type: ignore[assignment]
177
+ toml_parser = tomllib
178
+ except Exception as install_exc:
179
+ logger.warning(
180
+ "tomli installation failed; skipping pyproject.toml discovery "
181
+ f"({install_exc})"
182
+ )
183
+ return
184
+
185
+ with open(pyproject_path, 'rb') as f:
186
+ data = toml_parser.load(f)
187
+
188
+ dependencies = []
189
+ if 'project' in data and 'dependencies' in data['project']:
190
+ dependencies.extend(data['project']['dependencies'])
191
+
192
+ if 'project' in data and 'optional-dependencies' in data['project']:
193
+ for group_name, group_deps in data['project']['optional-dependencies'].items():
194
+ dependencies.extend(group_deps)
195
+
196
+ if 'build-system' in data and 'requires' in data['build-system']:
197
+ dependencies.extend(data['build-system']['requires'])
198
+
199
+ for dep in dependencies:
200
+ self._parse_dependency_string(dep, 'pyproject.toml')
201
+
202
+ self._discovery_sources.append('pyproject.toml')
203
+ except Exception as e:
204
+ logger.warning(f"Could not parse pyproject.toml: {e}")
205
+
206
+ def _discover_from_requirements_txt(self) -> None:
207
+ """Discover dependencies from requirements.txt."""
208
+ requirements_path = self.project_root / 'requirements.txt'
209
+ if not requirements_path.exists():
210
+ return
211
+
212
+ try:
213
+ with open(requirements_path, 'r', encoding='utf-8') as f:
214
+ for line in f:
215
+ line = line.strip()
216
+ if line and not line.startswith('#'):
217
+ self._parse_dependency_string(line, 'requirements.txt')
218
+
219
+ self._discovery_sources.append('requirements.txt')
220
+ except Exception as e:
221
+ logger.warning(f"Could not parse requirements.txt: {e}")
222
+
223
+ def _discover_from_setup_py(self) -> None:
224
+ """Discover dependencies from setup.py."""
225
+ setup_path = self.project_root / 'setup.py'
226
+ if not setup_path.exists():
227
+ return
228
+
229
+ try:
230
+ with open(setup_path, 'r', encoding='utf-8') as f:
231
+ content = f.read()
232
+
233
+ install_requires_match = re.search(
234
+ r'install_requires\s*=\s*\[(.*?)\]',
235
+ content,
236
+ re.DOTALL
237
+ )
238
+ if install_requires_match:
239
+ deps_str = install_requires_match.group(1)
240
+ deps = re.findall(r'["\']([^"\']+)["\']', deps_str)
241
+ for dep in deps:
242
+ self._parse_dependency_string(dep, 'setup.py')
243
+
244
+ self._discovery_sources.append('setup.py')
245
+ except Exception as e:
246
+ logger.warning(f"Could not parse setup.py: {e}")
247
+
248
+ def _discover_from_custom_config(self) -> None:
249
+ """Discover dependencies from custom configuration files."""
250
+ config_files = [
251
+ 'dependency-mappings.json',
252
+ 'lazy-dependencies.json',
253
+ 'dependencies.json'
254
+ ]
255
+
256
+ for config_file in config_files:
257
+ config_path = self.project_root / config_file
258
+ if config_path.exists():
259
+ try:
260
+ with open(config_path, 'r', encoding='utf-8') as f:
261
+ data = json.load(f)
262
+
263
+ if isinstance(data, dict):
264
+ for import_name, package_name in data.items():
265
+ self.discovered_dependencies[import_name] = DependencyInfo(
266
+ import_name=import_name,
267
+ package_name=package_name,
268
+ source=config_file,
269
+ category='custom'
270
+ )
271
+
272
+ self._discovery_sources.append(config_file)
273
+ except Exception as e:
274
+ logger.warning(f"Could not parse {config_file}: {e}")
275
+
276
+ def _parse_dependency_string(self, dep_str: str, source: str) -> None:
277
+ """Parse a dependency string and extract dependency information."""
278
+ dep_str = re.sub(r'[>=<!=~]+.*', '', dep_str)
279
+ dep_str = re.sub(r'\[.*\]', '', dep_str)
280
+ dep_str = dep_str.strip()
281
+
282
+ if not dep_str:
283
+ return
284
+
285
+ import_name = dep_str
286
+ package_name = dep_str
287
+
288
+ if dep_str in self.COMMON_MAPPINGS:
289
+ package_name = self.COMMON_MAPPINGS[dep_str]
290
+ elif dep_str in self.COMMON_MAPPINGS.values():
291
+ for imp_name, pkg_name in self.COMMON_MAPPINGS.items():
292
+ if pkg_name == dep_str:
293
+ import_name = imp_name
294
+ break
295
+
296
+ self.discovered_dependencies[import_name] = DependencyInfo(
297
+ import_name=import_name,
298
+ package_name=package_name,
299
+ source=source,
300
+ category='discovered'
301
+ )
302
+
303
+ def _add_common_mappings(self) -> None:
304
+ """Add common mappings that might not be in dependency files."""
305
+ for import_name, package_name in self.COMMON_MAPPINGS.items():
306
+ if import_name not in self.discovered_dependencies:
307
+ self.discovered_dependencies[import_name] = DependencyInfo(
308
+ import_name=import_name,
309
+ package_name=package_name,
310
+ source='common_mappings',
311
+ category='common'
312
+ )
313
+
314
+ def get_package_for_import(self, import_name: str) -> Optional[str]:
315
+ """Get package name for a given import name."""
316
+ mapping = self.discover_all_dependencies()
317
+ return mapping.get(import_name)
318
+
319
+ def get_imports_for_package(self, package_name: str) -> List[str]:
320
+ """Get all possible import names for a package."""
321
+ mapping = self.get_package_import_mapping()
322
+ return mapping.get(package_name, [package_name])
323
+
324
+ def get_package_import_mapping(self) -> Dict[str, List[str]]:
325
+ """Get mapping of package names to their possible import names."""
326
+ self.discover_all_dependencies()
327
+
328
+ package_to_imports = {}
329
+ for import_name, dep_info in self.discovered_dependencies.items():
330
+ package_name = dep_info.package_name
331
+
332
+ if package_name not in package_to_imports:
333
+ package_to_imports[package_name] = [package_name]
334
+
335
+ if import_name != package_name:
336
+ if import_name not in package_to_imports[package_name]:
337
+ package_to_imports[package_name].append(import_name)
338
+
339
+ return package_to_imports
340
+
341
+ def get_import_package_mapping(self) -> Dict[str, str]:
342
+ """Get mapping of import names to package names."""
343
+ self.discover_all_dependencies()
344
+ return {import_name: dep_info.package_name for import_name, dep_info in self.discovered_dependencies.items()}
345
+
346
+ def export_to_json(self, file_path: str) -> None:
347
+ """Export discovered dependencies to JSON file."""
348
+ data = {
349
+ 'dependencies': {name: info.package_name for name, info in self.discovered_dependencies.items()},
350
+ 'sources': self.get_discovery_sources(),
351
+ 'total_count': len(self.discovered_dependencies)
352
+ }
353
+
354
+ with open(file_path, 'w', encoding='utf-8') as f:
355
+ json.dump(data, f, indent=2, ensure_ascii=False)
356
+
357
+ # Global discovery instance
358
+ _discovery: Optional[LazyDiscovery] = None
359
+ _discovery_lock = threading.RLock()
360
+
361
+ def get_lazy_discovery(project_root: Optional[str] = None) -> LazyDiscovery:
362
+ """Get or create global discovery instance."""
363
+ global _discovery
364
+ with _discovery_lock:
365
+ if _discovery is None:
366
+ _discovery = LazyDiscovery(project_root)
367
+ return _discovery
368
+
369
+ __all__ = ['LazyDiscovery', 'get_lazy_discovery']
370
+
@@ -6,19 +6,46 @@ import os
6
6
  import sys
7
7
  from typing import Iterable, Sequence, Tuple
8
8
 
9
- from .lazy_core import (
10
- config_package_lazy_install_enabled,
11
- install_import_hook,
12
- is_lazy_install_enabled,
13
- LazyMetaPathFinder,
14
- register_lazy_module_methods,
15
- register_lazy_module_prefix,
16
- )
9
+ # Lazy imports to avoid circular dependency
10
+ def _get_config_package_lazy_install_enabled():
11
+ """Get config_package_lazy_install_enabled (lazy import to avoid circular dependency)."""
12
+ from ...facade import config_package_lazy_install_enabled
13
+ return config_package_lazy_install_enabled
14
+
15
+ def _get_install_import_hook():
16
+ """Get install_import_hook (lazy import to avoid circular dependency)."""
17
+ from ...facade import install_import_hook
18
+ return install_import_hook
19
+
20
+ def _get_is_lazy_install_enabled():
21
+ """Get is_lazy_install_enabled (lazy import to avoid circular dependency)."""
22
+ from ...facade import is_lazy_install_enabled
23
+ return is_lazy_install_enabled
24
+
25
+ def _get_register_lazy_module_methods():
26
+ """Get register_lazy_module_methods (lazy import to avoid circular dependency)."""
27
+ from ...facade import register_lazy_module_methods
28
+ return register_lazy_module_methods
29
+
30
+ def _get_register_lazy_module_prefix():
31
+ """Get register_lazy_module_prefix (lazy import to avoid circular dependency)."""
32
+ from ...facade import register_lazy_module_prefix
33
+ return register_lazy_module_prefix
34
+
35
+ # Note: LazyMetaPathFinder is implemented in module/meta_path_finder.py
36
+ # For now, create a placeholder
37
+ class LazyMetaPathFinder:
38
+ """Placeholder for LazyMetaPathFinder - to be implemented in hooks domain."""
39
+ def __init__(self, package_name: str):
40
+ self.package_name = package_name
41
+
42
+ def _enhance_classes_with_class_methods(self, module):
43
+ """Placeholder method."""
44
+ pass
17
45
 
18
46
  _TRUTHY = {"1", "true", "yes", "on"}
19
47
  _REGISTERED: dict[str, dict[str, Tuple[str, ...]]] = {}
20
48
 
21
-
22
49
  def _normalized(items: Iterable[str]) -> Tuple[str, ...]:
23
50
  seen = []
24
51
  for item in items:
@@ -26,7 +53,6 @@ def _normalized(items: Iterable[str]) -> Tuple[str, ...]:
26
53
  seen.append(item)
27
54
  return tuple(seen)
28
55
 
29
-
30
56
  def register_host_package(
31
57
  package_name: str,
32
58
  module_prefixes: Iterable[str] = (),
@@ -57,19 +83,19 @@ def register_host_package(
57
83
  }
58
84
 
59
85
  for prefix in module_prefixes:
60
- register_lazy_module_prefix(prefix)
86
+ _get_register_lazy_module_prefix()(prefix)
61
87
 
62
88
  for prefix in method_prefixes:
63
- register_lazy_module_methods(prefix, tuple(method_names))
89
+ _get_register_lazy_module_methods()(prefix, tuple(method_names))
64
90
 
65
91
  if auto_config:
66
92
  # Detect if lazy should be enabled (checks keyword, marker package, etc.)
67
- config_package_lazy_install_enabled(package_name, enabled=None, install_hook=False)
93
+ _get_config_package_lazy_install_enabled()(package_name, enabled=None, install_hook=False)
68
94
 
69
95
  # If detection found that lazy should be enabled, install the hook automatically
70
- if is_lazy_install_enabled(package_name):
96
+ if _get_is_lazy_install_enabled()(package_name):
71
97
  try:
72
- install_import_hook(package_name)
98
+ _get_install_import_hook()(package_name)
73
99
  except Exception:
74
100
  # Best-effort: package import must continue even if hook installation fails
75
101
  pass
@@ -79,14 +105,13 @@ def register_host_package(
79
105
  env_key = env_var or f"{package_name.upper()}_LAZY_INSTALL"
80
106
  flag = os.environ.get(env_key, "")
81
107
  if flag.strip().lower() in _TRUTHY:
82
- config_package_lazy_install_enabled(package_name, enabled=True)
108
+ _get_config_package_lazy_install_enabled()(package_name, enabled=True)
83
109
  try:
84
- install_import_hook(package_name)
110
+ _get_install_import_hook()(package_name)
85
111
  except Exception:
86
112
  # Best-effort: package import must continue even if hook installation fails
87
113
  pass
88
114
 
89
-
90
115
  def refresh_host_package(package_name: str) -> None:
91
116
  """Re-apply wrappers for a registered package."""
92
117
  data = _REGISTERED.get(package_name.lower())
@@ -98,7 +123,6 @@ def refresh_host_package(package_name: str) -> None:
98
123
  data["method_prefixes"],
99
124
  )
100
125
 
101
-
102
126
  def _apply_wrappers_for_loaded_modules(
103
127
  package_name: str,
104
128
  module_prefixes: Iterable[str],
@@ -119,4 +143,3 @@ def _apply_wrappers_for_loaded_modules(
119
143
  except Exception:
120
144
  continue
121
145
 
122
-