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
@@ -6,9 +6,9 @@ eliminating duplication between decorators and middleware.
6
6
  """
7
7
 
8
8
  import asyncio
9
+ from enum import Enum
9
10
  import random
10
11
  import time
11
- from enum import Enum
12
12
  from typing import Any, Callable, TypeVar
13
13
 
14
14
  from attrs import define, field, validators
@@ -22,7 +22,7 @@ T = TypeVar("T")
22
22
 
23
23
  class BackoffStrategy(str, Enum):
24
24
  """Backoff strategies for retry delays."""
25
-
25
+
26
26
  FIXED = "fixed" # Same delay every time
27
27
  LINEAR = "linear" # Linear increase (delay * attempt)
28
28
  EXPONENTIAL = "exponential" # Exponential increase (delay * 2^attempt)
@@ -33,10 +33,10 @@ class BackoffStrategy(str, Enum):
33
33
  class RetryPolicy:
34
34
  """
35
35
  Configuration for retry behavior.
36
-
36
+
37
37
  This policy can be used with both the @retry decorator and transport middleware,
38
38
  providing a unified configuration model for all retry scenarios.
39
-
39
+
40
40
  Attributes:
41
41
  max_attempts: Maximum number of retry attempts (must be >= 1)
42
42
  backoff: Backoff strategy to use for delays
@@ -46,48 +46,52 @@ class RetryPolicy:
46
46
  retryable_errors: Tuple of exception types to retry (None = all)
47
47
  retryable_status_codes: Set of HTTP status codes to retry (for middleware)
48
48
  """
49
-
49
+
50
50
  max_attempts: int = field(default=3, validator=validators.instance_of(int))
51
51
  backoff: BackoffStrategy = field(default=BackoffStrategy.EXPONENTIAL)
52
- base_delay: float = field(default=1.0, validator=validators.instance_of((int, float)))
53
- max_delay: float = field(default=60.0, validator=validators.instance_of((int, float)))
52
+ base_delay: float = field(
53
+ default=1.0, validator=validators.instance_of((int, float))
54
+ )
55
+ max_delay: float = field(
56
+ default=60.0, validator=validators.instance_of((int, float))
57
+ )
54
58
  jitter: bool = field(default=True)
55
59
  retryable_errors: tuple[type[Exception], ...] | None = field(default=None)
56
60
  retryable_status_codes: set[int] | None = field(default=None)
57
-
61
+
58
62
  @max_attempts.validator
59
- def _validate_max_attempts(self, attribute, value):
63
+ def _validate_max_attempts(self, attribute: object, value: int) -> None:
60
64
  """Validate max_attempts is at least 1."""
61
65
  if value < 1:
62
66
  raise ValueError("max_attempts must be at least 1")
63
-
67
+
64
68
  @base_delay.validator
65
- def _validate_base_delay(self, attribute, value):
69
+ def _validate_base_delay(self, attribute: object, value: float) -> None:
66
70
  """Validate base_delay is positive."""
67
71
  if value < 0:
68
72
  raise ValueError("base_delay must be positive")
69
-
73
+
70
74
  @max_delay.validator
71
- def _validate_max_delay(self, attribute, value):
75
+ def _validate_max_delay(self, attribute: object, value: float) -> None:
72
76
  """Validate max_delay is positive and >= base_delay."""
73
77
  if value < 0:
74
78
  raise ValueError("max_delay must be positive")
75
79
  if value < self.base_delay:
76
80
  raise ValueError("max_delay must be >= base_delay")
77
-
81
+
78
82
  def calculate_delay(self, attempt: int) -> float:
79
83
  """
80
84
  Calculate delay for a given attempt number.
81
-
85
+
82
86
  Args:
83
87
  attempt: Attempt number (1-based)
84
-
88
+
85
89
  Returns:
86
90
  Delay in seconds
87
91
  """
88
92
  if attempt <= 0:
89
93
  return 0
90
-
94
+
91
95
  if self.backoff == BackoffStrategy.FIXED:
92
96
  delay = self.base_delay
93
97
  elif self.backoff == BackoffStrategy.LINEAR:
@@ -102,61 +106,61 @@ class RetryPolicy:
102
106
  delay = self.base_delay * a
103
107
  else:
104
108
  delay = self.base_delay
105
-
109
+
106
110
  # Cap at max delay
107
111
  delay = min(delay, self.max_delay)
108
-
112
+
109
113
  # Add jitter if configured (±25% random variation)
110
114
  if self.jitter:
111
115
  jitter_factor = 0.75 + (random.random() * 0.5)
112
116
  delay *= jitter_factor
113
-
117
+
114
118
  return delay
115
-
119
+
116
120
  def should_retry(self, error: Exception, attempt: int) -> bool:
117
121
  """
