provide-foundation 0.0.0.dev1__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 (93) hide show
  1. provide/foundation/__init__.py +29 -3
  2. provide/foundation/archive/operations.py +4 -6
  3. provide/foundation/cli/__init__.py +2 -2
  4. provide/foundation/cli/commands/deps.py +13 -7
  5. provide/foundation/cli/commands/logs/__init__.py +1 -1
  6. provide/foundation/cli/commands/logs/query.py +1 -1
  7. provide/foundation/cli/commands/logs/send.py +1 -1
  8. provide/foundation/cli/commands/logs/tail.py +1 -1
  9. provide/foundation/cli/decorators.py +11 -10
  10. provide/foundation/cli/main.py +1 -1
  11. provide/foundation/cli/testing.py +2 -35
  12. provide/foundation/cli/utils.py +21 -17
  13. provide/foundation/config/__init__.py +35 -2
  14. provide/foundation/config/converters.py +479 -0
  15. provide/foundation/config/defaults.py +67 -0
  16. provide/foundation/config/env.py +4 -19
  17. provide/foundation/config/loader.py +9 -3
  18. provide/foundation/console/input.py +5 -5
  19. provide/foundation/console/output.py +35 -13
  20. provide/foundation/context/__init__.py +8 -4
  21. provide/foundation/context/core.py +85 -109
  22. provide/foundation/crypto/certificates/operations.py +1 -1
  23. provide/foundation/errors/__init__.py +2 -3
  24. provide/foundation/errors/decorators.py +0 -231
  25. provide/foundation/errors/types.py +0 -97
  26. provide/foundation/file/directory.py +13 -22
  27. provide/foundation/file/lock.py +3 -1
  28. provide/foundation/hub/components.py +72 -384
  29. provide/foundation/hub/config.py +151 -0
  30. provide/foundation/hub/discovery.py +62 -0
  31. provide/foundation/hub/handlers.py +81 -0
  32. provide/foundation/hub/lifecycle.py +194 -0
  33. provide/foundation/hub/manager.py +4 -4
  34. provide/foundation/hub/processors.py +44 -0
  35. provide/foundation/integrations/__init__.py +11 -0
  36. provide/foundation/{observability → integrations}/openobserve/__init__.py +10 -7
  37. provide/foundation/{observability → integrations}/openobserve/auth.py +1 -1
  38. provide/foundation/{observability → integrations}/openobserve/client.py +12 -12
  39. provide/foundation/{observability → integrations}/openobserve/commands.py +3 -3
  40. provide/foundation/integrations/openobserve/config.py +37 -0
  41. provide/foundation/{observability → integrations}/openobserve/formatters.py +1 -1
  42. provide/foundation/{observability → integrations}/openobserve/otlp.py +1 -1
  43. provide/foundation/{observability → integrations}/openobserve/search.py +2 -2
  44. provide/foundation/{observability → integrations}/openobserve/streaming.py +4 -4
  45. provide/foundation/logger/config/logging.py +68 -298
  46. provide/foundation/logger/config/telemetry.py +41 -121
  47. provide/foundation/logger/setup/coordinator.py +1 -1
  48. provide/foundation/observability/__init__.py +2 -2
  49. provide/foundation/process/__init__.py +9 -0
  50. provide/foundation/process/exit.py +47 -0
  51. provide/foundation/process/lifecycle.py +33 -33
  52. provide/foundation/resilience/__init__.py +35 -0
  53. provide/foundation/resilience/circuit.py +164 -0
  54. provide/foundation/resilience/decorators.py +220 -0
  55. provide/foundation/resilience/fallback.py +193 -0
  56. provide/foundation/resilience/retry.py +325 -0
  57. provide/foundation/streams/config.py +79 -0
  58. provide/foundation/streams/console.py +7 -8
  59. provide/foundation/streams/core.py +6 -3
  60. provide/foundation/streams/file.py +12 -2
  61. provide/foundation/testing/__init__.py +7 -2
  62. provide/foundation/testing/cli.py +30 -17
  63. provide/foundation/testing/common/__init__.py +0 -2
  64. provide/foundation/testing/common/fixtures.py +0 -27
  65. provide/foundation/testing/file/content_fixtures.py +316 -0
  66. provide/foundation/testing/file/directory_fixtures.py +107 -0
  67. provide/foundation/testing/file/fixtures.py +45 -516
  68. provide/foundation/testing/file/special_fixtures.py +153 -0
  69. provide/foundation/testing/logger.py +76 -0
  70. provide/foundation/testing/process/async_fixtures.py +405 -0
  71. provide/foundation/testing/process/fixtures.py +50 -571
  72. provide/foundation/testing/process/subprocess_fixtures.py +209 -0
  73. provide/foundation/testing/threading/basic_fixtures.py +101 -0
  74. provide/foundation/testing/threading/data_fixtures.py +99 -0
  75. provide/foundation/testing/threading/execution_fixtures.py +263 -0
  76. provide/foundation/testing/threading/fixtures.py +34 -500
  77. provide/foundation/testing/threading/sync_fixtures.py +97 -0
  78. provide/foundation/testing/time/fixtures.py +4 -4
  79. provide/foundation/tools/cache.py +8 -6
  80. provide/foundation/tools/downloader.py +23 -12
  81. provide/foundation/tracer/spans.py +2 -2
  82. provide/foundation/transport/config.py +26 -95
  83. provide/foundation/transport/middleware.py +30 -36
  84. provide/foundation/utils/deps.py +14 -12
  85. provide/foundation/utils/parsing.py +49 -4
  86. {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev2.dist-info}/METADATA +1 -1
  87. {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev2.dist-info}/RECORD +93 -68
  88. /provide/foundation/{observability → integrations}/openobserve/exceptions.py +0 -0
  89. /provide/foundation/{observability → integrations}/openobserve/models.py +0 -0
  90. {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev2.dist-info}/WHEEL +0 -0
  91. {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev2.dist-info}/entry_points.txt +0 -0
  92. {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev2.dist-info}/licenses/LICENSE +0 -0
  93. {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev2.dist-info}/top_level.txt +0 -0
