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,210 @@
|
|
1
|
+
"""
|
2
|
+
Subprocess-specific test fixtures for process testing.
|
3
|
+
|
4
|
+
Provides fixtures for mocking and testing subprocess operations,
|
5
|
+
stream handling, and process communication.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from unittest.mock import AsyncMock, Mock
|
9
|
+
|
10
|
+
import pytest
|
11
|
+
|
12
|
+
|
13
|
+
@pytest.fixture
|
14
|
+
def mock_async_process() -> AsyncMock:
|
15
|
+
"""
|
16
|
+
Mock async subprocess for testing.
|
17
|
+
|
18
|
+
Returns:
|
19
|
+
AsyncMock configured as a subprocess with common attributes.
|
20
|
+
"""
|
21
|
+
mock_process = AsyncMock()
|
22
|
+
mock_process.communicate = AsyncMock(return_value=(b"output", b""))
|
23
|
+
mock_process.returncode = 0
|
24
|
+
mock_process.pid = 12345
|
25
|
+
mock_process.stdin = AsyncMock()
|
26
|
+
mock_process.stdout = AsyncMock()
|
27
|
+
mock_process.stderr = AsyncMock()
|
28
|
+
mock_process.wait = AsyncMock(return_value=0)
|
29
|
+
mock_process.kill = Mock()
|
30
|
+
mock_process.terminate = Mock()
|
31
|
+
|
32
|
+
return mock_process
|
33
|
+
|
34
|
+
|
35
|
+
@pytest.fixture
|
36
|
+
async def async_stream_reader() -> AsyncMock:
|
37
|
+
"""
|
38
|
+
Mock async stream reader for subprocess stdout/stderr.
|
39
|
+
|
40
|
+
Returns:
|
41
|
+
AsyncMock configured as a stream reader.
|
42
|
+
"""
|
43
|
+
reader = AsyncMock()
|
44
|
+
|
45
|
+
# Simulate reading lines
|
46
|
+
async def readline_side_effect():
|
47
|
+
for line in [b"line1\n", b"line2\n", b""]:
|
48
|
+
yield line
|
49
|
+
|
50
|
+
reader.readline = AsyncMock(side_effect=readline_side_effect().__anext__)
|
51
|
+
reader.read = AsyncMock(return_value=b"full content")
|
52
|
+
reader.at_eof = Mock(side_effect=[False, False, True])
|
53
|
+
|
54
|
+
return reader
|
55
|
+
|
56
|
+
|
57
|
+
@pytest.fixture
|
58
|
+
def async_subprocess():
|
59
|
+
"""
|
60
|
+
Create mock async subprocess for testing.
|
61
|
+
|
62
|
+
Returns:
|
63
|
+
Function that creates mock subprocess with configurable behavior.
|
64
|
+
"""
|
65
|
+
|
66
|
+
def _create_subprocess(
|
67
|
+
returncode: int = 0, stdout: bytes = b"", stderr: bytes = b"", pid: int = 12345
|
68
|
+
) -> AsyncMock:
|
69
|
+
"""
|
70
|
+
Create a mock async subprocess.
|
71
|
+
|
72
|
+
Args:
|
73
|
+
returncode: Process return code
|
74
|
+
stdout: Process stdout output
|
75
|
+
stderr: Process stderr output
|
76
|
+
pid: Process ID
|
77
|
+
|
78
|
+
Returns:
|
79
|
+
AsyncMock configured as subprocess
|
80
|
+
"""
|
81
|
+
process = AsyncMock()
|
82
|
+
process.returncode = returncode
|
83
|
+
process.pid = pid
|
84
|
+
process.communicate = AsyncMock(return_value=(stdout, stderr))
|
85
|
+
process.wait = AsyncMock(return_value=returncode)
|
86
|
+
process.kill = Mock()
|
87
|
+
process.terminate = Mock()
|
88
|
+
process.send_signal = Mock()
|
89
|
+
|
90
|
+
# Add stdout/stderr as async stream readers
|
91
|
+
process.stdout = AsyncMock()
|
92
|
+
process.stdout.read = AsyncMock(return_value=stdout)
|
93
|
+
process.stdout.readline = AsyncMock(side_effect=[stdout, b""])
|
94
|
+
process.stdout.at_eof = Mock(side_effect=[False, True])
|
95
|
+
|
96
|
+
process.stderr = AsyncMock()
|
97
|
+
process.stderr.read = AsyncMock(return_value=stderr)
|
98
|
+
process.stderr.readline = AsyncMock(side_effect=[stderr, b""])
|
99
|
+
process.stderr.at_eof = Mock(side_effect=[False, True])
|
100
|
+
|
101
|
+
process.stdin = AsyncMock()
|
102
|
+
process.stdin.write = AsyncMock()
|
103
|
+
process.stdin.drain = AsyncMock()
|
104
|
+
process.stdin.close = Mock()
|
105
|
+
|
106
|
+
return process
|
107
|
+
|
108
|
+
return _create_subprocess
|
109
|
+
|
110
|
+
|
111
|
+
@pytest.fixture
|
112
|
+
def async_mock_server():
|
113
|
+
"""
|
114
|
+
Create a mock async server for testing.
|
115
|
+
|
116
|
+
Returns:
|
117
|
+
Mock server with async methods.
|
118
|
+
"""
|
119
|
+
|
120
|
+
class AsyncMockServer:
|
121
|
+
def __init__(self):
|
122
|
+
self.started = False
|
123
|
+
self.connections = []
|
124
|
+
self.requests = []
|
125
|
+
|
126
|
+
async def start(self, host: str = "localhost", port: int = 8080):
|
127
|
+
"""Start the mock server."""
|
128
|
+
self.started = True
|
129
|
+
self.host = host
|
130
|
+
self.port = port
|
131
|
+
|
132
|
+
async def stop(self):
|
133
|
+
"""Stop the mock server."""
|
134
|
+
self.started = False
|
135
|
+
for conn in self.connections:
|
136
|
+
await conn.close()
|
137
|
+
|
138
|
+
async def handle_connection(self, reader, writer):
|
139
|
+
"""Mock connection handler."""
|
140
|
+
conn = {"reader": reader, "writer": writer}
|
141
|
+
self.connections.append(conn)
|
142
|
+
|
143
|
+
# Mock reading request
|
144
|
+
data = await reader.read(1024)
|
145
|
+
self.requests.append(data)
|
146
|
+
|
147
|
+
# Mock sending response
|
148
|
+
writer.write(b"HTTP/1.1 200 OK\r\n\r\nOK")
|
149
|
+
await writer.drain()
|
150
|
+
|
151
|
+
writer.close()
|
152
|
+
await writer.wait_closed()
|
153
|
+
|
154
|
+
def get_url(self) -> str:
|
155
|
+
"""Get server URL."""
|
156
|
+
return f"http://{self.host}:{self.port}"
|
157
|
+
|
158
|
+
return AsyncMockServer()
|
159
|
+
|
160
|
+
|
161
|
+
@pytest.fixture
|
162
|
+
def async_test_client():
|
163
|
+
"""
|
164
|
+
Create an async HTTP test client.
|
165
|
+
|
166
|
+
Returns:
|
167
|
+
Mock async HTTP client for testing.
|
168
|
+
"""
|
169
|
+
|
170
|
+
class AsyncTestClient:
|
171
|
+
def __init__(self):
|
172
|
+
self.responses = {}
|
173
|
+
self.requests = []
|
174
|
+
|
175
|
+
def set_response(self, url: str, response: dict):
|
176
|
+
"""Set a mock response for a URL."""
|
177
|
+
self.responses[url] = response
|
178
|
+
|
179
|
+
async def get(self, url: str, **kwargs) -> dict:
|
180
|
+
"""Mock GET request."""
|
181
|
+
self.requests.append({"method": "GET", "url": url, "kwargs": kwargs})
|
182
|
+
return self.responses.get(url, {"status": 404, "body": "Not Found"})
|
183
|
+
|
184
|
+
async def post(self, url: str, data=None, **kwargs) -> dict:
|
185
|
+
"""Mock POST request."""
|
186
|
+
self.requests.append(
|
187
|
+
{"method": "POST", "url": url, "data": data, "kwargs": kwargs}
|
188
|
+
)
|
189
|
+
return self.responses.get(url, {"status": 200, "body": "OK"})
|
190
|
+
|
191
|
+
async def close(self):
|
192
|
+
"""Close the client."""
|
193
|
+
pass
|
194
|
+
|
195
|
+
async def __aenter__(self):
|
196
|
+
return self
|
197
|
+
|
198
|
+
async def __aexit__(self, *args):
|
199
|
+
await self.close()
|
200
|
+
|
201
|
+
return AsyncTestClient()
|
202
|
+
|
203
|
+
|
204
|
+
__all__ = [
|
205
|
+
"async_mock_server",
|
206
|
+
"async_stream_reader",
|
207
|
+
"async_subprocess",
|
208
|
+
"async_test_client",
|
209
|
+
"mock_async_process",
|
210
|
+
]
|
@@ -6,33 +6,33 @@ and concurrent operations across any project that depends on provide.foundation.
|
|
6
6
|
"""
|
7
7
|
|
8
8
|
from provide.foundation.testing.threading.fixtures import (
|
9
|
+
concurrent_executor,
|
10
|
+
deadlock_detector,
|
11
|
+
mock_thread,
|
9
12
|
test_thread,
|
10
|
-
thread_pool,
|
11
13
|
thread_barrier,
|
12
|
-
thread_safe_list,
|
13
|
-
thread_safe_counter,
|
14
|
-
thread_event,
|
15
14
|
thread_condition,
|
16
|
-
|
15
|
+
thread_event,
|
16
|
+
thread_exception_handler,
|
17
17
|
thread_local_storage,
|
18
|
-
|
18
|
+
thread_pool,
|
19
|
+
thread_safe_counter,
|
20
|
+
thread_safe_list,
|
19
21
|
thread_synchronizer,
|
20
|
-
deadlock_detector,
|
21
|
-
thread_exception_handler,
|
22
22
|
)
|
23
23
|
|
24
24
|
__all__ = [
|
25
|
+
"concurrent_executor",
|
26
|
+
"deadlock_detector",
|
27
|
+
"mock_thread",
|
25
28
|
"test_thread",
|
26
|
-
"thread_pool",
|
27
29
|
"thread_barrier",
|
28
|
-
"thread_safe_list",
|
29
|
-
"thread_safe_counter",
|
30
|
-
"thread_event",
|
31
30
|
"thread_condition",
|
32
|
-
"
|
31
|
+
"thread_event",
|
32
|
+
"thread_exception_handler",
|
33
33
|
"thread_local_storage",
|
34
|
-
"
|
34
|
+
"thread_pool",
|
35
|
+
"thread_safe_counter",
|
36
|
+
"thread_safe_list",
|
35
37
|
"thread_synchronizer",
|
36
|
-
|
37
|
-
"thread_exception_handler",
|
38
|
-
]
|
38
|
+
]
|
@@ -0,0 +1,105 @@
|
|
1
|
+
"""
|
2
|
+
Basic threading test fixtures.
|
3
|
+
|
4
|
+
Core fixtures for creating threads, thread pools, mocks, and thread-local storage.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from concurrent.futures import ThreadPoolExecutor
|
8
|
+
import threading
|
9
|
+
from typing import Callable
|
10
|
+
from unittest.mock import Mock
|
11
|
+
|
12
|
+
import pytest
|
13
|
+
|
14
|
+
|
15
|
+
@pytest.fixture
|
16
|
+
def test_thread():
|
17
|
+
"""
|
18
|
+
Create a test thread with automatic cleanup.
|
19
|
+
|
20
|
+
Returns:
|
21
|
+
Function to create and manage test threads.
|
22
|
+
"""
|
23
|
+
threads = []
|
24
|
+
|
25
|
+
def _create_thread(
|
26
|
+
target: Callable, args: tuple = (), kwargs: dict = None, daemon: bool = True
|
27
|
+
) -> threading.Thread:
|
28
|
+
"""
|
29
|
+
Create a test thread.
|
30
|
+
|
31
|
+
Args:
|
32
|
+
target: Function to run in thread
|
33
|
+
args: Positional arguments for target
|
34
|
+
kwargs: Keyword arguments for target
|
35
|
+
daemon: Whether thread should be daemon
|
36
|
+
|
37
|
+
Returns:
|
38
|
+
Started thread instance
|
39
|
+
"""
|
40
|
+
kwargs = kwargs or {}
|
41
|
+
thread = threading.Thread(
|
42
|
+
target=target, args=args, kwargs=kwargs, daemon=daemon
|
43
|
+
)
|
44
|
+
threads.append(thread)
|
45
|
+
thread.start()
|
46
|
+
return thread
|
47
|
+
|
48
|
+
yield _create_thread
|
49
|
+
|
50
|
+
# Cleanup: wait for all threads to complete
|
51
|
+
for thread in threads:
|
52
|
+
if thread.is_alive():
|
53
|
+
thread.join(timeout=1.0)
|
54
|
+
|
55
|
+
|
56
|
+
@pytest.fixture
|
57
|
+
def thread_pool():
|
58
|
+
"""
|
59
|
+
Create a thread pool executor for testing.
|
60
|
+
|
61
|
+
Returns:
|
62
|
+
ThreadPoolExecutor instance with automatic cleanup.
|
63
|
+
"""
|
64
|
+
executor = ThreadPoolExecutor(max_workers=4)
|
65
|
+
yield executor
|
66
|
+
executor.shutdown(wait=True, cancel_futures=True)
|
67
|
+
|
68
|
+
|
69
|
+
@pytest.fixture
|
70
|
+
def mock_thread():
|
71
|
+
"""
|
72
|
+
Create a mock thread for testing without actual threading.
|
73
|
+
|
74
|
+
Returns:
|
75
|
+
Mock thread object.
|
76
|
+
"""
|
77
|
+
mock = Mock(spec=threading.Thread)
|
78
|
+
mock.is_alive.return_value = False
|
79
|
+
mock.daemon = False
|
80
|
+
mock.name = "MockThread"
|
81
|
+
mock.ident = 12345
|
82
|
+
mock.start = Mock()
|
83
|
+
mock.join = Mock()
|
84
|
+
mock.run = Mock()
|
85
|
+
|
86
|
+
return mock
|
87
|
+
|
88
|
+
|
89
|
+
@pytest.fixture
|
90
|
+
def thread_local_storage():
|
91
|
+
"""
|
92
|
+
Create thread-local storage for testing.
|
93
|
+
|
94
|
+
Returns:
|
95
|
+
Thread-local storage object.
|
96
|
+
"""
|
97
|
+
return threading.local()
|
98
|
+
|
99
|
+
|
100
|
+
__all__ = [
|
101
|
+
"mock_thread",
|
102
|
+
"test_thread",
|
103
|
+
"thread_local_storage",
|
104
|
+
"thread_pool",
|
105
|
+
]
|
@@ -0,0 +1,101 @@
|
|
1
|
+
"""
|
2
|
+
Thread-safe data structure test fixtures.
|
3
|
+
|
4
|
+
Fixtures for thread-safe lists, counters, and other data structures for testing.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import threading
|
8
|
+
from typing import Any
|
9
|
+
|
10
|
+
import pytest
|
11
|
+
|
12
|
+
|
13
|
+
@pytest.fixture
|
14
|
+
def thread_safe_list():
|
15
|
+
"""
|
16
|
+
Create a thread-safe list for collecting results.
|
17
|
+
|
18
|
+
Returns:
|
19
|
+
Thread-safe list implementation.
|
20
|
+
"""
|
21
|
+
|
22
|
+
class ThreadSafeList:
|
23
|
+
def __init__(self):
|
24
|
+
self._list = []
|
25
|
+
self._lock = threading.Lock()
|
26
|
+
|
27
|
+
def append(self, item: Any):
|
28
|
+
"""Thread-safe append."""
|
29
|
+
with self._lock:
|
30
|
+
self._list.append(item)
|
31
|
+
|
32
|
+
def extend(self, items):
|
33
|
+
"""Thread-safe extend."""
|
34
|
+
with self._lock:
|
35
|
+
self._list.extend(items)
|
36
|
+
|
37
|
+
def get_all(self) -> list:
|
38
|
+
"""Get copy of all items."""
|
39
|
+
with self._lock:
|
40
|
+
return self._list.copy()
|
41
|
+
|
42
|
+
def clear(self):
|
43
|
+
"""Clear the list."""
|
44
|
+
with self._lock:
|
45
|
+
self._list.clear()
|
46
|
+
|
47
|
+
def __len__(self) -> int:
|
48
|
+
with self._lock:
|
49
|
+
return len(self._list)
|
50
|
+
|
51
|
+
def __getitem__(self, index):
|
52
|
+
with self._lock:
|
53
|
+
return self._list[index]
|
54
|
+
|
55
|
+
return ThreadSafeList()
|
56
|
+
|
57
|
+
|
58
|
+
@pytest.fixture
|
59
|
+
def thread_safe_counter():
|
60
|
+
"""
|
61
|
+
Create a thread-safe counter.
|
62
|
+
|
63
|
+
Returns:
|
64
|
+
Thread-safe counter implementation.
|
65
|
+
"""
|
66
|
+
|
67
|
+
class ThreadSafeCounter:
|
68
|
+
def __init__(self, initial: int = 0):
|
69
|
+
self._value = initial
|
70
|
+
self._lock = threading.Lock()
|
71
|
+
|
72
|
+
def increment(self, amount: int = 1) -> int:
|
73
|
+
"""Thread-safe increment."""
|
74
|
+
with self._lock:
|
75
|
+
self._value += amount
|
76
|
+
return self._value
|
77
|
+
|
78
|
+
def decrement(self, amount: int = 1) -> int:
|
79
|
+
"""Thread-safe decrement."""
|
80
|
+
with self._lock:
|
81
|
+
self._value -= amount
|
82
|
+
return self._value
|
83
|
+
|
84
|
+
@property
|
85
|
+
def value(self) -> int:
|
86
|
+
"""Get current value."""
|
87
|
+
with self._lock:
|
88
|
+
return self._value
|
89
|
+
|
90
|
+
def reset(self, value: int = 0):
|
91
|
+
"""Reset counter."""
|
92
|
+
with self._lock:
|
93
|
+
self._value = value
|
94
|
+
|
95
|
+
return ThreadSafeCounter()
|
96
|
+
|
97
|
+
|
98
|
+
__all__ = [
|
99
|
+
"thread_safe_counter",
|
100
|
+
"thread_safe_list",
|
101
|
+
]
|