118
122
  Determine if an error should be retried.
119
-
123
+
120
124
  Args:
121
125
  error: The exception that occurred
122
126
  attempt: Current attempt number (1-based)
123
-
127
+
124
128
  Returns:
125
129
  True if should retry, False otherwise
126
130
  """
127
131
  # Check attempt limit
128
132
  if attempt >= self.max_attempts:
129
133
  return False
130
-
134
+
131
135
  # Check error type if filter is configured
132
136
  if self.retryable_errors is not None:
133
137
  return isinstance(error, self.retryable_errors)
134
-
138
+
135
139
  # Default to retry for any error
136
140
  return True
137
-
141
+
138
142
  def should_retry_response(self, response: Any, attempt: int) -> bool:
139
143
  """
140
144
  Check if HTTP response should be retried.
141
-
145
+
142
146
  Args:
143
147
  response: Response object with status attribute
144
148
  attempt: Current attempt number (1-based)
145
-
149
+
146
150
  Returns:
147
151
  True if should retry, False otherwise
148
152
  """
149
153
  # Check attempt limit
150
154
  if attempt >= self.max_attempts:
151
155
  return False
152
-
156
+
153
157
  # Check status code if configured
154
158
  if self.retryable_status_codes is not None:
155
- return getattr(response, 'status', None) in self.retryable_status_codes
156
-
159
+ return getattr(response, "status", None) in self.retryable_status_codes
160
+
157
161
  # Default to no retry for responses
158
162
  return False
159
-
163
+
160
164
  def __str__(self) -> str:
161
165
  """Human-readable string representation."""
162
166
  return (
@@ -168,50 +172,50 @@ class RetryPolicy:
168
172
  class RetryExecutor:
169
173
  """
170
174
  Unified retry execution engine.
171
-
175
+
172
176
  This executor handles the actual retry loop logic for both sync and async
173
177
  functions, using a RetryPolicy for configuration. It's used internally by
174
178
  both the @retry decorator and RetryMiddleware.
175
179
  """
176
-
180
+
177
181
  def __init__(
178
182
  self,
179
183
  policy: RetryPolicy,
180
- on_retry: Callable[[int, Exception], None] | None = None
184
+ on_retry: Callable[[int, Exception], None] | None = None,
181
185
  ):
182
186
  """
183
187
  Initialize retry executor.
184
-
188
+
185
189
  Args:
186
190
  policy: Retry policy configuration
187
191
  on_retry: Optional callback for retry events (attempt, error)
188
192
  """
189
193
  self.policy = policy
190
194
  self.on_retry = on_retry
191
-
195
+
192
196
  def execute_sync(self, func: Callable[..., T], *args, **kwargs) -> T:
193
197
  """
194
198
  Execute synchronous function with retry logic.
195
-
199
+
196
200
  Args:
197
201
  func: Function to execute
198
202
  *args: Positional arguments for func
199
203
  **kwargs: Keyword arguments for func
200
-
204
+
201
205
  Returns:
202
206
  Result from successful execution
203
-
207
+
204
208
  Raises:
205
209
  Last exception if all retries are exhausted
206
210
  """
207
211
  last_exception = None
208
-
212
+
209
213
  for attempt in range(1, self.policy.max_attempts + 1):
210
214
  try:
211
215
  return func(*args, **kwargs)
212
216
  except Exception as e:
213
217
  last_exception = e
214
-
218
+
215
219
  # Don't retry on last attempt - log and raise
216
220
  if attempt >= self.policy.max_attempts:
217
221
  logger.error(
@@ -221,14 +225,14 @@ class RetryExecutor:
221
225
  error_type=type(e).__name__,
222
226
  )
223
227
  raise
224
-
228
+
225
229
  # Check if we should retry this error
226
230
  if not self.policy.should_retry(e, attempt):
227
231
  raise
228
-
232
+
229
233
  # Calculate delay
230
234
  delay = self.policy.calculate_delay(attempt)
231
-
235
+
232
236
  # Log retry attempt
233
237
  logger.info(
234
238
  f"Retry {attempt}/{self.policy.max_attempts} after {delay:.2f}s",
@@ -238,46 +242,45 @@ class RetryExecutor:
238
242
  error=str(e),
239
243
  error_type=type(e).__name__,
240
244
  )
241
-
245
+
242
246
  # Call retry callback if provided
243
247
  if self.on_retry:
244
248
  try:
245
249
  self.on_retry(attempt, e)
246
250
  except Exception as callback_error:
247
251
  logger.warning(
248
- "Retry callback failed",
249
- error=str(callback_error)
252
+ "Retry callback failed", error=str(callback_error)
250
253
  )
251
-
254
+
252
255
  # Wait before retry
253
256
  time.sleep(delay)
254
-
257
+
255
258
  # Should never reach here, but for safety
256
259
  raise last_exception
257
-
260
+
258
261
  async def execute_async(self, func: Callable[..., T], *args, **kwargs) -> T:
259
262
  """
