provide-foundation 0.0.0.dev0__py3-none-any.whl → 0.0.0.dev2__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 (161) hide show
  1. provide/foundation/__init__.py +41 -23
  2. provide/foundation/archive/__init__.py +23 -0
  3. provide/foundation/archive/base.py +70 -0
  4. provide/foundation/archive/bzip2.py +157 -0
  5. provide/foundation/archive/gzip.py +159 -0
  6. provide/foundation/archive/operations.py +334 -0
  7. provide/foundation/archive/tar.py +164 -0
  8. provide/foundation/archive/zip.py +203 -0
  9. provide/foundation/cli/__init__.py +2 -2
  10. provide/foundation/cli/commands/deps.py +13 -7
  11. provide/foundation/cli/commands/logs/__init__.py +1 -1
  12. provide/foundation/cli/commands/logs/query.py +1 -1
  13. provide/foundation/cli/commands/logs/send.py +1 -1
  14. provide/foundation/cli/commands/logs/tail.py +1 -1
  15. provide/foundation/cli/decorators.py +11 -10
  16. provide/foundation/cli/main.py +1 -1
  17. provide/foundation/cli/testing.py +2 -35
  18. provide/foundation/cli/utils.py +21 -17
  19. provide/foundation/config/__init__.py +35 -2
  20. provide/foundation/config/base.py +2 -2
  21. provide/foundation/config/converters.py +479 -0
  22. provide/foundation/config/defaults.py +67 -0
  23. provide/foundation/config/env.py +4 -19
  24. provide/foundation/config/loader.py +9 -3
  25. provide/foundation/config/sync.py +19 -4
  26. provide/foundation/console/input.py +5 -5
  27. provide/foundation/console/output.py +35 -13
  28. provide/foundation/context/__init__.py +8 -4
  29. provide/foundation/context/core.py +85 -109
  30. provide/foundation/core.py +1 -2
  31. provide/foundation/crypto/__init__.py +2 -0
  32. provide/foundation/crypto/certificates/__init__.py +34 -0
  33. provide/foundation/crypto/certificates/base.py +173 -0
  34. provide/foundation/crypto/certificates/certificate.py +290 -0
  35. provide/foundation/crypto/certificates/factory.py +213 -0
  36. provide/foundation/crypto/certificates/generator.py +138 -0
  37. provide/foundation/crypto/certificates/loader.py +130 -0
  38. provide/foundation/crypto/certificates/operations.py +198 -0
  39. provide/foundation/crypto/certificates/trust.py +107 -0
  40. provide/foundation/errors/__init__.py +2 -3
  41. provide/foundation/errors/decorators.py +0 -231
  42. provide/foundation/errors/types.py +0 -97
  43. provide/foundation/eventsets/__init__.py +0 -0
  44. provide/foundation/eventsets/display.py +84 -0
  45. provide/foundation/eventsets/registry.py +160 -0
  46. provide/foundation/eventsets/resolver.py +192 -0
  47. provide/foundation/eventsets/sets/das.py +128 -0
  48. provide/foundation/eventsets/sets/database.py +125 -0
  49. provide/foundation/eventsets/sets/http.py +153 -0
  50. provide/foundation/eventsets/sets/llm.py +139 -0
  51. provide/foundation/eventsets/sets/task_queue.py +107 -0
  52. provide/foundation/eventsets/types.py +70 -0
  53. provide/foundation/file/directory.py +13 -22
  54. provide/foundation/file/lock.py +3 -1
  55. provide/foundation/hub/components.py +77 -515
  56. provide/foundation/hub/config.py +151 -0
  57. provide/foundation/hub/discovery.py +62 -0
  58. provide/foundation/hub/handlers.py +81 -0
  59. provide/foundation/hub/lifecycle.py +194 -0
  60. provide/foundation/hub/manager.py +4 -4
  61. provide/foundation/hub/processors.py +44 -0
  62. provide/foundation/integrations/__init__.py +11 -0
  63. provide/foundation/{observability → integrations}/openobserve/__init__.py +10 -7
  64. provide/foundation/{observability → integrations}/openobserve/auth.py +1 -1
  65. provide/foundation/{observability → integrations}/openobserve/client.py +12 -12
  66. provide/foundation/{observability → integrations}/openobserve/commands.py +3 -3
  67. provide/foundation/integrations/openobserve/config.py +37 -0
  68. provide/foundation/{observability → integrations}/openobserve/formatters.py +1 -1
  69. provide/foundation/{observability → integrations}/openobserve/otlp.py +1 -1
  70. provide/foundation/{observability → integrations}/openobserve/search.py +2 -2
  71. provide/foundation/{observability → integrations}/openobserve/streaming.py +4 -4
  72. provide/foundation/logger/__init__.py +3 -10
  73. provide/foundation/logger/config/logging.py +68 -298
  74. provide/foundation/logger/config/telemetry.py +41 -121
  75. provide/foundation/logger/core.py +0 -2
  76. provide/foundation/logger/custom_processors.py +1 -0
  77. provide/foundation/logger/factories.py +11 -2
  78. provide/foundation/logger/processors/main.py +20 -84
  79. provide/foundation/logger/setup/__init__.py +5 -1
  80. provide/foundation/logger/setup/coordinator.py +76 -24
  81. provide/foundation/logger/setup/processors.py +2 -9
  82. provide/foundation/logger/trace.py +27 -0
  83. provide/foundation/metrics/otel.py +10 -10
  84. provide/foundation/observability/__init__.py +2 -2
  85. provide/foundation/process/__init__.py +9 -0
  86. provide/foundation/process/exit.py +47 -0
  87. provide/foundation/process/lifecycle.py +115 -59
  88. provide/foundation/resilience/__init__.py +35 -0
  89. provide/foundation/resilience/circuit.py +164 -0
  90. provide/foundation/resilience/decorators.py +220 -0
  91. provide/foundation/resilience/fallback.py +193 -0
  92. provide/foundation/resilience/retry.py +325 -0
  93. provide/foundation/streams/config.py +79 -0
  94. provide/foundation/streams/console.py +7 -8
  95. provide/foundation/streams/core.py +6 -3
  96. provide/foundation/streams/file.py +12 -2
  97. provide/foundation/testing/__init__.py +84 -2
  98. provide/foundation/testing/archive/__init__.py +24 -0
  99. provide/foundation/testing/archive/fixtures.py +217 -0
  100. provide/foundation/testing/cli.py +30 -17
  101. provide/foundation/testing/common/__init__.py +32 -0
  102. provide/foundation/testing/common/fixtures.py +236 -0
  103. provide/foundation/testing/file/__init__.py +40 -0
  104. provide/foundation/testing/file/content_fixtures.py +316 -0
  105. provide/foundation/testing/file/directory_fixtures.py +107 -0
  106. provide/foundation/testing/file/fixtures.py +52 -0
  107. provide/foundation/testing/file/special_fixtures.py +153 -0
  108. provide/foundation/testing/logger.py +117 -11
  109. provide/foundation/testing/mocking/__init__.py +46 -0
  110. provide/foundation/testing/mocking/fixtures.py +331 -0
  111. provide/foundation/testing/process/__init__.py +48 -0
  112. provide/foundation/testing/process/async_fixtures.py +405 -0
  113. provide/foundation/testing/process/fixtures.py +56 -0
  114. provide/foundation/testing/process/subprocess_fixtures.py +209 -0
  115. provide/foundation/testing/threading/__init__.py +38 -0
  116. provide/foundation/testing/threading/basic_fixtures.py +101 -0
  117. provide/foundation/testing/threading/data_fixtures.py +99 -0
  118. provide/foundation/testing/threading/execution_fixtures.py +263 -0
  119. provide/foundation/testing/threading/fixtures.py +54 -0
  120. provide/foundation/testing/threading/sync_fixtures.py +97 -0
  121. provide/foundation/testing/time/__init__.py +32 -0
  122. provide/foundation/testing/time/fixtures.py +409 -0
  123. provide/foundation/testing/transport/__init__.py +30 -0
  124. provide/foundation/testing/transport/fixtures.py +280 -0
  125. provide/foundation/tools/__init__.py +58 -0
  126. provide/foundation/tools/base.py +348 -0
  127. provide/foundation/tools/cache.py +268 -0
  128. provide/foundation/tools/downloader.py +224 -0
  129. provide/foundation/tools/installer.py +254 -0
  130. provide/foundation/tools/registry.py +223 -0
  131. provide/foundation/tools/resolver.py +321 -0
  132. provide/foundation/tools/verifier.py +186 -0
  133. provide/foundation/tracer/otel.py +7 -11
  134. provide/foundation/tracer/spans.py +2 -2
  135. provide/foundation/transport/__init__.py +155 -0
  136. provide/foundation/transport/base.py +171 -0
  137. provide/foundation/transport/client.py +266 -0
  138. provide/foundation/transport/config.py +140 -0
  139. provide/foundation/transport/errors.py +79 -0
  140. provide/foundation/transport/http.py +232 -0
  141. provide/foundation/transport/middleware.py +360 -0
  142. provide/foundation/transport/registry.py +167 -0
  143. provide/foundation/transport/types.py +45 -0
  144. provide/foundation/utils/deps.py +14 -12
  145. provide/foundation/utils/parsing.py +49 -4
  146. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev2.dist-info}/METADATA +5 -28
  147. provide_foundation-0.0.0.dev2.dist-info/RECORD +225 -0
  148. provide/foundation/cli/commands/logs/generate_old.py +0 -569
  149. provide/foundation/crypto/certificates.py +0 -896
  150. provide/foundation/logger/emoji/__init__.py +0 -44
  151. provide/foundation/logger/emoji/matrix.py +0 -209
  152. provide/foundation/logger/emoji/sets.py +0 -458
  153. provide/foundation/logger/emoji/types.py +0 -56
  154. provide/foundation/logger/setup/emoji_resolver.py +0 -64
  155. provide_foundation-0.0.0.dev0.dist-info/RECORD +0 -149
  156. /provide/foundation/{observability → integrations}/openobserve/exceptions.py +0 -0
  157. /provide/foundation/{observability → integrations}/openobserve/models.py +0 -0
  158. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev2.dist-info}/WHEEL +0 -0
  159. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev2.dist-info}/entry_points.txt +0 -0
  160. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev2.dist-info}/licenses/LICENSE +0 -0
  161. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,321 @@
