provide-foundation 0.0.0.dev1__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 +36 -10
- 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 +93 -96
- 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 +15 -9
- provide/foundation/cli/commands/logs/__init__.py +3 -3
- provide/foundation/cli/commands/logs/generate.py +2 -2
- provide/foundation/cli/commands/logs/query.py +4 -4
- provide/foundation/cli/commands/logs/send.py +3 -3
- provide/foundation/cli/commands/logs/tail.py +3 -3
- provide/foundation/cli/decorators.py +11 -11
- provide/foundation/cli/main.py +1 -1
- provide/foundation/cli/testing.py +2 -40
- provide/foundation/cli/utils.py +21 -18
- provide/foundation/config/__init__.py +35 -2
- provide/foundation/config/base.py +2 -2
- provide/foundation/config/converters.py +477 -0
- provide/foundation/config/defaults.py +67 -0
- provide/foundation/config/env.py +6 -20
- provide/foundation/config/loader.py +10 -4
- provide/foundation/config/sync.py +8 -6
- provide/foundation/config/types.py +5 -5
- provide/foundation/config/validators.py +4 -4
- provide/foundation/console/input.py +5 -5
- provide/foundation/console/output.py +36 -14
- provide/foundation/context/__init__.py +8 -4
- provide/foundation/context/core.py +88 -110
- 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 -3
- provide/foundation/errors/decorators.py +0 -234
- provide/foundation/errors/types.py +0 -98
- 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 +14 -23
- provide/foundation/file/lock.py +4 -3
- provide/foundation/hub/components.py +75 -389
- provide/foundation/hub/config.py +157 -0
- provide/foundation/hub/discovery.py +63 -0
- provide/foundation/hub/handlers.py +89 -0
- provide/foundation/hub/lifecycle.py +195 -0
- provide/foundation/hub/manager.py +7 -4
- provide/foundation/hub/processors.py +49 -0
- provide/foundation/integrations/__init__.py +11 -0
- provide/foundation/{observability → integrations}/openobserve/__init__.py +10 -7
- provide/foundation/{observability → integrations}/openobserve/auth.py +1 -1
- provide/foundation/{observability → integrations}/openobserve/client.py +14 -14
- provide/foundation/{observability → integrations}/openobserve/commands.py +12 -12
- provide/foundation/integrations/openobserve/config.py +37 -0
- provide/foundation/{observability → integrations}/openobserve/formatters.py +1 -1
- provide/foundation/{observability → integrations}/openobserve/otlp.py +2 -2
- provide/foundation/{observability → integrations}/openobserve/search.py +2 -3
- provide/foundation/{observability → integrations}/openobserve/streaming.py +5 -5
- provide/foundation/logger/__init__.py +0 -1
- provide/foundation/logger/config/base.py +1 -1
- provide/foundation/logger/config/logging.py +69 -299
- provide/foundation/logger/config/telemetry.py +39 -121
- 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 +39 -25
- 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 +3 -3
- provide/foundation/process/__init__.py +9 -0
- provide/foundation/process/exit.py +48 -0
- provide/foundation/process/lifecycle.py +69 -46
- provide/foundation/resilience/__init__.py +36 -0
- provide/foundation/resilience/circuit.py +166 -0
- provide/foundation/resilience/decorators.py +236 -0
- provide/foundation/resilience/fallback.py +208 -0
- provide/foundation/resilience/retry.py +327 -0
- provide/foundation/serialization/__init__.py +16 -0
- provide/foundation/serialization/core.py +70 -0
- provide/foundation/streams/config.py +78 -0
- provide/foundation/streams/console.py +4 -5
- provide/foundation/streams/core.py +5 -2
- provide/foundation/streams/file.py +12 -2
- provide/foundation/testing/__init__.py +29 -9
- provide/foundation/testing/archive/__init__.py +7 -7
- provide/foundation/testing/archive/fixtures.py +58 -54
- provide/foundation/testing/cli.py +30 -20
- provide/foundation/testing/common/__init__.py +13 -15
- provide/foundation/testing/common/fixtures.py +27 -57
- provide/foundation/testing/file/__init__.py +15 -15
- provide/foundation/testing/file/content_fixtures.py +289 -0
- provide/foundation/testing/file/directory_fixtures.py +107 -0
- provide/foundation/testing/file/fixtures.py +42 -516
- provide/foundation/testing/file/special_fixtures.py +145 -0
- provide/foundation/testing/logger.py +89 -8
- 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 +414 -0
- provide/foundation/testing/process/fixtures.py +48 -571
- provide/foundation/testing/process/subprocess_fixtures.py +210 -0
- provide/foundation/testing/threading/__init__.py +17 -17
- provide/foundation/testing/threading/basic_fixtures.py +105 -0
- provide/foundation/testing/threading/data_fixtures.py +101 -0
- provide/foundation/testing/threading/execution_fixtures.py +278 -0
- provide/foundation/testing/threading/fixtures.py +32 -502
- provide/foundation/testing/threading/sync_fixtures.py +100 -0
- provide/foundation/testing/time/__init__.py +11 -11
- provide/foundation/testing/time/fixtures.py +95 -83
- 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 +69 -74
- provide/foundation/tools/downloader.py +68 -62
- 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 +2 -14
- 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 +36 -107
- provide/foundation/transport/errors.py +13 -27
- provide/foundation/transport/http.py +69 -55
- provide/foundation/transport/middleware.py +113 -114
- provide/foundation/transport/registry.py +29 -27
- provide/foundation/transport/types.py +6 -6
- provide/foundation/utils/deps.py +17 -14
- provide/foundation/utils/parsing.py +49 -4
- {provide_foundation-0.0.0.dev1.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.dev1.dist-info/RECORD +0 -200
- /provide/foundation/{observability → integrations}/openobserve/exceptions.py +0 -0
- /provide/foundation/{observability → integrations}/openobserve/models.py +0 -0
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/WHEEL +0 -0
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/entry_points.txt +0 -0
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/licenses/LICENSE +0 -0
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,278 @@
|
|
1
|
+
"""
|
2
|
+
Thread execution and testing helper fixtures.
|
3
|
+
|
4
|
+
Advanced fixtures for concurrent execution, synchronization testing, deadlock detection,
|
5
|
+
and exception handling in threaded code.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from concurrent.futures import ThreadPoolExecutor
|
9
|
+
import threading
|
10
|
+
import time
|
11
|
+
from typing import Any, Callable, Optional
|
12
|
+
|
13
|
+
import pytest
|
14
|
+
|
15
|
+
|
16
|
+
@pytest.fixture
|
17
|
+
def concurrent_executor():
|
18
|
+
"""
|
19
|
+
Helper for executing functions concurrently in tests.
|
20
|
+
|
21
|
+
Returns:
|
22
|
+
Concurrent execution helper.
|
23
|
+
"""
|
24
|
+
|
25
|
+
class ConcurrentExecutor:
|
26
|
+
def __init__(self):
|
27
|
+
self.results = []
|
28
|
+
self.exceptions = []
|
29
|
+
|
30
|
+
def run_concurrent(
|
31
|
+
self, func: Callable, args_list: list[tuple], max_workers: int = 4
|
32
|
+
) -> list[Any]:
|
33
|
+
"""
|
34
|
+
Run function concurrently with different arguments.
|
35
|
+
|
36
|
+
Args:
|
37
|
+
func: Function to execute
|
38
|
+
args_list: List of argument tuples
|
39
|
+
max_workers: Maximum concurrent workers
|
40
|
+
|
41
|
+
Returns:
|
42
|
+
List of results in order
|
43
|
+
"""
|
44
|
+
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
45
|
+
futures = []
|
46
|
+
for args in args_list:
|
47
|
+
if isinstance(args, tuple):
|
48
|
+
future = executor.submit(func, *args)
|
49
|
+
else:
|
50
|
+
future = executor.submit(func, args)
|
51
|
+
futures.append(future)
|
52
|
+
|
53
|
+
results = []
|
54
|
+
for future in futures:
|
55
|
+
try:
|
56
|
+
result = future.result(timeout=10)
|
57
|
+
results.append(result)
|
58
|
+
self.results.append(result)
|
59
|
+
except Exception as e:
|
60
|
+
self.exceptions.append(e)
|
61
|
+
results.append(None)
|
62
|
+
|
63
|
+
return results
|
64
|
+
|
65
|
+
def run_parallel(self, funcs: list[Callable], timeout: float = 10) -> list[Any]:
|
66
|
+
"""
|
67
|
+
Run different functions in parallel.
|
68
|
+
|
69
|
+
Args:
|
70
|
+
funcs: List of functions to execute
|
71
|
+
timeout: Timeout for each function
|
72
|
+
|
73
|
+
Returns:
|
74
|
+
List of results
|
75
|
+
"""
|
76
|
+
with ThreadPoolExecutor(max_workers=len(funcs)) as executor:
|
77
|
+
futures = [executor.submit(func) for func in funcs]
|
78
|
+
results = []
|
79
|
+
|
80
|
+
for future in futures:
|
81
|
+
try:
|
82
|
+
result = future.result(timeout=timeout)
|
83
|
+
results.append(result)
|
84
|
+
except Exception as e:
|
85
|
+
self.exceptions.append(e)
|
86
|
+
results.append(None)
|
87
|
+
|
88
|
+
return results
|
89
|
+
|
90
|
+
return ConcurrentExecutor()
|
91
|
+
|
92
|
+
|
93
|
+
@pytest.fixture
|
94
|
+
def thread_synchronizer():
|
95
|
+
"""
|
96
|
+
Helper for synchronizing test threads.
|
97
|
+
|
98
|
+
Returns:
|
99
|
+
Thread synchronization helper.
|
100
|
+
"""
|
101
|
+
|
102
|
+
class ThreadSynchronizer:
|
103
|
+
def __init__(self):
|
104
|
+
self.checkpoints = {}
|
105
|
+
|
106
|
+
def checkpoint(self, name: str, thread_id: Optional[int] = None):
|
107
|
+
"""
|
108
|
+
Record that a thread reached a checkpoint.
|
109
|
+
|
110
|
+
Args:
|
111
|
+
name: Checkpoint name
|
112
|
+
thread_id: Optional thread ID (uses current if None)
|
113
|
+
"""
|
114
|
+
thread_id = thread_id or threading.get_ident()
|
115
|
+
if name not in self.checkpoints:
|
116
|
+
self.checkpoints[name] = []
|
117
|
+
self.checkpoints[name].append((thread_id, time.time()))
|
118
|
+
|
119
|
+
def wait_for_checkpoint(
|
120
|
+
self, name: str, count: int, timeout: float = 5.0
|
121
|
+
) -> bool:
|
122
|
+
"""
|
123
|
+
Wait for N threads to reach a checkpoint.
|
124
|
+
|
125
|
+
Args:
|
126
|
+
name: Checkpoint name
|
127
|
+
count: Number of threads to wait for
|
128
|
+
timeout: Maximum wait time
|
129
|
+
|
130
|
+
Returns:
|
131
|
+
True if checkpoint reached, False if timeout
|
132
|
+
"""
|
133
|
+
start = time.time()
|
134
|
+
while time.time() - start < timeout:
|
135
|
+
if name in self.checkpoints and len(self.checkpoints[name]) >= count:
|
136
|
+
return True
|
137
|
+
time.sleep(0.01)
|
138
|
+
return False
|
139
|
+
|
140
|
+
def get_order(self, checkpoint: str) -> list[int]:
|
141
|
+
"""
|
142
|
+
Get order in which threads reached checkpoint.
|
143
|
+
|
144
|
+
Args:
|
145
|
+
checkpoint: Checkpoint name
|
146
|
+
|
147
|
+
Returns:
|
148
|
+
List of thread IDs in order
|
149
|
+
"""
|
150
|
+
if checkpoint not in self.checkpoints:
|
151
|
+
return []
|
152
|
+
return [
|
153
|
+
tid
|
154
|
+
for tid, _ in sorted(self.checkpoints[checkpoint], key=lambda x: x[1])
|
155
|
+
]
|
156
|
+
|
157
|
+
def clear(self):
|
158
|
+
"""Clear all checkpoints."""
|
159
|
+
self.checkpoints.clear()
|
160
|
+
|
161
|
+
return ThreadSynchronizer()
|
162
|
+
|
163
|
+
|
164
|
+
@pytest.fixture
|
165
|
+
def deadlock_detector():
|
166
|
+
"""
|
167
|
+
Helper for detecting potential deadlocks in tests.
|
168
|
+
|
169
|
+
Returns:
|
170
|
+
Deadlock detection helper.
|
171
|
+
"""
|
172
|
+
|
173
|
+
class DeadlockDetector:
|
174
|
+
def __init__(self):
|
175
|
+
self.locks_held = {} # thread_id -> set of locks
|
176
|
+
self.lock = threading.Lock()
|
177
|
+
|
178
|
+
def acquire(self, lock_name: str, thread_id: Optional[int] = None):
|
179
|
+
"""Record lock acquisition."""
|
180
|
+
thread_id = thread_id or threading.get_ident()
|
181
|
+
with self.lock:
|
182
|
+
if thread_id not in self.locks_held:
|
183
|
+
self.locks_held[thread_id] = set()
|
184
|
+
self.locks_held[thread_id].add(lock_name)
|
185
|
+
|
186
|
+
def release(self, lock_name: str, thread_id: Optional[int] = None):
|
187
|
+
"""Record lock release."""
|
188
|
+
thread_id = thread_id or threading.get_ident()
|
189
|
+
with self.lock:
|
190
|
+
if thread_id in self.locks_held:
|
191
|
+
self.locks_held[thread_id].discard(lock_name)
|
192
|
+
|
193
|
+
def check_circular_wait(self) -> bool:
|
194
|
+
"""
|
195
|
+
Check for potential circular wait conditions.
|
196
|
+
|
197
|
+
Returns:
|
198
|
+
True if potential deadlock detected
|
199
|
+
"""
|
200
|
+
# Simplified check - in practice would need wait-for graph
|
201
|
+
with self.lock:
|
202
|
+
# Check if multiple threads hold multiple locks
|
203
|
+
multi_lock_threads = [
|
204
|
+
tid for tid, locks in self.locks_held.items() if len(locks) > 1
|
205
|
+
]
|
206
|
+
return len(multi_lock_threads) > 1
|
207
|
+
|
208
|
+
def get_held_locks(self) -> dict[int, set[str]]:
|
209
|
+
"""Get current lock holdings."""
|
210
|
+
with self.lock:
|
211
|
+
return self.locks_held.copy()
|
212
|
+
|
213
|
+
return DeadlockDetector()
|
214
|
+
|
215
|
+
|
216
|
+
@pytest.fixture
|
217
|
+
def thread_exception_handler():
|
218
|
+
"""
|
219
|
+
Capture exceptions from threads for testing.
|
220
|
+
|
221
|
+
Returns:
|
222
|
+
Exception handler for threads.
|
223
|
+
"""
|
224
|
+
|
225
|
+
class ThreadExceptionHandler:
|
226
|
+
def __init__(self):
|
227
|
+
self.exceptions = []
|
228
|
+
self.lock = threading.Lock()
|
229
|
+
|
230
|
+
def handle(self, func: Callable) -> Callable:
|
231
|
+
"""
|
232
|
+
Wrap function to capture exceptions.
|
233
|
+
|
234
|
+
Args:
|
235
|
+
func: Function to wrap
|
236
|
+
|
237
|
+
Returns:
|
238
|
+
Wrapped function
|
239
|
+
"""
|
240
|
+
|
241
|
+
def wrapper(*args, **kwargs):
|
242
|
+
try:
|
243
|
+
return func(*args, **kwargs)
|
244
|
+
except Exception as e:
|
245
|
+
with self.lock:
|
246
|
+
self.exceptions.append(
|
247
|
+
{
|
248
|
+
"thread": threading.current_thread().name,
|
249
|
+
"exception": e,
|
250
|
+
"time": time.time(),
|
251
|
+
}
|
252
|
+
)
|
253
|
+
raise
|
254
|
+
|
255
|
+
return wrapper
|
256
|
+
|
257
|
+
def get_exceptions(self) -> list[dict]:
|
258
|
+
"""Get all captured exceptions."""
|
259
|
+
with self.lock:
|
260
|
+
return self.exceptions.copy()
|
261
|
+
|
262
|
+
def assert_no_exceptions(self):
|
263
|
+
"""Assert no exceptions were raised."""
|
264
|
+
with self.lock:
|
265
|
+
if self.exceptions:
|
266
|
+
raise AssertionError(
|
267
|
+
f"Thread exceptions occurred: {self.exceptions}"
|
268
|
+
)
|
269
|
+
|
270
|
+
return ThreadExceptionHandler()
|
271
|
+
|
272
|
+
|
273
|
+
__all__ = [
|
274
|
+
"concurrent_executor",
|
275
|
+
"deadlock_detector",
|
276
|
+
"thread_exception_handler",
|
277
|
+
"thread_synchronizer",
|
278
|
+
]
|