260
263
  Execute asynchronous function with retry logic.
261
-
264
+
262
265
  Args:
263
266
  func: Async function to execute
264
267
  *args: Positional arguments for func
265
268
  **kwargs: Keyword arguments for func
266
-
269
+
267
270
  Returns:
268
271
  Result from successful execution
269
-
272
+
270
273
  Raises:
271
274
  Last exception if all retries are exhausted
272
275
  """
273
276
  last_exception = None
274
-
277
+
275
278
  for attempt in range(1, self.policy.max_attempts + 1):
276
279
  try:
277
280
  return await func(*args, **kwargs)
278
281
  except Exception as e:
279
282
  last_exception = e
280
-
283
+
281
284
  # Don't retry on last attempt - log and raise
282
285
  if attempt >= self.policy.max_attempts:
283
286
  logger.error(
@@ -287,14 +290,14 @@ class RetryExecutor:
287
290
  error_type=type(e).__name__,
288
291
  )
289
292
  raise
290
-
293
+
291
294
  # Check if we should retry this error
292
295
  if not self.policy.should_retry(e, attempt):
293
296
  raise
294
-
297
+
295
298
  # Calculate delay
296
299
  delay = self.policy.calculate_delay(attempt)
297
-
300
+
298
301
  # Log retry attempt
299
302
  logger.info(
300
303
  f"Retry {attempt}/{self.policy.max_attempts} after {delay:.2f}s",
@@ -304,7 +307,7 @@ class RetryExecutor:
304
307
  error=str(e),
305
308
  error_type=type(e).__name__,
306
309
  )
307
-
310
+
308
311
  # Call retry callback if provided
309
312
  if self.on_retry:
310
313
  try:
@@ -314,12 +317,11 @@ class RetryExecutor:
314
317
  self.on_retry(attempt, e)
315
318
  except Exception as callback_error:
316
319
  logger.warning(
317
- "Retry callback failed",
318
- error=str(callback_error)
320
+ "Retry callback failed", error=str(callback_error)
319
321
  )
320
-
322
+
321
323
  # Wait before retry
322
324
  await asyncio.sleep(delay)
323
-
325
+
324
326
  # Should never reach here, but for safety
325
- raise last_exception
327
+ raise last_exception
@@ -0,0 +1,16 @@
1
+ """
2
+ Serialization utilities for Foundation.
3
+
4
+ Provides consistent serialization handling with validation,
5
+ testing support, and integration with Foundation's configuration system.
6
+ """
7
+
8
+ from provide.foundation.serialization.core import (
9
+ provide_dumps,
10
+ provide_loads,
11
+ )
12
+
13
+ __all__ = [
14
+ "provide_dumps",
15
+ "provide_loads",
16
+ ]
@@ -0,0 +1,70 @@
1
+ """Core serialization utilities for Foundation."""
2
+
3
+ import json
4
+ from typing import Any
5
+
6
+ from provide.foundation.errors import ValidationError
7
+
8
+
9
+ def provide_dumps(
10
+ obj: Any,
11
+ *,
12
+ ensure_ascii: bool = False,
13
+ indent: int | None = None,
14
+ sort_keys: bool = False,
15
+ ) -> str:
16
+ """
17
+ Serialize object to JSON string with Foundation tracking.
18
+
19
+ Args:
20
+ obj: Object to serialize
21
+ ensure_ascii: If True, non-ASCII characters are escaped
22
+ indent: Number of spaces for indentation (None for compact)
23
+ sort_keys: Whether to sort dictionary keys
24
+
25
+ Returns:
26
+ JSON string representation
27
+
28
+ Raises:
29
+ ValidationError: If object cannot be serialized
30
+
31
+ Example:
32
+ >>> provide_dumps({"key": "value"})
33
+ '{"key": "value"}'
34
+ >>> provide_dumps({"b": 1, "a": 2}, sort_keys=True, indent=2)
35
+ '{\\n "a": 2,\\n "b": 1\\n}'
36
+ """
37
+ try:
38
+ return json.dumps(
39
+ obj, ensure_ascii=ensure_ascii, indent=indent, sort_keys=sort_keys
40
+ )
41
+ except (TypeError, ValueError) as e:
42
+ raise ValidationError(f"Cannot serialize object to JSON: {e}") from e
43
+
44
+
45
+ def provide_loads(s: str) -> Any:
46
+ """
47
+ Deserialize JSON string to Python object with Foundation tracking.
48
+
49
+ Args:
50
+ s: JSON string to deserialize
51
+
52
+ Returns:
53
+ Deserialized Python object
54
+
55
+ Raises:
56
+ ValidationError: If string is not valid JSON
57
+
58
+ Example:
59
+ >>> provide_loads('{"key": "value"}')
60
+ {'key': 'value'}
61
+ >>> provide_loads('[1, 2, 3]')
62
+ [1, 2, 3]
63
+ """
64
+ if not isinstance(s, str):
65
+ raise ValidationError("Input must be a string")
66
+
67
+ try:
68
+ return json.loads(s)
69
+ except json.JSONDecodeError as e:
70
+ raise ValidationError(f"Invalid JSON string: {e}") from e
@@ -7,9 +7,9 @@ including color support and testing mode detection.
7
7
 
8
8
  from attrs import define
9
9
 
10
- from provide.foundation.config.env import RuntimeConfig
11
10
  from provide.foundation.config.base import field
12
11
  from provide.foundation.config.converters import parse_bool_extended
12
+ from provide.foundation.config.env import RuntimeConfig
13
13
 
14
14
 
15
15
  @define(slots=True, repr=False)
@@ -22,35 +22,34 @@ class StreamConfig(RuntimeConfig):
22
22
  converter=parse_bool_extended,
23
23
  description="Disable color output in console",
24
24
  )
25
-
25
+
26
26
  force_color: bool = field(
27
27
  default=False,
28
28
  env_var="FORCE_COLOR",
29
29
  converter=parse_bool_extended,
30
30
  description="Force color output even when not in TTY",
31
31
  )
32
-
32
+
33
33
  click_testing: bool = field(
34
34
  default=False,
35
35
  env_var="CLICK_TESTING",
36
36
  converter=parse_bool_extended,
37
37
  description="Indicates if running inside Click testing framework",
38
38
  )
39
-
40
39
 
41
40
  def supports_color(self) -> bool:
42
41
  """