@@ -1,518 +1,52 @@
1
1
  """
2
2
  Threading Test Fixtures and Utilities.
3
3
 
4
+ Core threading fixtures with re-exports from specialized modules.
4
5
  Fixtures for testing multi-threaded code, thread synchronization,
5
6
  and concurrent operations across the provide-io ecosystem.
6
7
  """
7
8
 
8
- import threading
9
- import time
10
- import queue
11
- from typing import Any, Callable, Optional
12
- from collections.abc import Generator
13
- from concurrent.futures import ThreadPoolExecutor
14
- from unittest.mock import Mock
15
-
16
- import pytest
17
-
18
-
19
- @pytest.fixture
20
- def test_thread():
21
- """
22
- Create a test thread with automatic cleanup.
23
-
24
- Returns:
25
- Function to create and manage test threads.
26
- """
27
- threads = []
28
-
29
- def _create_thread(target: Callable, args: tuple = (), kwargs: dict = None, daemon: bool = True) -> threading.Thread:
30
- """
31
- Create a test thread.
32
-
33
- Args:
34
- target: Function to run in thread
35
- args: Positional arguments for target
36
- kwargs: Keyword arguments for target
37
- daemon: Whether thread should be daemon
38
-
39
- Returns:
40
- Started thread instance
41
- """
42
- kwargs = kwargs or {}
43
- thread = threading.Thread(target=target, args=args, kwargs=kwargs, daemon=daemon)
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 thread_barrier():
71
- """
72
- Create a barrier for thread synchronization.
73
-
74
- Returns:
75
- Function to create barriers for N threads.
76
- """
77
- barriers = []
78
-
79
- def _create_barrier(n_threads: int, timeout: Optional[float] = None) -> threading.Barrier:
80
- """
81
- Create a barrier for synchronizing threads.
82
-
83
- Args:
84
- n_threads: Number of threads to synchronize
85
- timeout: Optional timeout for barrier
86
-
87
- Returns:
88
- Barrier instance
89
- """
90
- barrier = threading.Barrier(n_threads, timeout=timeout)
91
- barriers.append(barrier)
92
- return barrier
93
-
94
- yield _create_barrier
95
-
96
- # Cleanup: abort all barriers
97
- for barrier in barriers:
98
- try:
99
- barrier.abort()
100
- except threading.BrokenBarrierError:
101
- pass
102
-
103
-
104
- @pytest.fixture
105
- def thread_safe_list():
106
- """
107
- Create a thread-safe list for collecting results.
108
-
109
- Returns:
110
- Thread-safe list implementation.
111
- """
112
- class ThreadSafeList:
113
- def __init__(self):
114
- self._list = []
115
- self._lock = threading.Lock()
116
-
117
- def append(self, item: Any):
118
- """Thread-safe append."""
119
- with self._lock:
120
- self._list.append(item)
121
-
122
- def extend(self, items):
123
- """Thread-safe extend."""
124
- with self._lock:
125
- self._list.extend(items)
126
-
127
- def get_all(self) -> list:
128
- """Get copy of all items."""
129
- with self._lock:
130
- return self._list.copy()
131
-
132
- def clear(self):
133
- """Clear the list."""
134
- with self._lock:
135
- self._list.clear()
136
-
137
- def __len__(self) -> int:
138
- with self._lock:
139
- return len(self._list)
140
-
141
- def __getitem__(self, index):
142
- with self._lock:
143
- return self._list[index]
144
-
145
- return ThreadSafeList()
146
-
147
-
148
- @pytest.fixture
149
- def thread_safe_counter():
150
- """
151
- Create a thread-safe counter.
152
-
153
- Returns:
154
- Thread-safe counter implementation.
155
- """
156
- class ThreadSafeCounter:
157
- def __init__(self, initial: int = 0):
158
- self._value = initial
159
- self._lock = threading.Lock()
160
-
161
- def increment(self, amount: int = 1) -> int:
162
- """Thread-safe increment."""
163
- with self._lock:
164
- self._value += amount
165
- return self._value
166
-
167
- def decrement(self, amount: int = 1) -> int:
168
- """Thread-safe decrement."""
169
- with self._lock:
170
- self._value -= amount
171
- return self._value
172
-
173
- @property
174
- def value(self) -> int:
175
- """Get current value."""
176
- with self._lock:
177
- return self._value
178
-
179
- def reset(self, value: int = 0):
180
- """Reset counter."""
181
- with self._lock:
182
- self._value = value
183
-
184
- return ThreadSafeCounter()
185
-
186
-
187
- @pytest.fixture
188
- def thread_event():
189
- """
190
- Create thread events for signaling.
191
-
192
- Returns:
193
- Function to create thread events.
194
- """
195
- events = []
196
-
197
- def _create_event() -> threading.Event:
198
- """Create a thread event."""
199
- event = threading.Event()
200
- events.append(event)
201
- return event
202
-
203
- yield _create_event
204
-
205
- # Cleanup: set all events to release waiting threads
206
- for event in events:
207
- event.set()
208
-
209
-
210
- @pytest.fixture
211
- def thread_condition():
212
- """
213
- Create condition variables for thread coordination.
214
-
215
- Returns:
216
- Function to create condition variables.
217
- """
218
- def _create_condition(lock: Optional[threading.Lock] = None) -> threading.Condition:
219
- """
220
- Create a condition variable.
221
-
222
- Args:
223
- lock: Optional lock to use (creates new if None)
224
-
225
- Returns:
226
- Condition variable
227
- """
228
- return threading.Condition(lock)
229
-
230
- return _create_condition
231
-
232
-
233
- @pytest.fixture
234
- def mock_thread():
235
- """
236
- Create a mock thread for testing without actual threading.
237
-
238
- Returns:
239
- Mock thread object.
240
- """
241
- mock = Mock(spec=threading.Thread)
242
- mock.is_alive.return_value = False
243
- mock.daemon = False
244
- mock.name = "MockThread"
245
- mock.ident = 12345
246
- mock.start = Mock()
247
- mock.join = Mock()
248
- mock.run = Mock()
249
-
250
- return mock
251
-
252
-
253
- @pytest.fixture
254
- def thread_local_storage():
255
- """
256
- Create thread-local storage for testing.
257
-
258
- Returns:
259
- Thread-local storage object.
260
- """
261
- return threading.local()
262
-
263
-
264
- @pytest.fixture
265
- def concurrent_executor():
266
- """
267
- Helper for executing functions concurrently in tests.
268
-
269
- Returns:
270
- Concurrent execution helper.
271
- """
272
- class ConcurrentExecutor:
273
- def __init__(self):
274
- self.results = []
275
- self.exceptions = []
276
-
277
- def run_concurrent(self, func: Callable, args_list: list[tuple], max_workers: int = 4) -> list[Any]:
278
- """
279
- Run function concurrently with different arguments.
280
-
281
- Args:
282
- func: Function to execute
283
- args_list: List of argument tuples
284
- max_workers: Maximum concurrent workers
285
-
286
- Returns:
287
- List of results in order
288
- """
289
- with ThreadPoolExecutor(max_workers=max_workers) as executor:
290
- futures = []
291
- for args in args_list:
292
- if isinstance(args, tuple):
293
- future = executor.submit(func, *args)
294
- else:
295
- future = executor.submit(func, args)
296
- futures.append(future)
297
-
298
- results = []
299
- for future in futures:
300
- try:
301
- result = future.result(timeout=10)
302
- results.append(result)
303
- self.results.append(result)
304
- except Exception as e:
305
- self.exceptions.append(e)
306
- results.append(None)
307
-
308
- return results
309
-
310
- def run_parallel(self, funcs: list[Callable], timeout: float = 10) -> list[Any]:
311
- """
312
- Run different functions in parallel.
313
-
314
- Args:
315
- funcs: List of functions to execute
316
- timeout: Timeout for each function
317
-
318
- Returns:
319
- List of results
320
- """
321
- with ThreadPoolExecutor(max_workers=len(funcs)) as executor:
322
- futures = [executor.submit(func) for func in funcs]
323
- results = []
324
-
325
- for future in futures:
326
- try:
327
- result = future.result(timeout=timeout)
328
- results.append(result)
329
- except Exception as e:
330
- self.exceptions.append(e)
331
- results.append(None)
332
-
333
- return results
334
-
335
- return ConcurrentExecutor()
336
-
337
-
338
- @pytest.fixture
339
- def thread_synchronizer():
340
- """
341
- Helper for synchronizing test threads.
342
-
343
- Returns:
344
- Thread synchronization helper.
345
- """
346
- class ThreadSynchronizer:
347
- def __init__(self):
348
- self.checkpoints = {}
349
-
350
- def checkpoint(self, name: str, thread_id: Optional[int] = None):
351
- """
352
- Record that a thread reached a checkpoint.
353
-
354
- Args:
355
- name: Checkpoint name
356
- thread_id: Optional thread ID (uses current if None)
357
- """
358
- thread_id = thread_id or threading.get_ident()
359
- if name not in self.checkpoints:
360
- self.checkpoints[name] = []
361
- self.checkpoints[name].append((thread_id, time.time()))
362
-
363
- def wait_for_checkpoint(self, name: str, count: int, timeout: float = 5.0) -> bool:
364
- """
365
- Wait for N threads to reach a checkpoint.
366
-
367
- Args:
368
- name: Checkpoint name
369
- count: Number of threads to wait for
370
- timeout: Maximum wait time
371
-
372
- Returns:
373
- True if checkpoint reached, False if timeout
374
- """
375
- start = time.time()
376
- while time.time() - start < timeout:
377
- if name in self.checkpoints and len(self.checkpoints[name]) >= count:
378
- return True
379
- time.sleep(0.01)
380
- return False
381
-
382
- def get_order(self, checkpoint: str) -> list[int]:
383
- """
384
- Get order in which threads reached checkpoint.
385
-
386
- Args:
387
- checkpoint: Checkpoint name
388
-
389
- Returns:
390
- List of thread IDs in order
391
- """
392
- if checkpoint not in self.checkpoints:
393
- return []
394
- return [tid for tid, _ in sorted(self.checkpoints[checkpoint], key=lambda x: x[1])]
395
-
396
- def clear(self):
397
- """Clear all checkpoints."""
398
- self.checkpoints.clear()
399
-
400
- return ThreadSynchronizer()
401
-
402
-
403
- @pytest.fixture
404
- def deadlock_detector():
405
- """
406
- Helper for detecting potential deadlocks in tests.
407
-
408
- Returns:
409
- Deadlock detection helper.
410
- """
411
- class DeadlockDetector:
412
- def __init__(self):
413
- self.locks_held = {} # thread_id -> set of locks
414
- self.lock = threading.Lock()
415
-
416
- def acquire(self, lock_name: str, thread_id: Optional[int] = None):
417
- """Record lock acquisition."""
418
- thread_id = thread_id or threading.get_ident()
419
- with self.lock:
420
- if thread_id not in self.locks_held:
421
- self.locks_held[thread_id] = set()
422
- self.locks_held[thread_id].add(lock_name)
423
-
424
- def release(self, lock_name: str, thread_id: Optional[int] = None):
425
- """Record lock release."""
426
- thread_id = thread_id or threading.get_ident()
427
- with self.lock:
428
- if thread_id in self.locks_held:
429
- self.locks_held[thread_id].discard(lock_name)
430
-
431
- def check_circular_wait(self) -> bool:
432
- """
433
- Check for potential circular wait conditions.
434
-
435
- Returns:
436
- True if potential deadlock detected
437
- """
438
- # Simplified check - in practice would need wait-for graph
439
- with self.lock:
440
- # Check if multiple threads hold multiple locks
441
- multi_lock_threads = [
442
- tid for tid, locks in self.locks_held.items()
443
- if len(locks) > 1
444
- ]
445
- return len(multi_lock_threads) > 1
446
-
447
- def get_held_locks(self) -> dict[int, set[str]]:
448
- """Get current lock holdings."""
449
- with self.lock:
450
- return self.locks_held.copy()
451
-
452
- return DeadlockDetector()
453
-
454
-
455
- @pytest.fixture
456
- def thread_exception_handler():
457
- """
458
- Capture exceptions from threads for testing.
459
-
460
- Returns:
461
- Exception handler for threads.
462
- """
463
- class ThreadExceptionHandler:
464
- def __init__(self):
465
- self.exceptions = []
466
- self.lock = threading.Lock()
467
-
468
- def handle(self, func: Callable) -> Callable:
469
- """
470
- Wrap function to capture exceptions.
471
-
472
- Args:
473
- func: Function to wrap
474
-
475
- Returns:
476
- Wrapped function
477
- """
478
- def wrapper(*args, **kwargs):
479
- try:
480
- return func(*args, **kwargs)
481
- except Exception as e:
482
- with self.lock:
483
- self.exceptions.append({
484
- 'thread': threading.current_thread().name,
485
- 'exception': e,
486
- 'time': time.time()
487
- })
488
- raise
489
-
490
- return wrapper
491
-
492
- def get_exceptions(self) -> list[dict]:
493
- """Get all captured exceptions."""
494
- with self.lock:
495
- return self.exceptions.copy()
496
-
497
- def assert_no_exceptions(self):
498
- """Assert no exceptions were raised."""
499
- with self.lock:
500
- if self.exceptions:
501
- raise AssertionError(f"Thread exceptions occurred: {self.exceptions}")
502
-
503
- return ThreadExceptionHandler()
9
+ # Re-export all fixtures from specialized modules
10
+ from provide.foundation.testing.threading.basic_fixtures import (
11
+ test_thread,
12
+ thread_pool,
13
+ mock_thread,
14
+ thread_local_storage,
15
+ )
16
+
17
+ from provide.foundation.testing.threading.sync_fixtures import (
18
+ thread_barrier,
19
+ thread_event,
20
+ thread_condition,
21
+ )
22
+
23
+ from provide.foundation.testing.threading.data_fixtures import (
24
+ thread_safe_list,
25
+ thread_safe_counter,
26
+ )
27
+
28
+ from provide.foundation.testing.threading.execution_fixtures import (
29
+ concurrent_executor,
30
+ thread_synchronizer,
31
+ deadlock_detector,
32
+ thread_exception_handler,
33
+ )
504
34
 