1
+ """
2
+ Version resolution for tool management.
3
+
4
+ Provides sophisticated version resolution including latest,
5
+ semver ranges, wildcards, and pre-release handling.
6
+ """
7
+
8
+ import re
9
+ from typing import Any
10
+
11
+ from provide.foundation.errors import FoundationError
12
+ from provide.foundation.logger import get_logger
13
+
14
+ log = get_logger(__name__)
15
+
16
+
17
+ class ResolutionError(FoundationError):
18
+ """Raised when version resolution fails."""
19
+
20
+ pass
21
+
22
+
23
+ class VersionResolver:
24
+ """
25
+ Resolve version specifications to concrete versions.
26
+
27
+ Supports:
28
+ - "latest": Most recent stable version
29
+ - "latest-beta": Most recent pre-release
30
+ - "~1.2.3": Patch version range
31
+ - "^1.2.3": Minor version range
32
+ - "1.2.*": Wildcard matching
33
+ - Exact versions
34
+ """
35
+
36
+ def resolve(self, spec: str, available: list[str]) -> str | None:
37
+ """
38
+ Resolve a version specification to a concrete version.
39
+
40
+ Args:
41
+ spec: Version specification.
42
+ available: List of available versions.
43
+
44
+ Returns:
45
+ Resolved version string, or None if not found.
46
+ """
47
+ if not available:
48
+ return None
49
+
50
+ spec = spec.strip()
51
+
52
+ # Handle special keywords
53
+ if spec == "latest":
54
+ return self.get_latest_stable(available)
55
+ elif spec == "latest-beta" or spec == "latest-prerelease":
56
+ return self.get_latest_prerelease(available)
57
+ elif spec == "latest-any":
58
+ return self.get_latest_any(available)
59
+
60
+ # Handle ranges
61
+ elif spec.startswith("~"):
62
+ return self.resolve_tilde(spec[1:], available)
63
+ elif spec.startswith("^"):
64
+ return self.resolve_caret(spec[1:], available)
65
+
66
+ # Handle wildcards
67
+ elif "*" in spec:
68
+ return self.resolve_wildcard(spec, available)
69
+
70
+ # Exact match
71
+ elif spec in available:
72
+ return spec
73
+
74
+ return None
75
+
76
+ def get_latest_stable(self, versions: list[str]) -> str | None:
77
+ """
78
+ Get latest stable version (no pre-release).
79
+
80
+ Args:
81
+ versions: List of available versions.
82
+
83
+ Returns:
84
+ Latest stable version, or None if no stable versions.
85
+ """
86
+ stable = [v for v in versions if not self.is_prerelease(v)]
87
+ if not stable:
88
+ return None
89
+
90
+ return self.sort_versions(stable)[-1]
91
+
92
+ def get_latest_prerelease(self, versions: list[str]) -> str | None:
93
+ """
94
+ Get latest pre-release version.
95
+
96
+ Args:
97
+ versions: List of available versions.
98
+
99
+ Returns:
100
+ Latest pre-release version, or None if no pre-releases.
101
+ """
102
+ prerelease = [v for v in versions if self.is_prerelease(v)]
103
+ if not prerelease:
104
+ return None
105
+
106
+ return self.sort_versions(prerelease)[-1]
107
+
108
+ def get_latest_any(self, versions: list[str]) -> str | None:
109
+ """
110
+ Get latest version (including pre-releases).
111
+
112
+ Args:
113
+ versions: List of available versions.
114
+
115
+ Returns:
116
+ Latest version, or None if list is empty.
117
+ """
118
+ if not versions:
119
+ return None
120
+
121
+ return self.sort_versions(versions)[-1]
122
+
123
+ def is_prerelease(self, version: str) -> bool:
124
+ """
125
+ Check if version is a pre-release.
126
+
127
+ Args:
128
+ version: Version string.
129
+
130
+ Returns:
131
+ True if version appears to be pre-release.
132
+ """
133
+ # Common pre-release indicators
134
+ prerelease_patterns = [
135
+ r"-alpha",
136
+ r"-beta",
137
+ r"-rc",
138
+ r"-dev",
139
+ r"-preview",
140
+ r"-pre",
141
+ r"-snapshot",
142
+ r"\.dev\d+",
143
+ r"a\d+$", # 1.0a1
144
+ r"b\d+$", # 1.0b2
145
+ r"rc\d+$", # 1.0rc3
146
+ ]
147
+
148
+ version_lower = version.lower()
149
+ for pattern in prerelease_patterns:
150
+ if re.search(pattern, version_lower):
151
+ return True
152
+
153
+ return False
154
+
155
+ def resolve_tilde(self, base: str, available: list[str]) -> str | None:
156
+ """
157
+ Resolve tilde range (~1.2.3 means >=1.2.3 <1.3.0).
158
+
159
+ Args:
160
+ base: Base version without tilde.
161
+ available: List of available versions.
162
+
163
+ Returns:
164
+ Best matching version, or None if no match.
165
+ """
166
+ try:
167
+ parts = self.parse_version(base)
168
+ if len(parts) < 2:
169
+ return None
170
+
171
+ major, minor = parts[0], parts[1]
172
+
173
+ # Filter versions that match the constraint
174
+ matches = []
175
+ for v in available:
176
+ v_parts = self.parse_version(v)
177
+ if len(v_parts) >= 2:
178
+ if v_parts[0] == major and v_parts[1] == minor:
179
+ if len(parts) >= 3:
180
+ # If patch specified, must be >= base patch
181
+ if len(v_parts) >= 3 and v_parts[2] >= parts[2]:
182
+ matches.append(v)
183
+ else:
184
+ matches.append(v)
185
+
186
+ if matches:
187
+ return self.sort_versions(matches)[-1]
188
+ except Exception as e:
189
+ log.debug(f"Failed to resolve tilde range {base}: {e}")
190
+
191
+ return None
192
+
193
+ def resolve_caret(self, base: str, available: list[str]) -> str | None:
194
+ """
195
+ Resolve caret range (^1.2.3 means >=1.2.3 <2.0.0).
196
+
197
+ Args:
198
+ base: Base version without caret.
199
+ available: List of available versions.
200
+
201
+ Returns:
202
+ Best matching version, or None if no match.
203
+ """
204
+ try:
205
+ parts = self.parse_version(base)
206
+ if not parts:
207
+ return None
208
+
209
+ major = parts[0]
210
+
211
+ # Filter versions that match the constraint
212
+ matches = []
213
+ for v in available:
214
+ v_parts = self.parse_version(v)
215
+ if v_parts and v_parts[0] == major:
216
+ # Must be >= base version
217
+ if self.compare_versions(v, base) >= 0:
218
+ matches.append(v)
219
+
220
+ if matches:
221
+ return self.sort_versions(matches)[-1]
222
+ except Exception as e:
223
+ log.debug(f"Failed to resolve caret range {base}: {e}")
224
+
225
+ return None
226
+
227
+ def resolve_wildcard(self, pattern: str, available: list[str]) -> str | None:
228
+ """
229
+ Resolve wildcard pattern (1.2.* matches any 1.2.x).
230
+
231
+ Args:
232
+ pattern: Version pattern with wildcards.
233
+ available: List of available versions.
234
+
235
+ Returns:
236
+ Best matching version, or None if no match.
237
+ """
238
+ # Convert wildcard to regex
239
+ regex_pattern = pattern.replace(".", r"\.")
240
+ regex_pattern = regex_pattern.replace("*", r".*")
241
+ regex_pattern = f"^{regex_pattern}$"
242
+
243
+ try:
244
+ regex = re.compile(regex_pattern)
245
+ matches = [v for v in available if regex.match(v)]
246
+
247
+ if matches:
248
+ # Return latest matching version
249
+ return self.sort_versions(matches)[-1]
250
+ except Exception as e:
251
+ log.debug(f"Failed to resolve wildcard {pattern}: {e}")
252
+
253
+ return None
254
+
255
+ def parse_version(self, version: str) -> list[int]:
256
+ """
257
+ Parse version string into numeric components.
258
+
259
+ Args:
260
+ version: Version string.
261
+
262
+ Returns:
263
+ List of numeric version components.
264
+ """
265
+ # Extract just the numeric version part
266
+ match = re.match(r"^v?(\d+(?:\.\d+)*)", version)
267
+ if not match:
268
+ return []
269
+
270
+ version_str = match.group(1)
271
+ parts = []
272
+
273
+ for part in version_str.split("."):
274
+ try:
275
+ parts.append(int(part))
276
+ except ValueError:
277
+ break
278
+
279
+ return parts
280
+
281
+ def compare_versions(self, v1: str, v2: str) -> int:
282
+ """
283
+ Compare two versions.
284
+
285
+ Args:
286
+ v1: First version.
287
+ v2: Second version.
288
+
289
+ Returns:
290
+ -1 if v1 < v2, 0 if equal, 1 if v1 > v2.
291
+ """
292
+ parts1 = self.parse_version(v1)
293
+ parts2 = self.parse_version(v2)
294
+
295
+ # Pad with zeros
296
+ max_len = max(len(parts1), len(parts2))
297
+ parts1.extend([0] * (max_len - len(parts1)))
298
+ parts2.extend([0] * (max_len - len(parts2)))
299
+
300
+ for p1, p2 in zip(parts1, parts2):
301
+ if p1 < p2:
302
+ return -1
303
+ elif p1 > p2:
304
+ return 1
305
+
306
+ return 0
307
+
308
+ def sort_versions(self, versions: list[str]) -> list[str]:
309
+ """
310
+ Sort versions in ascending order.
311
+
312
+ Args:
313
+ versions: List of version strings.
314
+
315
+ Returns:
316
+ Sorted list of versions.
317
+ """
318
+ return sorted(versions, key=lambda v: (
319
+ self.parse_version(v),
320
+ v # Secondary sort by string for pre-releases
321
+ ))
@@ -0,0 +1,186 @@
1
+ """
2
+ Tool verification system for checksums and signatures.
3
+
4
+ Provides capabilities for verifying downloaded tools using various
5
+ checksum algorithms and GPG/PGP signatures.
6
+ """
7
+
8
+ import hashlib
9
+ import re
10
+ from pathlib import Path
11
+ from typing import Literal
12
+
13
+ from provide.foundation.errors import FoundationError
14
+ from provide.foundation.logger import get_logger
15
+
16
+ log = get_logger(__name__)
17
+
18
+
19
+ class VerificationError(FoundationError):
20
+ """Raised when verification fails."""
21
+
22
+ pass
23
+
24
+
25
+ HashAlgo = Literal["sha256", "sha512", "md5", "blake2b"]
26
+
27
+
28
+ class ToolVerifier:
29
+ """
30
+ Verify tool artifacts using checksums and signatures.
31
+
32
+ Supports multiple checksum algorithms and GPG/PGP signatures
33
+ for ensuring artifact integrity and authenticity.
34
+ """
35
+
36
+ SUPPORTED_ALGORITHMS = ["sha256", "sha512", "md5", "blake2b"]
37
+ CHUNK_SIZE = 8192 # Read files in 8KB chunks
38
+
39
+ def verify_checksum(
40
+ self,
41
+ file_path: Path,
42
+ expected: str,
43
+ algo: HashAlgo = "sha256"
44
+ ) -> bool:
45
+ """
46
+ Verify file checksum.
47
+
48
+ Args:
49
+ file_path: Path to file to verify.
50
+ expected: Expected checksum (hex string).
51
+ algo: Hash algorithm to use.
52
+
53
+ Returns:
54
+ True if checksum matches, False otherwise.
55
+
56
+ Raises:
57
+ ValueError: If algorithm is not supported.
58
+ FileNotFoundError: If file doesn't exist.
59
+ """
60
+ if algo not in self.SUPPORTED_ALGORITHMS:
61
+ raise ValueError(f"Unsupported hash algorithm: {algo}")
62
+
63
+ if not file_path.exists():
64
+ raise FileNotFoundError(f"File not found: {file_path}")
65
+
66
+ log.debug(f"Verifying {algo} checksum for {file_path}")
67
+
68
+ # Create hasher
69
+ hasher = hashlib.new(algo)
70
+
71
+ # Read file in chunks
72
+ with file_path.open("rb") as f:
73
+ while chunk := f.read(self.CHUNK_SIZE):
74
+ hasher.update(chunk)
75
+
76
+ actual = hasher.hexdigest()
77
+ matches = actual == expected
78
+
79
+ if not matches:
80
+ log.warning(
81
+ f"Checksum mismatch for {file_path.name}: "
82
+ f"expected {expected}, got {actual}"
83
+ )
84
+
85
+ return matches
86
+
87
+ def verify_shasums_file(
88
+ self,
89
+ shasums_file: Path,
90
+ target_file: Path
91
+ ) -> bool:
92
+ """
93
+ Verify using a shasums file (common for Go/Terraform).
94
+
95
+ Args:
96
+ shasums_file: Path to shasums file.
97
+ target_file: Path to file to verify.
98
+
99
+ Returns:
100
+ True if file is listed and checksum matches, False otherwise.
101
+ """
102
+ log.debug(f"Verifying {target_file.name} using {shasums_file}")
103
+
104
+ with shasums_file.open() as f:
105
+ for line in f:
106
+ line = line.strip()
107
+ if not line:
108
+ continue
109
+
110
+ # Parse line: "checksum filename" or "checksum *filename"
111
+ parts = line.split(None, 1)
112
+ if len(parts) != 2:
113
+ continue
114
+
115
+ checksum, filename = parts
116
+ # Remove asterisk prefix if present (binary mode indicator)
117
+ filename = filename.lstrip("*")
118
+
119
+ # Check if this is our file
120
+ if filename == target_file.name:
121
+ return self.verify_checksum(target_file, checksum)
122
+
123
+ # File not found in shasums
124
+ log.warning(f"{target_file.name} not found in {shasums_file}")
125
+ return False
126
+
127
+ def verify_signature(
128
+ self,
129
+ file_path: Path,
130
+ signature: str,
131
+ public_key: str | None = None
132
+ ) -> bool:
133
+ """
134
+ Verify GPG/PGP signature.
135
+
136
+ Args:
137
+ file_path: Path to file to verify.
138
+ signature: Signature data.
139
+ public_key: Optional public key for verification.
140
+
141
+ Returns:
142
+ True if signature is valid, False otherwise.
143
+ """
144
+ log.debug(f"Verifying signature for {file_path}")
145
+
146
+ try:
147
+ # Use foundation's crypto module
148
+ from provide.foundation.crypto import verify_signature
149
+
150
+ return verify_signature(file_path, signature, public_key)
151
+ except ImportError:
152
+ log.warning("Crypto module not available, skipping signature verification")
153
+ return True # Skip if crypto not available
154
+ except Exception as e:
155
+ log.error(f"Signature verification failed: {e}")
156
+ return False
157
+
158
+ def extract_checksum(self, checksum_string: str) -> str:
159
+ """
160
+ Extract checksum from various string formats.
161
+
162
+ Handles formats like:
163
+ - "abc123"
164
+ - "abc123 filename.tar.gz"
165
+ - "sha256:abc123"
166
+ - "SHA256:def456"
167
+
168
+ Args:
169
+ checksum_string: String containing checksum.
170
+
171
+ Returns:
172
+ Extracted checksum hex string.
173
+ """
174
+ checksum_string = checksum_string.strip()
175
+
176
+ # Remove algorithm prefix if present
177
+ if ":" in checksum_string:
178
+ checksum_string = checksum_string.split(":", 1)[1]
179
+
180
+ # Take first word (checksum is before any whitespace)
181
+ checksum = checksum_string.split()[0]
182
+
183
+ # Remove any asterisk prefix (binary mode indicator)
184
+ checksum = checksum.lstrip("*")
185
+
186
+ return checksum
@@ -1,9 +1,9 @@
1
1
  """OpenTelemetry integration for Foundation tracer."""