43
42
  Determine if the console supports color output.
44
-
43
+
45
44
  Returns:
46
45
  True if color is supported, False otherwise
47
46
  """
48
47
  if self.no_color:
49
48
  return False
50
-
49
+
51
50
  if self.force_color:
52
51
  return True
53
-
52
+
54
53
  # Additional logic for TTY detection would go here
55
54
  # For now, just return based on the flags
56
55
  return not self.no_color
@@ -63,7 +62,7 @@ _stream_config: StreamConfig | None = None
63
62
  def get_stream_config() -> StreamConfig:
64
63
  """
65
64
  Get the global stream configuration instance.
66
-
65
+
67
66
  Returns:
68
67
  StreamConfig instance loaded from environment
69
68
  """
@@ -76,4 +75,4 @@ def get_stream_config() -> StreamConfig:
76
75
  def reset_stream_config() -> None:
77
76
  """Reset the global stream configuration (mainly for testing)."""
78
77
  global _stream_config
79
- _stream_config = None
78
+ _stream_config = None
@@ -27,13 +27,13 @@ def is_tty() -> bool:
27
27
  def supports_color() -> bool:
28
28
  """Check if the current stream supports color output."""
29
29
  config = get_stream_config()
30
-
30
+
31
31
  if config.no_color:
32
32
  return False
33
-
33
+
34
34
  if config.force_color:
35
35
  return True
36
-
36
+
37
37
  # Check if we're in a TTY
38
38
  return is_tty()
39
39
 
@@ -20,9 +20,9 @@ _STREAM_LOCK = threading.Lock()
20
20
  def _is_in_click_testing() -> bool:
21
21
  """Check if we're running inside Click's testing framework."""
22
22
  import inspect
23
-
23
+
24
24
  config = get_stream_config()
25
-
25
+
26
26
  # Check environment variables for Click testing
27
27
  if config.click_testing:
28
28
  return True
@@ -21,7 +21,7 @@ from provide.foundation.utils.streams import get_safe_stderr
21
21
  def _safe_error_output(message: str) -> None:
22
22
  """
