provide-foundation 0.0.0.dev2__py3-none-any.whl → 0.0.0.dev3__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.
- provide/foundation/__init__.py +20 -20
- provide/foundation/archive/__init__.py +1 -1
- provide/foundation/archive/base.py +15 -14
- provide/foundation/archive/bzip2.py +40 -40
- provide/foundation/archive/gzip.py +42 -42
- provide/foundation/archive/operations.py +90 -91
- provide/foundation/archive/tar.py +33 -31
- provide/foundation/archive/zip.py +52 -50
- provide/foundation/asynctools/__init__.py +20 -0
- provide/foundation/asynctools/core.py +126 -0
- provide/foundation/cli/__init__.py +2 -2
- provide/foundation/cli/commands/deps.py +4 -4
- provide/foundation/cli/commands/logs/__init__.py +2 -2
- provide/foundation/cli/commands/logs/generate.py +2 -2
- provide/foundation/cli/commands/logs/query.py +3 -3
- provide/foundation/cli/commands/logs/send.py +2 -2
- provide/foundation/cli/commands/logs/tail.py +2 -2
- provide/foundation/cli/decorators.py +0 -1
- provide/foundation/cli/testing.py +0 -5
- provide/foundation/cli/utils.py +1 -2
- provide/foundation/config/__init__.py +19 -19
- provide/foundation/config/base.py +2 -2
- provide/foundation/config/converters.py +81 -83
- provide/foundation/config/defaults.py +1 -1
- provide/foundation/config/env.py +2 -1
- provide/foundation/config/loader.py +1 -1
- provide/foundation/config/sync.py +8 -6
- provide/foundation/config/types.py +5 -5
- provide/foundation/config/validators.py +4 -4
- provide/foundation/console/output.py +7 -7
- provide/foundation/context/core.py +19 -17
- provide/foundation/crypto/certificates/__init__.py +9 -5
- provide/foundation/crypto/certificates/base.py +2 -2
- provide/foundation/crypto/certificates/certificate.py +48 -19
- provide/foundation/crypto/certificates/factory.py +26 -18
- provide/foundation/crypto/certificates/generator.py +24 -23
- provide/foundation/crypto/certificates/loader.py +24 -16
- provide/foundation/crypto/certificates/operations.py +17 -10
- provide/foundation/crypto/certificates/trust.py +21 -21
- provide/foundation/env/__init__.py +28 -0
- provide/foundation/env/core.py +218 -0
- provide/foundation/errors/__init__.py +3 -2
- provide/foundation/errors/decorators.py +0 -3
- provide/foundation/errors/types.py +0 -1
- provide/foundation/eventsets/display.py +13 -14
- provide/foundation/eventsets/registry.py +61 -31
- provide/foundation/eventsets/resolver.py +50 -46
- provide/foundation/eventsets/sets/das.py +8 -8
- provide/foundation/eventsets/sets/database.py +14 -14
- provide/foundation/eventsets/sets/http.py +21 -21
- provide/foundation/eventsets/sets/llm.py +16 -16
- provide/foundation/eventsets/sets/task_queue.py +13 -13
- provide/foundation/eventsets/types.py +7 -7
- provide/foundation/file/directory.py +1 -1
- provide/foundation/file/lock.py +2 -3
- provide/foundation/hub/components.py +19 -21
- provide/foundation/hub/config.py +25 -19
- provide/foundation/hub/discovery.py +5 -4
- provide/foundation/hub/handlers.py +13 -5
- provide/foundation/hub/lifecycle.py +10 -9
- provide/foundation/hub/manager.py +3 -0
- provide/foundation/hub/processors.py +8 -3
- provide/foundation/integrations/__init__.py +1 -1
- provide/foundation/integrations/openobserve/client.py +2 -2
- provide/foundation/integrations/openobserve/commands.py +9 -9
- provide/foundation/integrations/openobserve/config.py +2 -2
- provide/foundation/integrations/openobserve/otlp.py +2 -2
- provide/foundation/integrations/openobserve/search.py +1 -2
- provide/foundation/integrations/openobserve/streaming.py +1 -1
- provide/foundation/logger/__init__.py +0 -1
- provide/foundation/logger/config/base.py +1 -1
- provide/foundation/logger/config/logging.py +19 -19
- provide/foundation/logger/config/telemetry.py +11 -13
- provide/foundation/logger/factories.py +2 -2
- provide/foundation/logger/processors/main.py +12 -10
- provide/foundation/logger/ratelimit/limiters.py +4 -4
- provide/foundation/logger/ratelimit/processor.py +1 -1
- provide/foundation/logger/setup/coordinator.py +38 -24
- provide/foundation/logger/setup/processors.py +3 -3
- provide/foundation/logger/setup/testing.py +14 -0
- provide/foundation/logger/trace.py +5 -5
- provide/foundation/metrics/__init__.py +1 -1
- provide/foundation/metrics/otel.py +3 -1
- provide/foundation/observability/__init__.py +1 -1
- provide/foundation/process/__init__.py +1 -1
- provide/foundation/process/exit.py +6 -5
- provide/foundation/process/lifecycle.py +41 -18
- provide/foundation/resilience/__init__.py +6 -5
- provide/foundation/resilience/circuit.py +32 -30
- provide/foundation/resilience/decorators.py +58 -42
- provide/foundation/resilience/fallback.py +55 -40
- provide/foundation/resilience/retry.py +67 -65
- provide/foundation/serialization/__init__.py +16 -0
- provide/foundation/serialization/core.py +70 -0
- provide/foundation/streams/config.py +8 -9
- provide/foundation/streams/console.py +3 -3
- provide/foundation/streams/core.py +2 -2
- provide/foundation/streams/file.py +1 -1
- provide/foundation/testing/__init__.py +22 -7
- provide/foundation/testing/archive/__init__.py +7 -7
- provide/foundation/testing/archive/fixtures.py +58 -54
- provide/foundation/testing/cli.py +3 -6
- provide/foundation/testing/common/__init__.py +13 -13
- provide/foundation/testing/common/fixtures.py +27 -30
- provide/foundation/testing/file/__init__.py +15 -15
- provide/foundation/testing/file/content_fixtures.py +65 -92
- provide/foundation/testing/file/directory_fixtures.py +19 -19
- provide/foundation/testing/file/fixtures.py +14 -17
- provide/foundation/testing/file/special_fixtures.py +34 -42
- provide/foundation/testing/logger.py +28 -23
- provide/foundation/testing/mocking/__init__.py +21 -21
- provide/foundation/testing/mocking/fixtures.py +80 -67
- provide/foundation/testing/process/__init__.py +23 -23
- provide/foundation/testing/process/async_fixtures.py +89 -80
- provide/foundation/testing/process/fixtures.py +11 -13
- provide/foundation/testing/process/subprocess_fixtures.py +41 -40
- provide/foundation/testing/threading/__init__.py +17 -17
- provide/foundation/testing/threading/basic_fixtures.py +21 -17
- provide/foundation/testing/threading/data_fixtures.py +18 -16
- provide/foundation/testing/threading/execution_fixtures.py +67 -52
- provide/foundation/testing/threading/fixtures.py +10 -14
- provide/foundation/testing/threading/sync_fixtures.py +21 -18
- provide/foundation/testing/time/__init__.py +11 -11
- provide/foundation/testing/time/fixtures.py +91 -79
- provide/foundation/testing/transport/__init__.py +9 -9
- provide/foundation/testing/transport/fixtures.py +54 -54
- provide/foundation/time/__init__.py +18 -0
- provide/foundation/time/core.py +63 -0
- provide/foundation/tools/__init__.py +2 -2
- provide/foundation/tools/base.py +68 -67
- provide/foundation/tools/cache.py +62 -69
- provide/foundation/tools/downloader.py +51 -56
- provide/foundation/tools/installer.py +51 -57
- provide/foundation/tools/registry.py +38 -45
- provide/foundation/tools/resolver.py +70 -68
- provide/foundation/tools/verifier.py +39 -50
- provide/foundation/tracer/spans.py +1 -13
- provide/foundation/transport/__init__.py +26 -33
- provide/foundation/transport/base.py +32 -30
- provide/foundation/transport/client.py +44 -49
- provide/foundation/transport/config.py +11 -13
- provide/foundation/transport/errors.py +13 -27
- provide/foundation/transport/http.py +69 -55
- provide/foundation/transport/middleware.py +86 -81
- provide/foundation/transport/registry.py +29 -27
- provide/foundation/transport/types.py +6 -6
- provide/foundation/utils/deps.py +3 -2
- provide/foundation/utils/parsing.py +7 -7
- {provide_foundation-0.0.0.dev2.dist-info → provide_foundation-0.0.0.dev3.dist-info}/METADATA +2 -2
- provide_foundation-0.0.0.dev3.dist-info/RECORD +233 -0
- provide_foundation-0.0.0.dev2.dist-info/RECORD +0 -225
- {provide_foundation-0.0.0.dev2.dist-info → provide_foundation-0.0.0.dev3.dist-info}/WHEEL +0 -0
- {provide_foundation-0.0.0.dev2.dist-info → provide_foundation-0.0.0.dev3.dist-info}/entry_points.txt +0 -0
- {provide_foundation-0.0.0.dev2.dist-info → provide_foundation-0.0.0.dev3.dist-info}/licenses/LICENSE +0 -0
- {provide_foundation-0.0.0.dev2.dist-info → provide_foundation-0.0.0.dev3.dist-info}/top_level.txt +0 -0
@@ -19,32 +19,32 @@ log = get_logger(__name__)
|
|
19
19
|
class ToolRegistry:
|
20
20
|
"""
|
21
21
|
Wrapper around the hub registry for tool management.
|
22
|
-
|
22
|
+
|
23
23
|
Uses the main hub registry with dimension="tool_manager"
|
24
24
|
to avoid namespace pollution while leveraging existing
|
25
25
|
registry infrastructure.
|
26
26
|
"""
|
27
|
-
|
27
|
+
|
28
28
|
DIMENSION = "tool_manager"
|
29
|
-
|
29
|
+
|
30
30
|
def __init__(self):
|
31
31
|
"""Initialize the tool registry."""
|
32
32
|
self.hub = get_hub()
|
33
33
|
self._discover_tools()
|
34
|
-
|
34
|
+
|
35
35
|
def _discover_tools(self) -> None:
|
36
36
|
"""
|
37
37
|
Auto-discover tool managers via entry points.
|
38
|
-
|
38
|
+
|
39
39
|
Looks for entry points in the "wrknv.tools" group
|
40
40
|
and automatically registers them.
|
41
41
|
"""
|
42
42
|
try:
|
43
43
|
# Get entry points for tool managers
|
44
|
-
if hasattr(importlib.metadata,
|
44
|
+
if hasattr(importlib.metadata, "entry_points"):
|
45
45
|
# Python 3.10+
|
46
46
|
eps = importlib.metadata.entry_points()
|
47
|
-
if hasattr(eps,
|
47
|
+
if hasattr(eps, "select"):
|
48
48
|
# Python 3.10+ with select method
|
49
49
|
group_eps = eps.select(group="wrknv.tools")
|
50
50
|
else:
|
@@ -54,7 +54,7 @@ class ToolRegistry:
|
|
54
54
|
# Python 3.8-3.9
|
55
55
|
eps = importlib.metadata.entry_points()
|
56
56
|
group_eps = eps.get("wrknv.tools", [])
|
57
|
-
|
57
|
+
|
58
58
|
for ep in group_eps:
|
59
59
|
try:
|
60
60
|
manager_class = ep.load()
|
@@ -64,16 +64,16 @@ class ToolRegistry:
|
|
64
64
|
log.warning(f"Failed to load tool manager {ep.name}: {e}")
|
65
65
|
except Exception as e:
|
66
66
|
log.debug(f"Entry point discovery not available: {e}")
|
67
|
-
|
67
|
+
|
68
68
|
def register_tool_manager(
|
69
69
|
self,
|
70
70
|
name: str,
|
71
71
|
manager_class: type[BaseToolManager],
|
72
|
-
aliases: list[str] | None = None
|
72
|
+
aliases: list[str] | None = None,
|
73
73
|
) -> None:
|
74
74
|
"""
|
75
75
|
Register a tool manager with the hub.
|
76
|
-
|
76
|
+
|
77
77
|
Args:
|
78
78
|
name: Tool name (e.g., "terraform").
|
79
79
|
manager_class: Tool manager class.
|
@@ -85,7 +85,7 @@ class ToolRegistry:
|
|
85
85
|
"executable": manager_class.executable_name,
|
86
86
|
"platforms": manager_class.supported_platforms,
|
87
87
|
}
|
88
|
-
|
88
|
+
|
89
89
|
# Register with hub
|
90
90
|
self.hub.registry.register(
|
91
91
|
name=name,
|
@@ -93,35 +93,33 @@ class ToolRegistry:
|
|
93
93
|
dimension=self.DIMENSION,
|
94
94
|
metadata=metadata,
|
95
95
|
aliases=aliases,
|
96
|
-
replace=True # Allow re-registration for updates
|
96
|
+
replace=True, # Allow re-registration for updates
|
97
97
|
)
|
98
|
-
|
98
|
+
|
99
99
|
log.info(f"Registered tool manager: {name}")
|
100
|
-
|
100
|
+
|
101
101
|
def get_tool_manager_class(self, name: str) -> type[BaseToolManager] | None:
|
102
102
|
"""
|
103
103
|
Get a tool manager class by name.
|
104
|
-
|
104
|
+
|
105
105
|
Args:
|
106
106
|
name: Tool name or alias.
|
107
|
-
|
107
|
+
|
108
108
|
Returns:
|
109
109
|
Tool manager class, or None if not found.
|
110
110
|
"""
|
111
111
|
return self.hub.registry.get(name, dimension=self.DIMENSION)
|
112
|
-
|
112
|
+
|
113
113
|
def create_tool_manager(
|
114
|
-
self,
|
115
|
-
name: str,
|
116
|
-
config: BaseConfig
|
114
|
+
self, name: str, config: BaseConfig
|
117
115
|
) -> BaseToolManager | None:
|
118
116
|
"""
|
119
117
|
Create a tool manager instance.
|
120
|
-
|
118
|
+
|
121
119
|
Args:
|
122
120
|
name: Tool name or alias.
|
123
121
|
config: Configuration object.
|
124
|
-
|
122
|
+
|
125
123
|
Returns:
|
126
124
|
Tool manager instance, or None if not found.
|
127
125
|
"""
|
@@ -129,42 +127,42 @@ class ToolRegistry:
|
|
129
127
|
if manager_class:
|
130
128
|
return manager_class(config)
|
131
129
|
return None
|
132
|
-
|
130
|
+
|
133
131
|
def list_tools(self) -> list[tuple[str, dict[str, Any]]]:
|
134
132
|
"""
|
135
133
|
List all registered tools.
|
136
|
-
|
134
|
+
|
137
135
|
Returns:
|
138
136
|
List of (name, metadata) tuples.
|
139
137
|
"""
|
140
138
|
tools = []
|
141
139
|
for name, entry in self.hub.registry.list_dimension(self.DIMENSION):
|
142
|
-
metadata = entry.metadata if hasattr(entry,
|
140
|
+
metadata = entry.metadata if hasattr(entry, "metadata") else {}
|
143
141
|
tools.append((name, metadata))
|
144
142
|
return tools
|
145
|
-
|
143
|
+
|
146
144
|
def get_tool_info(self, name: str) -> dict[str, Any] | None:
|
147
145
|
"""
|
148
146
|
Get information about a specific tool.
|
149
|
-
|
147
|
+
|
150
148
|
Args:
|
151
149
|
name: Tool name or alias.
|
152
|
-
|
150
|
+
|
153
151
|
Returns:
|
154
152
|
Tool metadata dictionary, or None if not found.
|
155
153
|
"""
|
156
154
|
entry = self.hub.registry.get_entry(name, dimension=self.DIMENSION)
|
157
|
-
if entry and hasattr(entry,
|
155
|
+
if entry and hasattr(entry, "metadata"):
|
158
156
|
return entry.metadata
|
159
157
|
return None
|
160
|
-
|
158
|
+
|
161
159
|
def is_tool_registered(self, name: str) -> bool:
|
162
160
|
"""
|
163
161
|
Check if a tool is registered.
|
164
|
-
|
162
|
+
|
165
163
|
Args:
|
166
164
|
name: Tool name or alias.
|
167
|
-
|
165
|
+
|
168
166
|
Returns:
|
169
167
|
True if registered, False otherwise.
|
170
168
|
"""
|
@@ -178,7 +176,7 @@ _tool_registry: ToolRegistry | None = None
|
|
178
176
|
def get_tool_registry() -> ToolRegistry:
|
179
177
|
"""
|
180
178
|
Get the global tool registry instance.
|
181
|
-
|
179
|
+
|
182
180
|
Returns:
|
183
181
|
Tool registry instance.
|
184
182
|
"""
|
@@ -189,13 +187,11 @@ def get_tool_registry() -> ToolRegistry:
|
|
189
187
|
|
190
188
|
|
191
189
|
def register_tool_manager(
|
192
|
-
name: str,
|
193
|
-
manager_class: type[BaseToolManager],
|
194
|
-
aliases: list[str] | None = None
|
190
|
+
name: str, manager_class: type[BaseToolManager], aliases: list[str] | None = None
|
195
191
|
) -> None:
|
196
192
|
"""
|
197
193
|
Register a tool manager with the global registry.
|
198
|
-
|
194
|
+
|
199
195
|
Args:
|
200
196
|
name: Tool name.
|
201
197
|
manager_class: Tool manager class.
|
@@ -205,19 +201,16 @@ def register_tool_manager(
|
|
205
201
|
registry.register_tool_manager(name, manager_class, aliases)
|
206
202
|
|
207
203
|
|
208
|
-
def get_tool_manager(
|
209
|
-
name: str,
|
210
|
-
config: BaseConfig
|
211
|
-
) -> BaseToolManager | None:
|
204
|
+
def get_tool_manager(name: str, config: BaseConfig) -> BaseToolManager | None:
|
212
205
|
"""
|
213
206
|
Get a tool manager instance from the global registry.
|
214
|
-
|
207
|
+
|
215
208
|
Args:
|
216
209
|
name: Tool name or alias.
|
217
210
|
config: Configuration object.
|
218
|
-
|
211
|
+
|
219
212
|
Returns:
|
220
213
|
Tool manager instance, or None if not found.
|
221
214
|
"""
|
222
215
|
registry = get_tool_registry()
|
223
|
-
return registry.create_tool_manager(name, config)
|
216
|
+
return registry.create_tool_manager(name, config)
|
@@ -6,7 +6,6 @@ semver ranges, wildcards, and pre-release handling.
|
|
6
6
|
"""
|
7
7
|
|
8
8
|
import re
|
9
|
-
from typing import Any
|
10
9
|
|
11
10
|
from provide.foundation.errors import FoundationError
|
12
11
|
from provide.foundation.logger import get_logger
|
@@ -16,14 +15,14 @@ log = get_logger(__name__)
|
|
16
15
|
|
17
16
|
class ResolutionError(FoundationError):
|
18
17
|
"""Raised when version resolution fails."""
|
19
|
-
|
18
|
+
|
20
19
|
pass
|
21
20
|
|
22
21
|
|
23
22
|
class VersionResolver:
|
24
23
|
"""
|
25
24
|
Resolve version specifications to concrete versions.
|
26
|
-
|
25
|
+
|
27
26
|
Supports:
|
28
27
|
- "latest": Most recent stable version
|
29
28
|
- "latest-beta": Most recent pre-release
|
@@ -32,23 +31,23 @@ class VersionResolver:
|
|
32
31
|
- "1.2.*": Wildcard matching
|
33
32
|
- Exact versions
|
34
33
|
"""
|
35
|
-
|
34
|
+
|
36
35
|
def resolve(self, spec: str, available: list[str]) -> str | None:
|
37
36
|
"""
|
38
37
|
Resolve a version specification to a concrete version.
|
39
|
-
|
38
|
+
|
40
39
|
Args:
|
41
40
|
spec: Version specification.
|
42
41
|
available: List of available versions.
|
43
|
-
|
42
|
+
|
44
43
|
Returns:
|
45
44
|
Resolved version string, or None if not found.
|
46
45
|
"""
|
47
46
|
if not available:
|
48
47
|
return None
|
49
|
-
|
48
|
+
|
50
49
|
spec = spec.strip()
|
51
|
-
|
50
|
+
|
52
51
|
# Handle special keywords
|
53
52
|
if spec == "latest":
|
54
53
|
return self.get_latest_stable(available)
|
@@ -56,77 +55,77 @@ class VersionResolver:
|
|
56
55
|
return self.get_latest_prerelease(available)
|
57
56
|
elif spec == "latest-any":
|
58
57
|
return self.get_latest_any(available)
|
59
|
-
|
58
|
+
|
60
59
|
# Handle ranges
|
61
60
|
elif spec.startswith("~"):
|
62
61
|
return self.resolve_tilde(spec[1:], available)
|
63
62
|
elif spec.startswith("^"):
|
64
63
|
return self.resolve_caret(spec[1:], available)
|
65
|
-
|
64
|
+
|
66
65
|
# Handle wildcards
|
67
66
|
elif "*" in spec:
|
68
67
|
return self.resolve_wildcard(spec, available)
|
69
|
-
|
68
|
+
|
70
69
|
# Exact match
|
71
70
|
elif spec in available:
|
72
71
|
return spec
|
73
|
-
|
72
|
+
|
74
73
|
return None
|
75
|
-
|
74
|
+
|
76
75
|
def get_latest_stable(self, versions: list[str]) -> str | None:
|
77
76
|
"""
|
78
77
|
Get latest stable version (no pre-release).
|
79
|
-
|
78
|
+
|
80
79
|
Args:
|
81
80
|
versions: List of available versions.
|
82
|
-
|
81
|
+
|
83
82
|
Returns:
|
84
83
|
Latest stable version, or None if no stable versions.
|
85
84
|
"""
|
86
85
|
stable = [v for v in versions if not self.is_prerelease(v)]
|
87
86
|
if not stable:
|
88
87
|
return None
|
89
|
-
|
88
|
+
|
90
89
|
return self.sort_versions(stable)[-1]
|
91
|
-
|
90
|
+
|
92
91
|
def get_latest_prerelease(self, versions: list[str]) -> str | None:
|
93
92
|
"""
|
94
93
|
Get latest pre-release version.
|
95
|
-
|
94
|
+
|
96
95
|
Args:
|
97
96
|
versions: List of available versions.
|
98
|
-
|
97
|
+
|
99
98
|
Returns:
|
100
99
|
Latest pre-release version, or None if no pre-releases.
|
101
100
|
"""
|
102
101
|
prerelease = [v for v in versions if self.is_prerelease(v)]
|
103
102
|
if not prerelease:
|
104
103
|
return None
|
105
|
-
|
104
|
+
|
106
105
|
return self.sort_versions(prerelease)[-1]
|
107
|
-
|
106
|
+
|
108
107
|
def get_latest_any(self, versions: list[str]) -> str | None:
|
109
108
|
"""
|
110
109
|
Get latest version (including pre-releases).
|
111
|
-
|
110
|
+
|
112
111
|
Args:
|
113
112
|
versions: List of available versions.
|
114
|
-
|
113
|
+
|
115
114
|
Returns:
|
116
115
|
Latest version, or None if list is empty.
|
117
116
|
"""
|
118
117
|
if not versions:
|
119
118
|
return None
|
120
|
-
|
119
|
+
|
121
120
|
return self.sort_versions(versions)[-1]
|
122
|
-
|
121
|
+
|
123
122
|
def is_prerelease(self, version: str) -> bool:
|
124
123
|
"""
|
125
124
|
Check if version is a pre-release.
|
126
|
-
|
125
|
+
|
127
126
|
Args:
|
128
127
|
version: Version string.
|
129
|
-
|
128
|
+
|
130
129
|
Returns:
|
131
130
|
True if version appears to be pre-release.
|
132
131
|
"""
|
@@ -144,22 +143,22 @@ class VersionResolver:
|
|
144
143
|
r"b\d+$", # 1.0b2
|
145
144
|
r"rc\d+$", # 1.0rc3
|
146
145
|
]
|
147
|
-
|
146
|
+
|
148
147
|
version_lower = version.lower()
|
149
148
|
for pattern in prerelease_patterns:
|
150
149
|
if re.search(pattern, version_lower):
|
151
150
|
return True
|
152
|
-
|
151
|
+
|
153
152
|
return False
|
154
|
-
|
153
|
+
|
155
154
|
def resolve_tilde(self, base: str, available: list[str]) -> str | None:
|
156
155
|
"""
|
157
156
|
Resolve tilde range (~1.2.3 means >=1.2.3 <1.3.0).
|
158
|
-
|
157
|
+
|
159
158
|
Args:
|
160
159
|
base: Base version without tilde.
|
161
160
|
available: List of available versions.
|
162
|
-
|
161
|
+
|
163
162
|
Returns:
|
164
163
|
Best matching version, or None if no match.
|
165
164
|
"""
|
@@ -167,9 +166,9 @@ class VersionResolver:
|
|
167
166
|
parts = self.parse_version(base)
|
168
167
|
if len(parts) < 2:
|
169
168
|
return None
|
170
|
-
|
169
|
+
|
171
170
|
major, minor = parts[0], parts[1]
|
172
|
-
|
171
|
+
|
173
172
|
# Filter versions that match the constraint
|
174
173
|
matches = []
|
175
174
|
for v in available:
|
@@ -182,22 +181,22 @@ class VersionResolver:
|
|
182
181
|
matches.append(v)
|
183
182
|
else:
|
184
183
|
matches.append(v)
|
185
|
-
|
184
|
+
|
186
185
|
if matches:
|
187
186
|
return self.sort_versions(matches)[-1]
|
188
187
|
except Exception as e:
|
189
188
|
log.debug(f"Failed to resolve tilde range {base}: {e}")
|
190
|
-
|
189
|
+
|
191
190
|
return None
|
192
|
-
|
191
|
+
|
193
192
|
def resolve_caret(self, base: str, available: list[str]) -> str | None:
|
194
193
|
"""
|
195
194
|
Resolve caret range (^1.2.3 means >=1.2.3 <2.0.0).
|
196
|
-
|
195
|
+
|
197
196
|
Args:
|
198
197
|
base: Base version without caret.
|
199
198
|
available: List of available versions.
|
200
|
-
|
199
|
+
|
201
200
|
Returns:
|
202
201
|
Best matching version, or None if no match.
|
203
202
|
"""
|
@@ -205,9 +204,9 @@ class VersionResolver:
|
|
205
204
|
parts = self.parse_version(base)
|
206
205
|
if not parts:
|
207
206
|
return None
|
208
|
-
|
207
|
+
|
209
208
|
major = parts[0]
|
210
|
-
|
209
|
+
|
211
210
|
# Filter versions that match the constraint
|
212
211
|
matches = []
|
213
212
|
for v in available:
|
@@ -216,22 +215,22 @@ class VersionResolver:
|
|
216
215
|
# Must be >= base version
|
217
216
|
if self.compare_versions(v, base) >= 0:
|
218
217
|
matches.append(v)
|
219
|
-
|
218
|
+
|
220
219
|
if matches:
|
221
220
|
return self.sort_versions(matches)[-1]
|
222
221
|
except Exception as e:
|
223
222
|
log.debug(f"Failed to resolve caret range {base}: {e}")
|
224
|
-
|
223
|
+
|
225
224
|
return None
|
226
|
-
|
225
|
+
|
227
226
|
def resolve_wildcard(self, pattern: str, available: list[str]) -> str | None:
|
228
227
|
"""
|
229
228
|
Resolve wildcard pattern (1.2.* matches any 1.2.x).
|
230
|
-
|
229
|
+
|
231
230
|
Args:
|
232
231
|
pattern: Version pattern with wildcards.
|
233
232
|
available: List of available versions.
|
234
|
-
|
233
|
+
|
235
234
|
Returns:
|
236
235
|
Best matching version, or None if no match.
|
237
236
|
"""
|
@@ -239,26 +238,26 @@ class VersionResolver:
|
|
239
238
|
regex_pattern = pattern.replace(".", r"\.")
|
240
239
|
regex_pattern = regex_pattern.replace("*", r".*")
|
241
240
|
regex_pattern = f"^{regex_pattern}$"
|
242
|
-
|
241
|
+
|
243
242
|
try:
|
244
243
|
regex = re.compile(regex_pattern)
|
245
244
|
matches = [v for v in available if regex.match(v)]
|
246
|
-
|
245
|
+
|
247
246
|
if matches:
|
248
247
|
# Return latest matching version
|
249
248
|
return self.sort_versions(matches)[-1]
|
250
249
|
except Exception as e:
|
251
250
|
log.debug(f"Failed to resolve wildcard {pattern}: {e}")
|
252
|
-
|
251
|
+
|
253
252
|
return None
|
254
|
-
|
253
|
+
|
255
254
|
def parse_version(self, version: str) -> list[int]:
|
256
255
|
"""
|
257
256
|
Parse version string into numeric components.
|
258
|
-
|
257
|
+
|
259
258
|
Args:
|
260
259
|
version: Version string.
|
261
|
-
|
260
|
+
|
262
261
|
Returns:
|
263
262
|
List of numeric version components.
|
264
263
|
"""
|
@@ -266,56 +265,59 @@ class VersionResolver:
|
|
266
265
|
match = re.match(r"^v?(\d+(?:\.\d+)*)", version)
|
267
266
|
if not match:
|
268
267
|
return []
|
269
|
-
|
268
|
+
|
270
269
|
version_str = match.group(1)
|
271
270
|
parts = []
|
272
|
-
|
271
|
+
|
273
272
|
for part in version_str.split("."):
|
274
273
|
try:
|
275
274
|
parts.append(int(part))
|
276
275
|
except ValueError:
|
277
276
|
break
|
278
|
-
|
277
|
+
|
279
278
|
return parts
|
280
|
-
|
279
|
+
|
281
280
|
def compare_versions(self, v1: str, v2: str) -> int:
|
282
281
|
"""
|
283
282
|
Compare two versions.
|
284
|
-
|
283
|
+
|
285
284
|
Args:
|
286
285
|
v1: First version.
|
287
286
|
v2: Second version.
|
288
|
-
|
287
|
+
|
289
288
|
Returns:
|
290
289
|
-1 if v1 < v2, 0 if equal, 1 if v1 > v2.
|
291
290
|
"""
|
292
291
|
parts1 = self.parse_version(v1)
|
293
292
|
parts2 = self.parse_version(v2)
|
294
|
-
|
293
|
+
|
295
294
|
# Pad with zeros
|
296
295
|
max_len = max(len(parts1), len(parts2))
|
297
296
|
parts1.extend([0] * (max_len - len(parts1)))
|
298
297
|
parts2.extend([0] * (max_len - len(parts2)))
|
299
|
-
|
298
|
+
|
300
299
|
for p1, p2 in zip(parts1, parts2):
|
301
300
|
if p1 < p2:
|
302
301
|
return -1
|
303
302
|
elif p1 > p2:
|
304
303
|
return 1
|
305
|
-
|
304
|
+
|
306
305
|
return 0
|
307
|
-
|
306
|
+
|
308
307
|
def sort_versions(self, versions: list[str]) -> list[str]:
|
309
308
|
"""
|
310
309
|
Sort versions in ascending order.
|
311
|
-
|
310
|
+
|
312
311
|
Args:
|
313
312
|
versions: List of version strings.
|
314
|
-
|
313
|
+
|
315
314
|
Returns:
|
316
315
|
Sorted list of versions.
|
317
316
|
"""
|
318
|
-
return sorted(
|
319
|
-
|
320
|
-
v
|
321
|
-
|
317
|
+
return sorted(
|
318
|
+
versions,
|
319
|
+
key=lambda v: (
|
320
|
+
self.parse_version(v),
|
321
|
+
v, # Secondary sort by string for pre-releases
|
322
|
+
),
|
323
|
+
)
|