zexus 1.6.8 → 1.7.2
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.
- package/README.md +12 -5
- package/package.json +1 -1
- package/src/__init__.py +7 -0
- package/src/zexus/__init__.py +1 -1
- package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/debug_sanitizer.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/input_validation.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/module_manager.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/security_enforcement.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
- package/src/zexus/access_control_system/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/access_control_system/__pycache__/access_control.cpython-312.pyc +0 -0
- package/src/zexus/advanced_types.py +17 -2
- package/src/zexus/blockchain/__init__.py +411 -0
- package/src/zexus/blockchain/accelerator.py +1160 -0
- package/src/zexus/blockchain/chain.py +660 -0
- package/src/zexus/blockchain/consensus.py +821 -0
- package/src/zexus/blockchain/contract_vm.py +1019 -0
- package/src/zexus/blockchain/crypto.py +79 -14
- package/src/zexus/blockchain/events.py +526 -0
- package/src/zexus/blockchain/loadtest.py +721 -0
- package/src/zexus/blockchain/monitoring.py +350 -0
- package/src/zexus/blockchain/mpt.py +716 -0
- package/src/zexus/blockchain/multichain.py +951 -0
- package/src/zexus/blockchain/multiprocess_executor.py +338 -0
- package/src/zexus/blockchain/network.py +886 -0
- package/src/zexus/blockchain/node.py +666 -0
- package/src/zexus/blockchain/rpc.py +1203 -0
- package/src/zexus/blockchain/rust_bridge.py +421 -0
- package/src/zexus/blockchain/storage.py +423 -0
- package/src/zexus/blockchain/tokens.py +750 -0
- package/src/zexus/blockchain/upgradeable.py +1004 -0
- package/src/zexus/blockchain/verification.py +1602 -0
- package/src/zexus/blockchain/wallet.py +621 -0
- package/src/zexus/capability_system.py +184 -9
- package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
- package/src/zexus/cli/main.py +383 -34
- package/src/zexus/cli/zpm.py +1 -1
- package/src/zexus/compiler/__pycache__/bytecode.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/semantic.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/compiler/bytecode.py +124 -7
- package/src/zexus/compiler/compat_runtime.py +6 -2
- package/src/zexus/compiler/lexer.py +16 -5
- package/src/zexus/compiler/parser.py +108 -7
- package/src/zexus/compiler/semantic.py +18 -19
- package/src/zexus/compiler/zexus_ast.py +26 -1
- package/src/zexus/concurrency_system.py +79 -0
- package/src/zexus/config.py +54 -0
- package/src/zexus/crypto_bridge.py +244 -8
- package/src/zexus/dap/__init__.py +10 -0
- package/src/zexus/dap/__main__.py +4 -0
- package/src/zexus/dap/dap_server.py +391 -0
- package/src/zexus/dap/debug_engine.py +298 -0
- package/src/zexus/environment.py +112 -9
- package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/resource_limiter.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/unified_execution.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/bytecode_compiler.py +457 -37
- package/src/zexus/evaluator/core.py +644 -50
- package/src/zexus/evaluator/expressions.py +358 -62
- package/src/zexus/evaluator/functions.py +458 -20
- package/src/zexus/evaluator/resource_limiter.py +4 -4
- package/src/zexus/evaluator/statements.py +774 -122
- package/src/zexus/evaluator/unified_execution.py +573 -72
- package/src/zexus/evaluator/utils.py +14 -2
- package/src/zexus/evaluator_original.py +1 -1
- package/src/zexus/event_loop.py +186 -0
- package/src/zexus/lexer.py +742 -458
- package/src/zexus/lsp/__init__.py +1 -1
- package/src/zexus/lsp/definition_provider.py +163 -9
- package/src/zexus/lsp/server.py +22 -8
- package/src/zexus/lsp/symbol_provider.py +182 -9
- package/src/zexus/module_cache.py +239 -9
- package/src/zexus/module_manager.py +129 -1
- package/src/zexus/object.py +76 -6
- package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
- package/src/zexus/parser/parser.py +1349 -408
- package/src/zexus/parser/strategy_context.py +755 -58
- package/src/zexus/parser/strategy_structural.py +121 -21
- package/src/zexus/persistence.py +15 -1
- package/src/zexus/renderer/__init__.py +61 -0
- package/src/zexus/renderer/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/backend.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/canvas.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/color_system.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/layout.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/main_renderer.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/painter.cpython-312.pyc +0 -0
- package/src/zexus/renderer/backend.py +261 -0
- package/src/zexus/renderer/canvas.py +78 -0
- package/src/zexus/renderer/color_system.py +201 -0
- package/src/zexus/renderer/graphics.py +31 -0
- package/src/zexus/renderer/layout.py +222 -0
- package/src/zexus/renderer/main_renderer.py +66 -0
- package/src/zexus/renderer/painter.py +30 -0
- package/src/zexus/renderer/tk_backend.py +208 -0
- package/src/zexus/renderer/web_backend.py +260 -0
- package/src/zexus/runtime/__init__.py +10 -2
- package/src/zexus/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/runtime/__pycache__/async_runtime.cpython-312.pyc +0 -0
- package/src/zexus/runtime/__pycache__/load_manager.cpython-312.pyc +0 -0
- package/src/zexus/runtime/file_flags.py +137 -0
- package/src/zexus/runtime/load_manager.py +368 -0
- package/src/zexus/safety/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/safety/__pycache__/memory_safety.cpython-312.pyc +0 -0
- package/src/zexus/security.py +424 -34
- package/src/zexus/stdlib/fs.py +23 -18
- package/src/zexus/stdlib/http.py +289 -186
- package/src/zexus/stdlib/sockets.py +207 -163
- package/src/zexus/stdlib/websockets.py +282 -0
- package/src/zexus/stdlib_integration.py +369 -2
- package/src/zexus/strategy_recovery.py +6 -3
- package/src/zexus/type_checker.py +423 -0
- package/src/zexus/virtual_filesystem.py +189 -2
- package/src/zexus/vm/__init__.py +113 -3
- package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/bytecode_converter.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/compiler.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/gas_metering.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/async_optimizer.py +80 -6
- package/src/zexus/vm/binary_bytecode.py +659 -0
- package/src/zexus/vm/bytecode.py +59 -11
- package/src/zexus/vm/bytecode_converter.py +26 -12
- package/src/zexus/vm/cabi.c +1985 -0
- package/src/zexus/vm/cabi.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/cabi.h +127 -0
- package/src/zexus/vm/cache.py +561 -17
- package/src/zexus/vm/compiler.py +818 -51
- package/src/zexus/vm/fastops.c +15743 -0
- package/src/zexus/vm/fastops.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/fastops.pyx +288 -0
- package/src/zexus/vm/gas_metering.py +50 -9
- package/src/zexus/vm/jit.py +364 -20
- package/src/zexus/vm/native_jit_backend.py +1816 -0
- package/src/zexus/vm/native_runtime.cpp +1388 -0
- package/src/zexus/vm/native_runtime.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/optimizer.py +161 -11
- package/src/zexus/vm/parallel_vm.py +140 -45
- package/src/zexus/vm/peephole_optimizer.py +82 -4
- package/src/zexus/vm/profiler.py +38 -18
- package/src/zexus/vm/register_allocator.py +16 -5
- package/src/zexus/vm/register_vm.py +8 -5
- package/src/zexus/vm/vm.py +3581 -531
- package/src/zexus/vm/wasm_compiler.py +658 -0
- package/src/zexus/zexus_ast.py +137 -11
- package/src/zexus/zexus_token.py +16 -5
- package/src/zexus/zpm/installer.py +55 -15
- package/src/zexus/zpm/package_manager.py +1 -1
- package/src/zexus/zpm/registry.py +257 -28
- package/src/zexus.egg-info/PKG-INFO +16 -6
- package/src/zexus.egg-info/SOURCES.txt +129 -17
- package/src/zexus.egg-info/entry_points.txt +1 -0
- package/src/zexus.egg-info/requires.txt +4 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
"""Resource loading utilities for the `load` keyword."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LoadManager:
|
|
12
|
+
"""Coordinates value lookups for the `load` keyword."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, project_root: Optional[Path] = None) -> None:
|
|
15
|
+
self.project_root = Path(project_root or os.getcwd()).resolve()
|
|
16
|
+
self._providers: Dict[str, Callable[..., Any]] = {}
|
|
17
|
+
self._dotenv_cache: Dict[Path, Dict[str, Any]] = {}
|
|
18
|
+
self._structured_cache: Dict[tuple, Dict[str, Any]] = {}
|
|
19
|
+
self._register_defaults()
|
|
20
|
+
|
|
21
|
+
# ------------------------------------------------------------------
|
|
22
|
+
# Provider registration
|
|
23
|
+
# ------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
def _register_defaults(self) -> None:
|
|
26
|
+
self.register_provider("env", self._load_from_env)
|
|
27
|
+
self.register_provider("dotenv", self._load_from_dotenv)
|
|
28
|
+
self.register_provider("file", self._load_from_file)
|
|
29
|
+
|
|
30
|
+
def register_provider(self, name: str, handler: Callable[..., Any]) -> None:
|
|
31
|
+
self._providers[name.lower()] = handler
|
|
32
|
+
|
|
33
|
+
def unregister_provider(self, name: str) -> None:
|
|
34
|
+
self._providers.pop(name.lower(), None)
|
|
35
|
+
|
|
36
|
+
def available_providers(self) -> Sequence[str]:
|
|
37
|
+
return tuple(self._providers.keys())
|
|
38
|
+
|
|
39
|
+
def is_provider_registered(self, name: Optional[str]) -> bool:
|
|
40
|
+
return bool(name) and name.lower() in self._providers
|
|
41
|
+
|
|
42
|
+
# ------------------------------------------------------------------
|
|
43
|
+
# Public load API
|
|
44
|
+
# ------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
def load(
|
|
47
|
+
self,
|
|
48
|
+
key: str,
|
|
49
|
+
*,
|
|
50
|
+
provider: Optional[str] = None,
|
|
51
|
+
source: Optional[str] = None,
|
|
52
|
+
current_dir: Optional[str] = None,
|
|
53
|
+
) -> Any:
|
|
54
|
+
"""Resolve a value using registered providers and fallbacks."""
|
|
55
|
+
|
|
56
|
+
provider_name = provider.lower() if provider else None
|
|
57
|
+
if provider_name:
|
|
58
|
+
handler = self._providers.get(provider_name)
|
|
59
|
+
if not handler:
|
|
60
|
+
raise KeyError(f"Unknown provider '{provider}'")
|
|
61
|
+
value = handler(
|
|
62
|
+
key,
|
|
63
|
+
source=source,
|
|
64
|
+
current_dir=current_dir,
|
|
65
|
+
explicit=True,
|
|
66
|
+
)
|
|
67
|
+
if value is None:
|
|
68
|
+
raise KeyError(key)
|
|
69
|
+
return value
|
|
70
|
+
|
|
71
|
+
# Default lookup chain
|
|
72
|
+
value = self._load_from_env(key)
|
|
73
|
+
if value is not None:
|
|
74
|
+
return value
|
|
75
|
+
|
|
76
|
+
value = self._load_from_dotenv(
|
|
77
|
+
key,
|
|
78
|
+
source=source,
|
|
79
|
+
current_dir=current_dir,
|
|
80
|
+
)
|
|
81
|
+
if value is not None:
|
|
82
|
+
return value
|
|
83
|
+
|
|
84
|
+
if source:
|
|
85
|
+
value = self._load_from_file(
|
|
86
|
+
key,
|
|
87
|
+
source,
|
|
88
|
+
current_dir=current_dir,
|
|
89
|
+
explicit=True,
|
|
90
|
+
)
|
|
91
|
+
if value is not None:
|
|
92
|
+
return value
|
|
93
|
+
|
|
94
|
+
raise KeyError(key)
|
|
95
|
+
|
|
96
|
+
def clear_caches(self) -> None:
|
|
97
|
+
self._dotenv_cache.clear()
|
|
98
|
+
self._structured_cache.clear()
|
|
99
|
+
|
|
100
|
+
# ------------------------------------------------------------------
|
|
101
|
+
# Provider helpers
|
|
102
|
+
# ------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
def _load_from_env(
|
|
105
|
+
self,
|
|
106
|
+
key: str,
|
|
107
|
+
*,
|
|
108
|
+
source: Optional[str] = None,
|
|
109
|
+
current_dir: Optional[str] = None,
|
|
110
|
+
explicit: bool | None = None,
|
|
111
|
+
) -> Optional[str]:
|
|
112
|
+
if not key:
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
for candidate in self._key_variants(key):
|
|
116
|
+
value = os.environ.get(candidate)
|
|
117
|
+
if value is not None:
|
|
118
|
+
return value
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
def _load_from_dotenv(
|
|
122
|
+
self,
|
|
123
|
+
key: str,
|
|
124
|
+
*,
|
|
125
|
+
source: Optional[str] = None,
|
|
126
|
+
current_dir: Optional[str] = None,
|
|
127
|
+
explicit: bool | None = None,
|
|
128
|
+
) -> Optional[str]:
|
|
129
|
+
candidates: List[Path] = []
|
|
130
|
+
|
|
131
|
+
if source:
|
|
132
|
+
resolved = self._resolve_source(source, current_dir)
|
|
133
|
+
if resolved and resolved.is_file():
|
|
134
|
+
candidates.append(resolved)
|
|
135
|
+
|
|
136
|
+
if not explicit:
|
|
137
|
+
# Current directory .env
|
|
138
|
+
if current_dir:
|
|
139
|
+
candidates.append(Path(current_dir) / ".env")
|
|
140
|
+
# Project root .env
|
|
141
|
+
candidates.append(self.project_root / ".env")
|
|
142
|
+
|
|
143
|
+
for path in candidates:
|
|
144
|
+
mapping = self._load_dotenv_file(path)
|
|
145
|
+
if not mapping:
|
|
146
|
+
continue
|
|
147
|
+
value = self._lookup_mapping(mapping, key)
|
|
148
|
+
if value is not None:
|
|
149
|
+
return value
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
def _load_from_file(
|
|
153
|
+
self,
|
|
154
|
+
key: str,
|
|
155
|
+
source: str,
|
|
156
|
+
*,
|
|
157
|
+
current_dir: Optional[str] = None,
|
|
158
|
+
explicit: bool | None = None,
|
|
159
|
+
) -> Any:
|
|
160
|
+
resolved = self._resolve_source(source, current_dir)
|
|
161
|
+
if not resolved:
|
|
162
|
+
raise FileNotFoundError(source)
|
|
163
|
+
|
|
164
|
+
if resolved.is_dir():
|
|
165
|
+
# Allow pointing at directory containing .env
|
|
166
|
+
env_candidate = resolved / ".env"
|
|
167
|
+
return self._load_from_dotenv(
|
|
168
|
+
key,
|
|
169
|
+
source=str(env_candidate),
|
|
170
|
+
current_dir=current_dir,
|
|
171
|
+
explicit=True,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
suffix = resolved.suffix.lower()
|
|
175
|
+
if suffix in (".env", ""):
|
|
176
|
+
return self._load_from_dotenv(
|
|
177
|
+
key,
|
|
178
|
+
source=str(resolved),
|
|
179
|
+
current_dir=current_dir,
|
|
180
|
+
explicit=True,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
if suffix in (".json", ".jsn"):
|
|
184
|
+
data = self._load_json_file(resolved)
|
|
185
|
+
return self._extract_from_data(data, key)
|
|
186
|
+
|
|
187
|
+
if suffix in (".yaml", ".yml"):
|
|
188
|
+
try:
|
|
189
|
+
import yaml # type: ignore
|
|
190
|
+
except ImportError as exc: # pragma: no cover - optional dependency
|
|
191
|
+
raise FileNotFoundError(
|
|
192
|
+
f"YAML support requires PyYAML to be installed: {resolved}"
|
|
193
|
+
) from exc
|
|
194
|
+
data = self._load_yaml_file(resolved, yaml)
|
|
195
|
+
return self._extract_from_data(data, key)
|
|
196
|
+
|
|
197
|
+
if not key:
|
|
198
|
+
return resolved.read_text(encoding="utf-8")
|
|
199
|
+
return None
|
|
200
|
+
|
|
201
|
+
# ------------------------------------------------------------------
|
|
202
|
+
# File helpers
|
|
203
|
+
# ------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
def _resolve_source(
|
|
206
|
+
self,
|
|
207
|
+
source: str,
|
|
208
|
+
current_dir: Optional[str],
|
|
209
|
+
) -> Optional[Path]:
|
|
210
|
+
candidate = Path(source)
|
|
211
|
+
search_order: Iterable[Path]
|
|
212
|
+
|
|
213
|
+
if candidate.is_absolute():
|
|
214
|
+
search_order = (candidate,)
|
|
215
|
+
else:
|
|
216
|
+
search_order = filter(
|
|
217
|
+
None,
|
|
218
|
+
(
|
|
219
|
+
Path(current_dir) / candidate if current_dir else None,
|
|
220
|
+
self.project_root / candidate,
|
|
221
|
+
),
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
for root in search_order:
|
|
225
|
+
try:
|
|
226
|
+
resolved = root.resolve()
|
|
227
|
+
except (OSError, RuntimeError):
|
|
228
|
+
continue
|
|
229
|
+
if resolved.exists():
|
|
230
|
+
return resolved
|
|
231
|
+
return candidate.resolve() if candidate.exists() else None
|
|
232
|
+
|
|
233
|
+
def _load_dotenv_file(self, path: Path) -> Optional[Dict[str, str]]:
|
|
234
|
+
if not path or not path.exists() or not path.is_file():
|
|
235
|
+
return None
|
|
236
|
+
|
|
237
|
+
try:
|
|
238
|
+
mtime = path.stat().st_mtime_ns
|
|
239
|
+
except (OSError, AttributeError):
|
|
240
|
+
mtime = None
|
|
241
|
+
|
|
242
|
+
cache = self._dotenv_cache.get(path)
|
|
243
|
+
if cache and cache.get("mtime") == mtime:
|
|
244
|
+
return cache.get("data")
|
|
245
|
+
|
|
246
|
+
data: Dict[str, str] = {}
|
|
247
|
+
try:
|
|
248
|
+
with path.open("r", encoding="utf-8") as handle:
|
|
249
|
+
for line in handle:
|
|
250
|
+
stripped = line.strip()
|
|
251
|
+
if not stripped or stripped.startswith("#"):
|
|
252
|
+
continue
|
|
253
|
+
if "=" not in stripped:
|
|
254
|
+
continue
|
|
255
|
+
key, raw_value = stripped.split("=", 1)
|
|
256
|
+
value = raw_value.strip().strip('"').strip("'")
|
|
257
|
+
data[key.strip()] = value
|
|
258
|
+
except OSError:
|
|
259
|
+
return None
|
|
260
|
+
|
|
261
|
+
self._dotenv_cache[path] = {"mtime": mtime, "data": data}
|
|
262
|
+
return data
|
|
263
|
+
|
|
264
|
+
def _load_json_file(self, path: Path) -> Any:
|
|
265
|
+
try:
|
|
266
|
+
mtime = path.stat().st_mtime_ns
|
|
267
|
+
except (OSError, AttributeError):
|
|
268
|
+
mtime = None
|
|
269
|
+
|
|
270
|
+
cache = self._structured_cache.get((path, "json"))
|
|
271
|
+
if cache and cache.get("mtime") == mtime:
|
|
272
|
+
return cache.get("data")
|
|
273
|
+
|
|
274
|
+
with path.open("r", encoding="utf-8") as handle:
|
|
275
|
+
data = json.load(handle)
|
|
276
|
+
|
|
277
|
+
self._structured_cache[(path, "json")] = {"mtime": mtime, "data": data}
|
|
278
|
+
return data
|
|
279
|
+
|
|
280
|
+
def _load_yaml_file(self, path: Path, yaml_module) -> Any: # pragma: no cover - optional dependency
|
|
281
|
+
try:
|
|
282
|
+
mtime = path.stat().st_mtime_ns
|
|
283
|
+
except (OSError, AttributeError):
|
|
284
|
+
mtime = None
|
|
285
|
+
|
|
286
|
+
cache = self._structured_cache.get((path, "yaml"))
|
|
287
|
+
if cache and cache.get("mtime") == mtime:
|
|
288
|
+
return cache.get("data")
|
|
289
|
+
|
|
290
|
+
with path.open("r", encoding="utf-8") as handle:
|
|
291
|
+
data = yaml_module.safe_load(handle)
|
|
292
|
+
|
|
293
|
+
self._structured_cache[(path, "yaml")] = {"mtime": mtime, "data": data}
|
|
294
|
+
return data
|
|
295
|
+
|
|
296
|
+
# ------------------------------------------------------------------
|
|
297
|
+
# Lookup helpers
|
|
298
|
+
# ------------------------------------------------------------------
|
|
299
|
+
|
|
300
|
+
def _lookup_mapping(self, mapping: Dict[str, Any], key: str) -> Optional[str]:
|
|
301
|
+
for candidate in self._key_variants(key):
|
|
302
|
+
value = mapping.get(candidate)
|
|
303
|
+
if value is not None:
|
|
304
|
+
return value
|
|
305
|
+
return None
|
|
306
|
+
|
|
307
|
+
def _extract_from_data(self, data: Any, key: str) -> Any:
|
|
308
|
+
if key is None:
|
|
309
|
+
return data
|
|
310
|
+
if not key:
|
|
311
|
+
return data
|
|
312
|
+
|
|
313
|
+
parts = key.split(".")
|
|
314
|
+
current = data
|
|
315
|
+
for part in parts:
|
|
316
|
+
if isinstance(current, dict):
|
|
317
|
+
if part in current:
|
|
318
|
+
current = current[part]
|
|
319
|
+
continue
|
|
320
|
+
alt = part.replace("-", "_")
|
|
321
|
+
if alt in current:
|
|
322
|
+
current = current[alt]
|
|
323
|
+
continue
|
|
324
|
+
alt_upper = alt.upper()
|
|
325
|
+
if alt_upper in current:
|
|
326
|
+
current = current[alt_upper]
|
|
327
|
+
continue
|
|
328
|
+
return None
|
|
329
|
+
if isinstance(current, list):
|
|
330
|
+
try:
|
|
331
|
+
index = int(part)
|
|
332
|
+
except ValueError:
|
|
333
|
+
return None
|
|
334
|
+
if 0 <= index < len(current):
|
|
335
|
+
current = current[index]
|
|
336
|
+
continue
|
|
337
|
+
return None
|
|
338
|
+
return None
|
|
339
|
+
return current
|
|
340
|
+
|
|
341
|
+
def _key_variants(self, key: str) -> List[str]:
|
|
342
|
+
variants = {key, key.lower(), key.upper()}
|
|
343
|
+
if "." in key:
|
|
344
|
+
collapsed = key.replace(".", "_")
|
|
345
|
+
variants.add(collapsed)
|
|
346
|
+
variants.add(collapsed.lower())
|
|
347
|
+
variants.add(collapsed.upper())
|
|
348
|
+
if "-" in key:
|
|
349
|
+
hyphenless = key.replace("-", "_")
|
|
350
|
+
variants.add(hyphenless)
|
|
351
|
+
variants.add(hyphenless.lower())
|
|
352
|
+
variants.add(hyphenless.upper())
|
|
353
|
+
return [variant for variant in variants if variant]
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
_manager = LoadManager()
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def get_load_manager() -> LoadManager:
|
|
360
|
+
return _manager
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def register_provider(name: str, handler: Callable[..., Any]) -> None:
|
|
364
|
+
_manager.register_provider(name, handler)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def clear_load_caches() -> None:
|
|
368
|
+
_manager.clear_caches()
|
|
Binary file
|
|
Binary file
|