505
35
 
506
36
  __all__ = [
37
+ # Basic threading fixtures
507
38
  "test_thread",
508
39
  "thread_pool",
40
+ "mock_thread",
41
+ "thread_local_storage",
42
+ # Synchronization fixtures
509
43
  "thread_barrier",
510
- "thread_safe_list",
511
- "thread_safe_counter",
512
44
  "thread_event",
513
45
  "thread_condition",
514
- "mock_thread",
515
- "thread_local_storage",
46
+ # Thread-safe data structures
47
+ "thread_safe_list",
48
+ "thread_safe_counter",
49
+ # Execution and testing helpers
516
50
  "concurrent_executor",
517
51
  "thread_synchronizer",
518
52
  "deadlock_detector",
@@ -0,0 +1,97 @@
1
+ """
2
+ Thread synchronization test fixtures.
3
+
4
+ Fixtures for thread barriers, events, conditions, and other synchronization primitives.
5
+ """
6
+
7
+ import threading
8
+
9
+ import pytest
10
+
11
+
12
+ @pytest.fixture
13
+ def thread_barrier():
14
+ """
15
+ Create a barrier for thread synchronization.
16
+
17
+ Returns:
18
+ Function to create barriers for N threads.
19
+ """
20
+ barriers = []
21
+
22
+ def _create_barrier(n_threads: int, timeout: float | None = None) -> threading.Barrier:
23
+ """
24
+ Create a barrier for synchronizing threads.
25
+
26
+ Args:
27
+ n_threads: Number of threads to synchronize
28
+ timeout: Optional timeout for barrier
29
+
30
+ Returns:
31
+ Barrier instance
32
+ """
33
+ barrier = threading.Barrier(n_threads, timeout=timeout)
34
+ barriers.append(barrier)
35
+ return barrier
36
+
37
+ yield _create_barrier
38
+
39
+ # Cleanup: abort all barriers
40
+ for barrier in barriers:
41
+ try:
42
+ barrier.abort()
43
+ except threading.BrokenBarrierError:
44
+ pass
45
+
46
+
47
+ @pytest.fixture
48
+ def thread_event():
49
+ """
50
+ Create thread events for signaling.
51
+
52
+ Returns:
53
+ Function to create thread events.
54
+ """
55
+ events = []
56
+
57
+ def _create_event() -> threading.Event:
58
+ """Create a thread event."""
59
+ event = threading.Event()
60
+ events.append(event)
61
+ return event
62
+
63
+ yield _create_event
64
+
65
+ # Cleanup: set all events to release waiting threads
66
+ for event in events:
67
+ event.set()
68
+
69
+
70
+ @pytest.fixture
71
+ def thread_condition():
72
+ """
73
+ Create condition variables for thread coordination.
74
+
75
+ Returns:
76
+ Function to create condition variables.
77
+ """
78
+ def _create_condition(lock: Optional[threading.Lock] = None) -> threading.Condition:
79
+ """
80
+ Create a condition variable.
81
+
82
+ Args:
83
+ lock: Optional lock to use (creates new if None)
84
+
85
+ Returns:
86
+ Condition variable
87
+ """
88
+ return threading.Condition(lock)
89
+
90
+ return _create_condition
91
+
92
+
93
+ __all__ = [
94
+ "thread_barrier",
95
+ "thread_event",
96
+ "thread_condition",
97
+ ]
@@ -7,7 +7,7 @@ across the provide-io ecosystem.
7
7
 
8
8
  import time
9
9
  import datetime
10
- from typing import Any, Callable, Optional
10
+ from typing import Any, Callable
11
11
  from collections.abc import Generator
12
12
  from unittest.mock import Mock, patch
13
13
 
@@ -23,7 +23,7 @@ def freeze_time():
23
23
  Function that freezes time and returns a context manager.
24
24
  """
25
25
  class FrozenTime:
26
- def __init__(self, frozen_time: Optional[datetime.datetime] = None):
26
+ def __init__(self, frozen_time: datetime.datetime | None = None):
27
27
  self.frozen_time = frozen_time or datetime.datetime.now()
28
28
  self.original_time = time.time
29
29
  self.original_datetime = datetime.datetime
@@ -56,7 +56,7 @@ def freeze_time():
56
56
  if hasattr(p, 'return_value'):
57
57
  p.return_value = self.frozen_time.timestamp()
58
58
 
59
- def _freeze(at: Optional[datetime.datetime] = None) -> FrozenTime:
59
+ def _freeze(at: datetime.datetime | None = None) -> FrozenTime:
60
60
  """
61
61
  Freeze time at a specific point.
62
62
 
@@ -134,7 +134,7 @@ def time_machine():
134
134
  self.patches = []
135
135
  self.is_frozen = False
136
136
 
137
- def freeze(self, at: Optional[float] = None):
137
+ def freeze(self, at: float | None = None):
138
138
  """Freeze time at a specific timestamp."""
139
139
  self.is_frozen = True
140
140
  self.current_time = at or time.time()