2
2
 
3
- from provide.foundation.logger import get_logger
4
3
  from provide.foundation.logger.config.telemetry import TelemetryConfig
4
+ from provide.foundation.logger.setup import get_vanilla_logger
5
5
 
6
- log = get_logger(__name__)
6
+ slog = get_vanilla_logger(__name__)
7
7
 
8
8
  # Feature detection
9
9
  try:
@@ -49,16 +49,12 @@ def setup_opentelemetry_tracing(config: TelemetryConfig) -> None:
49
49
  """
50
50
  # Check if tracing is disabled first, before checking dependencies
51
51
  if not config.tracing_enabled or config.globally_disabled:
52
- log.debug("🔍 OpenTelemetry tracing disabled")
53
52
  return
54
53
 
55
54
  # Check if OpenTelemetry is available
56
55
  if not _HAS_OTEL:
57
- log.debug("🔍 OpenTelemetry tracing not available (dependencies not installed)")
58
56
  return
59
57
 
60
- log.debug("🔍🚀 Setting up OpenTelemetry tracing")
61
-
62
58
  # Create resource with service information
63
59
  resource_attrs = {}
64
60
  if config.service_name:
@@ -77,7 +73,7 @@ def setup_opentelemetry_tracing(config: TelemetryConfig) -> None:
77
73
  endpoint = config.otlp_traces_endpoint or config.otlp_endpoint
78
74
  headers = config.get_otlp_headers_dict()
79
75
 
80
- log.debug(f"🔍📤 Configuring OTLP exporter: {endpoint}")
76
+ # Configuring OTLP exporter
81
77
 
82
78
  # Choose exporter based on protocol
83
79
  if config.otlp_protocol == "grpc":
@@ -95,12 +91,12 @@ def setup_opentelemetry_tracing(config: TelemetryConfig) -> None:
95
91
  processor = BatchSpanProcessor(exporter)
96
92
  tracer_provider.add_span_processor(processor)
97
93
 
98
- log.debug(f"✅ OTLP span exporter configured: {config.otlp_protocol}")
94
+ slog.debug(f"✅ OTLP span exporter configured: {config.otlp_protocol}")
99
95
 
100
96
  # Set the global tracer provider
101
97
  otel_trace.set_tracer_provider(tracer_provider)
102
98
 
103
- log.info("🔍✅ OpenTelemetry tracing setup complete")
99
+ slog.info("🔍✅ OpenTelemetry tracing setup complete")
104
100
 
105
101
 
106
102
  def get_otel_tracer(name: str) -> "otel_trace.Tracer | None":
@@ -130,6 +126,6 @@ def shutdown_opentelemetry() -> None:
130
126
  tracer_provider = otel_trace.get_tracer_provider()
131
127
  if hasattr(tracer_provider, "shutdown"):
132
128
  tracer_provider.shutdown()
133
- log.debug("🔍🛑 OpenTelemetry tracer provider shutdown")
129
+ slog.debug("🔍🛑 OpenTelemetry tracer provider shutdown")
134
130
  except Exception as e:
135
- log.warning(f"⚠️ Error shutting down OpenTelemetry: {e}")
131
+ slog.warning(f"⚠️ Error shutting down OpenTelemetry: {e}")
@@ -8,7 +8,7 @@ Provides OpenTelemetry integration when available, falls back to simple tracing.
8
8
 
9
9
  from dataclasses import dataclass, field
10
10
  import time
11
- from typing import Any, Optional
11
+ from typing import Any
12
12
  import uuid
13
13
 
14
14
  from provide.foundation.logger import get_logger
@@ -47,7 +47,7 @@ class Span:
47
47
  error: str | None = None
48
48
 
49
49
  # Internal OpenTelemetry span (when available)
50
- _otel_span: Optional["otel_trace.Span"] = field(
50
+ _otel_span: "otel_trace.Span | None" = field(
51
51
  default=None, init=False, repr=False
52
52
  )
53
53
  _active: bool = field(default=True, init=False, repr=False)