23
23
  Output error message to stderr using basic print to avoid circular dependencies.
24
-
24
+
25
25
  This function intentionally uses print() instead of Foundation's perr() to prevent
26
26
  circular import issues during stream initialization and teardown phases.
27
27
  """
@@ -98,12 +98,22 @@ def __getattr__(name: str) -> Any:
98
98
  import provide.foundation.testing.fixtures as fixtures_module
99
99
 
100
100
  return getattr(fixtures_module, name)
101
-
101
+
102
102
  # Import submodules directly
103
- elif name in ["archive", "common", "file", "process", "transport", "mocking", "time", "threading"]:
103
+ elif name in [
104
+ "archive",
105
+ "common",
106
+ "file",
107
+ "process",
108
+ "transport",
109
+ "mocking",
110
+ "time",
111
+ "threading",
112
+ ]:
104
113
  import importlib
114
+
105
115
  return importlib.import_module(f"provide.foundation.testing.{name}")
106
-
116
+
107
117
  # File testing utilities (backward compatibility)
108
118
  elif name in [
109
119
  "temp_directory",
@@ -115,8 +125,9 @@ def __getattr__(name: str) -> Any:
115
125
  "readonly_file",
116
126
  ]:
117
127
  import provide.foundation.testing.file.fixtures as file_module
128
+
118
129
  return getattr(file_module, name)
119
-
130
+
120
131
  # Process/async testing utilities (backward compatibility)
121
132
  elif name in [
122
133
  "clean_event_loop",
@@ -131,8 +142,9 @@ def __getattr__(name: str) -> Any:
131
142
  "mock_async_sleep",
132
143
  ]:
133
144
  import provide.foundation.testing.process.fixtures as process_module
145
+
134
146
  return getattr(process_module, name)
135
-
147
+
136
148
  # Common mock utilities (backward compatibility)
137
149
  elif name in [
138
150
  "mock_http_config",
@@ -147,8 +159,9 @@ def __getattr__(name: str) -> Any:
147
159
  "mock_subprocess",
148
160
  ]:
149
161
  import provide.foundation.testing.common.fixtures as common_module
162
+
150
163
  return getattr(common_module, name)
151
-
164
+
152
165
  # Transport/network testing utilities (backward compatibility)
153
166
  elif name in [
154
167
  "free_port",
@@ -162,8 +175,9 @@ def __getattr__(name: str) -> Any:
162
175
  "mock_http_headers",
163
176
  ]:
164
177
  import provide.foundation.testing.transport.fixtures as transport_module
178
+
165
179
  return getattr(transport_module, name)
166
-
180
+
167
181
  # Archive testing utilities
168
182
  elif name in [
169
183
  "archive_test_content",
@@ -174,6 +188,7 @@ def __getattr__(name: str) -> Any:
174
188
  "archive_stress_test_files",
175
189
  ]:
176
190
  import provide.foundation.testing.archive.fixtures as archive_module
191
+
177
192
  return getattr(archive_module, name)
178
193
 
179
194
  # Crypto fixtures (many fixtures)
@@ -6,19 +6,19 @@ across any project that depends on provide.foundation.
6
6
  """
7
7
 
8
8
  from provide.foundation.testing.archive.fixtures import (
9
+ archive_stress_test_files,
9
10
  archive_test_content,
10
- large_file_for_compression,
11
- multi_format_archives,
12
11
  archive_with_permissions,
13
12
  corrupted_archives,
14
- archive_stress_test_files,
13
+ large_file_for_compression,
14
+ multi_format_archives,
15
15
  )
16
16
 
17
17
  __all__ = [
18
+ "archive_stress_test_files",
18
19
  "archive_test_content",
19
- "large_file_for_compression",
20
- "multi_format_archives",
21
20
  "archive_with_permissions",
22
21
  "corrupted_archives",
23
- "archive_stress_test_files",
24
- ]
22
+ "large_file_for_compression",
23
+ "multi_format_archives",
24
+ ]