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.
Files changed (155) hide show
  1. provide/foundation/__init__.py +20 -20
  2. provide/foundation/archive/__init__.py +1 -1
  3. provide/foundation/archive/base.py +15 -14
  4. provide/foundation/archive/bzip2.py +40 -40
  5. provide/foundation/archive/gzip.py +42 -42
  6. provide/foundation/archive/operations.py +90 -91
  7. provide/foundation/archive/tar.py +33 -31
  8. provide/foundation/archive/zip.py +52 -50
  9. provide/foundation/asynctools/__init__.py +20 -0
  10. provide/foundation/asynctools/core.py +126 -0
  11. provide/foundation/cli/__init__.py +2 -2
  12. provide/foundation/cli/commands/deps.py +4 -4
  13. provide/foundation/cli/commands/logs/__init__.py +2 -2
  14. provide/foundation/cli/commands/logs/generate.py +2 -2
  15. provide/foundation/cli/commands/logs/query.py +3 -3
  16. provide/foundation/cli/commands/logs/send.py +2 -2
  17. provide/foundation/cli/commands/logs/tail.py +2 -2
  18. provide/foundation/cli/decorators.py +0 -1
  19. provide/foundation/cli/testing.py +0 -5
  20. provide/foundation/cli/utils.py +1 -2
  21. provide/foundation/config/__init__.py +19 -19
  22. provide/foundation/config/base.py +2 -2
  23. provide/foundation/config/converters.py +81 -83
  24. provide/foundation/config/defaults.py +1 -1
  25. provide/foundation/config/env.py +2 -1
  26. provide/foundation/config/loader.py +1 -1
  27. provide/foundation/config/sync.py +8 -6
  28. provide/foundation/config/types.py +5 -5
  29. provide/foundation/config/validators.py +4 -4
  30. provide/foundation/console/output.py +7 -7
  31. provide/foundation/context/core.py +19 -17
  32. provide/foundation/crypto/certificates/__init__.py +9 -5
  33. provide/foundation/crypto/certificates/base.py +2 -2
  34. provide/foundation/crypto/certificates/certificate.py +48 -19
  35. provide/foundation/crypto/certificates/factory.py +26 -18
  36. provide/foundation/crypto/certificates/generator.py +24 -23
  37. provide/foundation/crypto/certificates/loader.py +24 -16
  38. provide/foundation/crypto/certificates/operations.py +17 -10
  39. provide/foundation/crypto/certificates/trust.py +21 -21
  40. provide/foundation/env/__init__.py +28 -0
  41. provide/foundation/env/core.py +218 -0
  42. provide/foundation/errors/__init__.py +3 -2
  43. provide/foundation/errors/decorators.py +0 -3
  44. provide/foundation/errors/types.py +0 -1
  45. provide/foundation/eventsets/display.py +13 -14
  46. provide/foundation/eventsets/registry.py +61 -31
  47. provide/foundation/eventsets/resolver.py +50 -46
  48. provide/foundation/eventsets/sets/das.py +8 -8
  49. provide/foundation/eventsets/sets/database.py +14 -14
  50. provide/foundation/eventsets/sets/http.py +21 -21
  51. provide/foundation/eventsets/sets/llm.py +16 -16
  52. provide/foundation/eventsets/sets/task_queue.py +13 -13
  53. provide/foundation/eventsets/types.py +7 -7
  54. provide/foundation/file/directory.py +1 -1
  55. provide/foundation/file/lock.py +2 -3
  56. provide/foundation/hub/components.py +19 -21
  57. provide/foundation/hub/config.py +25 -19
  58. provide/foundation/hub/discovery.py +5 -4
  59. provide/foundation/hub/handlers.py +13 -5
  60. provide/foundation/hub/lifecycle.py +10 -9
  61. provide/foundation/hub/manager.py +3 -0
  62. provide/foundation/hub/processors.py +8 -3
  63. provide/foundation/integrations/__init__.py +1 -1
  64. provide/foundation/integrations/openobserve/client.py +2 -2
  65. provide/foundation/integrations/openobserve/commands.py +9 -9
  66. provide/foundation/integrations/openobserve/config.py +2 -2
  67. provide/foundation/integrations/openobserve/otlp.py +2 -2
  68. provide/foundation/integrations/openobserve/search.py +1 -2
  69. provide/foundation/integrations/openobserve/streaming.py +1 -1
  70. provide/foundation/logger/__init__.py +0 -1
  71. provide/foundation/logger/config/base.py +1 -1
  72. provide/foundation/logger/config/logging.py +19 -19
  73. provide/foundation/logger/config/telemetry.py +11 -13
  74. provide/foundation/logger/factories.py +2 -2
  75. provide/foundation/logger/processors/main.py +12 -10
  76. provide/foundation/logger/ratelimit/limiters.py +4 -4
  77. provide/foundation/logger/ratelimit/processor.py +1 -1
  78. provide/foundation/logger/setup/coordinator.py +38 -24
  79. provide/foundation/logger/setup/processors.py +3 -3
  80. provide/foundation/logger/setup/testing.py +14 -0
  81. provide/foundation/logger/trace.py +5 -5
  82. provide/foundation/metrics/__init__.py +1 -1
  83. provide/foundation/metrics/otel.py +3 -1
  84. provide/foundation/observability/__init__.py +1 -1
  85. provide/foundation/process/__init__.py +1 -1
  86. provide/foundation/process/exit.py +6 -5
  87. provide/foundation/process/lifecycle.py +41 -18
  88. provide/foundation/resilience/__init__.py +6 -5
  89. provide/foundation/resilience/circuit.py +32 -30
  90. provide/foundation/resilience/decorators.py +58 -42
  91. provide/foundation/resilience/fallback.py +55 -40
  92. provide/foundation/resilience/retry.py +67 -65
  93. provide/foundation/serialization/__init__.py +16 -0
  94. provide/foundation/serialization/core.py +70 -0
  95. provide/foundation/streams/config.py +8 -9
  96. provide/foundation/streams/console.py +3 -3
  97. provide/foundation/streams/core.py +2 -2
  98. provide/foundation/streams/file.py +1 -1
  99. provide/foundation/testing/__init__.py +22 -7
  100. provide/foundation/testing/archive/__init__.py +7 -7
  101. provide/foundation/testing/archive/fixtures.py +58 -54
  102. provide/foundation/testing/cli.py +3 -6
  103. provide/foundation/testing/common/__init__.py +13 -13
  104. provide/foundation/testing/common/fixtures.py +27 -30
  105. provide/foundation/testing/file/__init__.py +15 -15
  106. provide/foundation/testing/file/content_fixtures.py +65 -92
  107. provide/foundation/testing/file/directory_fixtures.py +19 -19
  108. provide/foundation/testing/file/fixtures.py +14 -17
  109. provide/foundation/testing/file/special_fixtures.py +34 -42
  110. provide/foundation/testing/logger.py +28 -23
  111. provide/foundation/testing/mocking/__init__.py +21 -21
  112. provide/foundation/testing/mocking/fixtures.py +80 -67
  113. provide/foundation/testing/process/__init__.py +23 -23
  114. provide/foundation/testing/process/async_fixtures.py +89 -80
  115. provide/foundation/testing/process/fixtures.py +11 -13
  116. provide/foundation/testing/process/subprocess_fixtures.py +41 -40
  117. provide/foundation/testing/threading/__init__.py +17 -17
  118. provide/foundation/testing/threading/basic_fixtures.py +21 -17
  119. provide/foundation/testing/threading/data_fixtures.py +18 -16
  120. provide/foundation/testing/threading/execution_fixtures.py +67 -52
  121. provide/foundation/testing/threading/fixtures.py +10 -14
  122. provide/foundation/testing/threading/sync_fixtures.py +21 -18
  123. provide/foundation/testing/time/__init__.py +11 -11
  124. provide/foundation/testing/time/fixtures.py +91 -79
  125. provide/foundation/testing/transport/__init__.py +9 -9
  126. provide/foundation/testing/transport/fixtures.py +54 -54
  127. provide/foundation/time/__init__.py +18 -0
  128. provide/foundation/time/core.py +63 -0
  129. provide/foundation/tools/__init__.py +2 -2
  130. provide/foundation/tools/base.py +68 -67
  131. provide/foundation/tools/cache.py +62 -69
  132. provide/foundation/tools/downloader.py +51 -56
  133. provide/foundation/tools/installer.py +51 -57
  134. provide/foundation/tools/registry.py +38 -45
  135. provide/foundation/tools/resolver.py +70 -68
  136. provide/foundation/tools/verifier.py +39 -50
  137. provide/foundation/tracer/spans.py +1 -13
  138. provide/foundation/transport/__init__.py +26 -33
  139. provide/foundation/transport/base.py +32 -30
  140. provide/foundation/transport/client.py +44 -49
  141. provide/foundation/transport/config.py +11 -13
  142. provide/foundation/transport/errors.py +13 -27
  143. provide/foundation/transport/http.py +69 -55
  144. provide/foundation/transport/middleware.py +86 -81
  145. provide/foundation/transport/registry.py +29 -27
  146. provide/foundation/transport/types.py +6 -6
  147. provide/foundation/utils/deps.py +3 -2
  148. provide/foundation/utils/parsing.py +7 -7
  149. {provide_foundation-0.0.0.dev2.dist-info → provide_foundation-0.0.0.dev3.dist-info}/METADATA +2 -2
  150. provide_foundation-0.0.0.dev3.dist-info/RECORD +233 -0
  151. provide_foundation-0.0.0.dev2.dist-info/RECORD +0 -225
  152. {provide_foundation-0.0.0.dev2.dist-info → provide_foundation-0.0.0.dev3.dist-info}/WHEEL +0 -0
  153. {provide_foundation-0.0.0.dev2.dist-info → provide_foundation-0.0.0.dev3.dist-info}/entry_points.txt +0 -0
  154. {provide_foundation-0.0.0.dev2.dist-info → provide_foundation-0.0.0.dev3.dist-info}/licenses/LICENSE +0 -0
  155. {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, 'entry_points'):
44
+ if hasattr(importlib.metadata, "entry_points"):
45
45
  # Python 3.10+
46
46
  eps = importlib.metadata.entry_points()
47
- if hasattr(eps, 'select'):
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, 'metadata') else {}
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, 'metadata'):
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(versions, key=lambda v: (
319
- self.parse_version(v),
320
- v # Secondary sort by string for pre-releases
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
+ )