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.
Files changed (163) hide show
  1. provide/foundation/__init__.py +36 -10
  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 +93 -96
  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 +15 -9
  13. provide/foundation/cli/commands/logs/__init__.py +3 -3
  14. provide/foundation/cli/commands/logs/generate.py +2 -2
  15. provide/foundation/cli/commands/logs/query.py +4 -4
  16. provide/foundation/cli/commands/logs/send.py +3 -3
  17. provide/foundation/cli/commands/logs/tail.py +3 -3
  18. provide/foundation/cli/decorators.py +11 -11
  19. provide/foundation/cli/main.py +1 -1
  20. provide/foundation/cli/testing.py +2 -40
  21. provide/foundation/cli/utils.py +21 -18
  22. provide/foundation/config/__init__.py +35 -2
  23. provide/foundation/config/base.py +2 -2
  24. provide/foundation/config/converters.py +477 -0
  25. provide/foundation/config/defaults.py +67 -0
  26. provide/foundation/config/env.py +6 -20
  27. provide/foundation/config/loader.py +10 -4
  28. provide/foundation/config/sync.py +8 -6
  29. provide/foundation/config/types.py +5 -5
  30. provide/foundation/config/validators.py +4 -4
  31. provide/foundation/console/input.py +5 -5
  32. provide/foundation/console/output.py +36 -14
  33. provide/foundation/context/__init__.py +8 -4
  34. provide/foundation/context/core.py +88 -110
  35. provide/foundation/crypto/certificates/__init__.py +9 -5
  36. provide/foundation/crypto/certificates/base.py +2 -2
  37. provide/foundation/crypto/certificates/certificate.py +48 -19
  38. provide/foundation/crypto/certificates/factory.py +26 -18
  39. provide/foundation/crypto/certificates/generator.py +24 -23
  40. provide/foundation/crypto/certificates/loader.py +24 -16
  41. provide/foundation/crypto/certificates/operations.py +17 -10
  42. provide/foundation/crypto/certificates/trust.py +21 -21
  43. provide/foundation/env/__init__.py +28 -0
  44. provide/foundation/env/core.py +218 -0
  45. provide/foundation/errors/__init__.py +3 -3
  46. provide/foundation/errors/decorators.py +0 -234
  47. provide/foundation/errors/types.py +0 -98
  48. provide/foundation/eventsets/display.py +13 -14
  49. provide/foundation/eventsets/registry.py +61 -31
  50. provide/foundation/eventsets/resolver.py +50 -46
  51. provide/foundation/eventsets/sets/das.py +8 -8
  52. provide/foundation/eventsets/sets/database.py +14 -14
  53. provide/foundation/eventsets/sets/http.py +21 -21
  54. provide/foundation/eventsets/sets/llm.py +16 -16
  55. provide/foundation/eventsets/sets/task_queue.py +13 -13
  56. provide/foundation/eventsets/types.py +7 -7
  57. provide/foundation/file/directory.py +14 -23
  58. provide/foundation/file/lock.py +4 -3
  59. provide/foundation/hub/components.py +75 -389
  60. provide/foundation/hub/config.py +157 -0
  61. provide/foundation/hub/discovery.py +63 -0
  62. provide/foundation/hub/handlers.py +89 -0
  63. provide/foundation/hub/lifecycle.py +195 -0
  64. provide/foundation/hub/manager.py +7 -4
  65. provide/foundation/hub/processors.py +49 -0
  66. provide/foundation/integrations/__init__.py +11 -0
  67. provide/foundation/{observability → integrations}/openobserve/__init__.py +10 -7
  68. provide/foundation/{observability → integrations}/openobserve/auth.py +1 -1
  69. provide/foundation/{observability → integrations}/openobserve/client.py +14 -14
  70. provide/foundation/{observability → integrations}/openobserve/commands.py +12 -12
  71. provide/foundation/integrations/openobserve/config.py +37 -0
  72. provide/foundation/{observability → integrations}/openobserve/formatters.py +1 -1
  73. provide/foundation/{observability → integrations}/openobserve/otlp.py +2 -2
  74. provide/foundation/{observability → integrations}/openobserve/search.py +2 -3
  75. provide/foundation/{observability → integrations}/openobserve/streaming.py +5 -5
  76. provide/foundation/logger/__init__.py +0 -1
  77. provide/foundation/logger/config/base.py +1 -1
  78. provide/foundation/logger/config/logging.py +69 -299
  79. provide/foundation/logger/config/telemetry.py +39 -121
  80. provide/foundation/logger/factories.py +2 -2
  81. provide/foundation/logger/processors/main.py +12 -10
  82. provide/foundation/logger/ratelimit/limiters.py +4 -4
  83. provide/foundation/logger/ratelimit/processor.py +1 -1
  84. provide/foundation/logger/setup/coordinator.py +39 -25
  85. provide/foundation/logger/setup/processors.py +3 -3
  86. provide/foundation/logger/setup/testing.py +14 -0
  87. provide/foundation/logger/trace.py +5 -5
  88. provide/foundation/metrics/__init__.py +1 -1
  89. provide/foundation/metrics/otel.py +3 -1
  90. provide/foundation/observability/__init__.py +3 -3
  91. provide/foundation/process/__init__.py +9 -0
  92. provide/foundation/process/exit.py +48 -0
  93. provide/foundation/process/lifecycle.py +69 -46
  94. provide/foundation/resilience/__init__.py +36 -0
  95. provide/foundation/resilience/circuit.py +166 -0
  96. provide/foundation/resilience/decorators.py +236 -0
  97. provide/foundation/resilience/fallback.py +208 -0
  98. provide/foundation/resilience/retry.py +327 -0
  99. provide/foundation/serialization/__init__.py +16 -0
  100. provide/foundation/serialization/core.py +70 -0
  101. provide/foundation/streams/config.py +78 -0
  102. provide/foundation/streams/console.py +4 -5
  103. provide/foundation/streams/core.py +5 -2
  104. provide/foundation/streams/file.py +12 -2
  105. provide/foundation/testing/__init__.py +29 -9
  106. provide/foundation/testing/archive/__init__.py +7 -7
  107. provide/foundation/testing/archive/fixtures.py +58 -54
  108. provide/foundation/testing/cli.py +30 -20
  109. provide/foundation/testing/common/__init__.py +13 -15
  110. provide/foundation/testing/common/fixtures.py +27 -57
  111. provide/foundation/testing/file/__init__.py +15 -15
  112. provide/foundation/testing/file/content_fixtures.py +289 -0
  113. provide/foundation/testing/file/directory_fixtures.py +107 -0
  114. provide/foundation/testing/file/fixtures.py +42 -516
  115. provide/foundation/testing/file/special_fixtures.py +145 -0
  116. provide/foundation/testing/logger.py +89 -8
  117. provide/foundation/testing/mocking/__init__.py +21 -21
  118. provide/foundation/testing/mocking/fixtures.py +80 -67
  119. provide/foundation/testing/process/__init__.py +23 -23
  120. provide/foundation/testing/process/async_fixtures.py +414 -0
  121. provide/foundation/testing/process/fixtures.py +48 -571
  122. provide/foundation/testing/process/subprocess_fixtures.py +210 -0
  123. provide/foundation/testing/threading/__init__.py +17 -17
  124. provide/foundation/testing/threading/basic_fixtures.py +105 -0
  125. provide/foundation/testing/threading/data_fixtures.py +101 -0
  126. provide/foundation/testing/threading/execution_fixtures.py +278 -0
  127. provide/foundation/testing/threading/fixtures.py +32 -502
  128. provide/foundation/testing/threading/sync_fixtures.py +100 -0
  129. provide/foundation/testing/time/__init__.py +11 -11
  130. provide/foundation/testing/time/fixtures.py +95 -83
  131. provide/foundation/testing/transport/__init__.py +9 -9
  132. provide/foundation/testing/transport/fixtures.py +54 -54
  133. provide/foundation/time/__init__.py +18 -0
  134. provide/foundation/time/core.py +63 -0
  135. provide/foundation/tools/__init__.py +2 -2
  136. provide/foundation/tools/base.py +68 -67
  137. provide/foundation/tools/cache.py +69 -74
  138. provide/foundation/tools/downloader.py +68 -62
  139. provide/foundation/tools/installer.py +51 -57
  140. provide/foundation/tools/registry.py +38 -45
  141. provide/foundation/tools/resolver.py +70 -68
  142. provide/foundation/tools/verifier.py +39 -50
  143. provide/foundation/tracer/spans.py +2 -14
  144. provide/foundation/transport/__init__.py +26 -33
  145. provide/foundation/transport/base.py +32 -30
  146. provide/foundation/transport/client.py +44 -49
  147. provide/foundation/transport/config.py +36 -107
  148. provide/foundation/transport/errors.py +13 -27
  149. provide/foundation/transport/http.py +69 -55
  150. provide/foundation/transport/middleware.py +113 -114
  151. provide/foundation/transport/registry.py +29 -27
  152. provide/foundation/transport/types.py +6 -6
  153. provide/foundation/utils/deps.py +17 -14
  154. provide/foundation/utils/parsing.py +49 -4
  155. {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/METADATA +2 -2
  156. provide_foundation-0.0.0.dev3.dist-info/RECORD +233 -0
  157. provide_foundation-0.0.0.dev1.dist-info/RECORD +0 -200
  158. /provide/foundation/{observability → integrations}/openobserve/exceptions.py +0 -0
  159. /provide/foundation/{observability → integrations}/openobserve/models.py +0 -0
  160. {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/WHEEL +0 -0
  161. {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/entry_points.txt +0 -0
  162. {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/licenses/LICENSE +0 -0
  163. {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/top_level.txt +0 -0
@@ -5,8 +5,7 @@ Reusable mock objects for configuration, logging, and other common
5
5
  testing scenarios across the provide-io ecosystem.
6
6
  """
7
7
 
8
- from unittest.mock import Mock, MagicMock, PropertyMock
9
- from typing import Any
8
+ from unittest.mock import Mock, PropertyMock
10
9
 
11
10
  import pytest
12
11
 
@@ -18,12 +17,12 @@ from provide.foundation.logger.config.logging import LoggingConfig
18
17
  def mock_http_config():
19
18
  """
20
19
  Standard HTTP configuration for testing.
21
-
20
+
22
21
  Returns:
23
22
  HTTPConfig with common test settings.
24
23
  """
25
24
  from provide.foundation.transport.config import HTTPConfig
26
-
25
+
27
26
  return HTTPConfig(
28
27
  timeout=30.0,
29
28
  max_retries=3,
@@ -37,40 +36,11 @@ def mock_http_config():
37
36
  )
38
37
 
39
38
 
40
- @pytest.fixture
41
- def mock_logger():
42
- """
43
- Mock logger with captured method calls.
44
-
45
- Returns:
46
- Mock logger with debug, info, warning, error methods.
47
- """
48
- logger = Mock()
49
- logger.debug = Mock()
50
- logger.info = Mock()
51
- logger.warning = Mock()
52
- logger.error = Mock()
53
- logger.exception = Mock()
54
- logger.critical = Mock()
55
-
56
- # Add common logger attributes
57
- logger.name = "mock_logger"
58
- logger.level = 10 # DEBUG level
59
- logger.handlers = []
60
- logger.disabled = False
61
-
62
- # Add bind method for structlog compatibility
63
- logger.bind = Mock(return_value=logger)
64
- logger.unbind = Mock(return_value=logger)
65
-
66
- return logger
67
-
68
-
69
39
  @pytest.fixture
70
40
  def mock_telemetry_config():
71
41
  """
72
42
  Standard telemetry configuration for testing.
73
-
43
+
74
44
  Returns:
75
45
  TelemetryConfig with debug logging enabled.
76
46
  """
@@ -86,7 +56,7 @@ def mock_telemetry_config():
86
56
  def mock_config_source():
87
57
  """
88
58
  Mock configuration source for testing config loading.
89
-
59
+
90
60
  Returns:
91
61
  Mock that simulates a configuration source.
92
62
  """
@@ -97,7 +67,7 @@ def mock_config_source():
97
67
  source.watch = Mock()
98
68
  source.priority = 100
99
69
  source.name = "mock_source"
100
-
70
+
101
71
  return source
102
72
 
103
73
 
@@ -105,7 +75,7 @@ def mock_config_source():
105
75
  def mock_event_emitter():
106
76
  """
107
77
  Mock event emitter for testing event-driven components.
108
-
78
+
109
79
  Returns:
110
80
  Mock with emit, on, off methods.
111
81
  """
@@ -116,7 +86,7 @@ def mock_event_emitter():
116
86
  emitter.once = Mock()
117
87
  emitter.listeners = Mock(return_value=[])
118
88
  emitter.remove_all_listeners = Mock()
119
-
89
+
120
90
  return emitter
121
91
 
122
92
 
@@ -124,7 +94,7 @@ def mock_event_emitter():
124
94
  def mock_transport():
125
95
  """
126
96
  Mock transport for testing network operations.
127
-
97
+
128
98
  Returns:
129
99
  Mock transport with request/response methods.
130
100
  """
@@ -135,7 +105,7 @@ def mock_transport():
135
105
  transport.put = Mock(return_value={"status": 200, "data": {}})
136
106
  transport.delete = Mock(return_value={"status": 204})
137
107
  transport.close = Mock()
138
-
108
+
139
109
  return transport
140
110
 
141
111
 
@@ -143,7 +113,7 @@ def mock_transport():
143
113
  def mock_metrics_collector():
144
114
  """
145
115
  Mock metrics collector for testing instrumentation.
146
-
116
+
147
117
  Returns:
148
118
  Mock with common metrics methods.
149
119
  """
@@ -154,13 +124,13 @@ def mock_metrics_collector():
154
124
  collector.histogram = Mock()
155
125
  collector.timer = Mock()
156
126
  collector.flush = Mock()
157
-
127
+
158
128
  # Add context manager support for timing
159
129
  timer_cm = Mock()
160
130
  timer_cm.__enter__ = Mock(return_value=timer_cm)
161
131
  timer_cm.__exit__ = Mock(return_value=None)
162
132
  collector.timer.return_value = timer_cm
163
-
133
+
164
134
  return collector
165
135
 
166
136
 
@@ -168,12 +138,12 @@ def mock_metrics_collector():
168
138
  def mock_cache():
169
139
  """
170
140
  Mock cache for testing caching behavior.
171
-
141
+
172
142
  Returns:
173
143
  Mock with get, set, delete, clear methods.
174
144
  """
175
145
  cache_data = {}
176
-
146
+
177
147
  cache = Mock()
178
148
  cache.get = Mock(side_effect=lambda k, default=None: cache_data.get(k, default))
179
149
  cache.set = Mock(side_effect=lambda k, v, ttl=None: cache_data.update({k: v}))
@@ -181,10 +151,10 @@ def mock_cache():
181
151
  cache.clear = Mock(side_effect=cache_data.clear)
182
152
  cache.exists = Mock(side_effect=lambda k: k in cache_data)
183
153
  cache.keys = Mock(return_value=list(cache_data.keys()))
184
-
154
+
185
155
  # Store reference to data for test assertions
186
156
  cache._data = cache_data
187
-
157
+
188
158
  return cache
189
159
 
190
160
 
@@ -192,7 +162,7 @@ def mock_cache():
192
162
  def mock_database():
193
163
  """
194
164
  Mock database connection for testing.
195
-
165
+
196
166
  Returns:
197
167
  Mock with execute, fetch, commit, rollback methods.
198
168
  """
@@ -205,11 +175,11 @@ def mock_database():
205
175
  db.rollback = Mock()
206
176
  db.close = Mock()
207
177
  db.is_connected = PropertyMock(return_value=True)
208
-
178
+
209
179
  # Add context manager support
210
180
  db.__enter__ = Mock(return_value=db)
211
181
  db.__exit__ = Mock(return_value=None)
212
-
182
+
213
183
  return db
214
184
 
215
185
 
@@ -217,7 +187,7 @@ def mock_database():
217
187
  def mock_file_system():
218
188
  """
219
189
  Mock file system operations.
220
-
190
+
221
191
  Returns:
222
192
  Mock with read, write, exists, delete methods.
223
193
  """
@@ -230,7 +200,7 @@ def mock_file_system():
230
200
  fs.rmdir = Mock()
231
201
  fs.list = Mock(return_value=[])
232
202
  fs.stat = Mock(return_value=Mock(st_size=1024, st_mtime=0))
233
-
203
+
234
204
  return fs
235
205
 
236
206
 
@@ -238,19 +208,19 @@ def mock_file_system():
238
208
  def mock_subprocess():
239
209
  """
240
210
  Mock subprocess for testing command execution.
241
-
211
+
242
212
  Returns:
243
213
  Mock with run, Popen methods.
244
214
  """
245
215
  subprocess = Mock()
246
-
216
+
247
217
  # Mock run method
248
218
  result = Mock()
249
219
  result.returncode = 0
250
220
  result.stdout = "output"
251
221
  result.stderr = ""
252
222
  subprocess.run = Mock(return_value=result)
253
-
223
+
254
224
  # Mock Popen
255
225
  process = Mock()
256
226
  process.communicate = Mock(return_value=("output", ""))
@@ -259,5 +229,5 @@ def mock_subprocess():
259
229
  process.poll = Mock(return_value=0)
260
230
  process.wait = Mock(return_value=0)
261
231
  subprocess.Popen = Mock(return_value=process)
262
-
263
- return subprocess
232
+
233
+ return subprocess
@@ -6,35 +6,35 @@ across any project that depends on provide.foundation.
6
6
  """
7
7
 
8
8
  from provide.foundation.testing.file.fixtures import (
9
- temp_directory,
10
- test_files_structure,
11
- temp_file,
12
9
  binary_file,
13
- nested_directory_structure,
14
10
  empty_directory,
11
+ nested_directory_structure,
15
12
  readonly_file,
16
- temp_named_file,
17
- temp_file_with_content,
18
13
  temp_binary_file,
19
14
  temp_csv_file,
15
+ temp_directory,
16
+ temp_executable_file,
17
+ temp_file,
18
+ temp_file_with_content,
20
19
  temp_json_file,
20
+ temp_named_file,
21
21
  temp_symlink,
22
- temp_executable_file,
22
+ test_files_structure,
23
23
  )
24
24
 
25
25
  __all__ = [
26
- "temp_directory",
27
- "test_files_structure",
28
- "temp_file",
29
26
  "binary_file",
30
- "nested_directory_structure",
31
27
  "empty_directory",
28
+ "nested_directory_structure",
32
29
  "readonly_file",
33
- "temp_named_file",
34
- "temp_file_with_content",
35
30
  "temp_binary_file",
36
31
  "temp_csv_file",
32
+ "temp_directory",
33
+ "temp_executable_file",
34
+ "temp_file",
35
+ "temp_file_with_content",
37
36
  "temp_json_file",
37
+ "temp_named_file",
38
38
  "temp_symlink",
39
- "temp_executable_file",
40
- ]
39
+ "test_files_structure",
40
+ ]
@@ -0,0 +1,289 @@
1
+ """
2
+ Content-based file test fixtures.
3
+
4
+ Fixtures for creating files with specific content types like text, binary,
5
+ CSV, JSON, and other structured data.
6
+ """
7
+
8
+ import csv
9
+ import json
10
+ from pathlib import Path
11
+ import random
12
+ import tempfile
13
+
14
+ import pytest
15
+
16
+ from provide.foundation.file.safe import safe_delete
17
+
18
+
19
+ @pytest.fixture
20
+ def temp_file():
21
+ """
22
+ Create a temporary file factory with optional content.
23
+
24
+ Returns:
25
+ A function that creates temporary files with specified content and suffix.
26
+ """
27
+ created_files = []
28
+
29
+ def _make_temp_file(content: str = "test content", suffix: str = ".txt") -> Path:
30
+ """
31
+ Create a temporary file.
32
+
33
+ Args:
34
+ content: Content to write to the file
35
+ suffix: File suffix/extension
36
+
37
+ Returns:
38
+ Path to the created temporary file
39
+ """
40
+ with tempfile.NamedTemporaryFile(mode="w", suffix=suffix, delete=False) as f:
41
+ f.write(content)
42
+ path = Path(f.name)
43
+ created_files.append(path)
44
+ return path
45
+
46
+ yield _make_temp_file
47
+
48
+ # Cleanup all created files
49
+ for path in created_files:
50
+ safe_delete(path, missing_ok=True)
51
+
52
+
53
+ @pytest.fixture
54
+ def temp_named_file():
55
+ """
56
+ Create a named temporary file factory.
57
+
58
+ Returns:
59
+ Function that creates named temporary files.
60
+ """
61
+ created_files = []
62
+
63
+ def _make_named_file(
64
+ content: bytes | str = None,
65
+ suffix: str = "",
66
+ prefix: str = "tmp",
67
+ dir: Path | str = None,
68
+ mode: str = "w+b",
69
+ delete: bool = False,
70
+ ) -> Path:
71
+ """
72
+ Create a named temporary file.
73
+
74
+ Args:
75
+ content: Optional content to write
76
+ suffix: File suffix
77
+ prefix: File prefix
78
+ dir: Directory for the file
79
+ mode: File mode
80
+ delete: Whether to delete on close
81
+
82
+ Returns:
83
+ Path to the created file
84
+ """
85
+ if isinstance(dir, Path):
86
+ dir = str(dir)
87
+
88
+ f = tempfile.NamedTemporaryFile(
89
+ mode=mode, suffix=suffix, prefix=prefix, dir=dir, delete=delete
90
+ )
91
+
92
+ if content is not None:
93
+ if isinstance(content, str):
94
+ if "b" in mode:
95
+ f.write(content.encode())
96
+ else:
97
+ f.write(content)
98
+ else:
99
+ f.write(content)
100
+ f.flush()
101
+
102
+ path = Path(f.name)
103
+ f.close()
104
+
105
+ if not delete:
106
+ created_files.append(path)
107
+
108
+ return path
109
+
110
+ yield _make_named_file
111
+
112
+ # Cleanup
113
+ for path in created_files:
114
+ safe_delete(path, missing_ok=True)
115
+
116
+
117
+ @pytest.fixture
118
+ def temp_file_with_content():
119
+ """
120
+ Create temporary files with specific content.
121
+
122
+ Returns:
123
+ Function that creates files with content.
124
+ """
125
+ created_files = []
126
+
127
+ def _make_file(
128
+ content: str | bytes, suffix: str = ".txt", encoding: str = "utf-8"
129
+ ) -> Path:
130
+ """
131
+ Create a temporary file with content.
132
+
133
+ Args:
134
+ content: Content to write
135
+ suffix: File suffix
136
+ encoding: Text encoding (for str content)
137
+
138
+ Returns:
139
+ Path to created file
140
+ """
141
+ with tempfile.NamedTemporaryFile(
142
+ mode="wb" if isinstance(content, bytes) else "w",
143
+ suffix=suffix,
144
+ delete=False,
145
+ encoding=None if isinstance(content, bytes) else encoding,
146
+ ) as f:
147
+ f.write(content)
148
+ path = Path(f.name)
149
+
150
+ created_files.append(path)
151
+ return path
152
+
153
+ yield _make_file
154
+
155
+ # Cleanup
156
+ for path in created_files:
157
+ safe_delete(path, missing_ok=True)
158
+
159
+
160
+ @pytest.fixture
161
+ def temp_binary_file():
162
+ """
163
+ Create temporary binary files.
164
+
165
+ Returns:
166
+ Function that creates binary files.
167
+ """
168
+ created_files = []
169
+
170
+ def _make_binary(
171
+ size: int = 1024, pattern: bytes = None, suffix: str = ".bin"
172
+ ) -> Path:
173
+ """
174
+ Create a temporary binary file.
175
+
176
+ Args:
177
+ size: File size in bytes
178
+ pattern: Optional byte pattern to repeat
179
+ suffix: File suffix
180
+
181
+ Returns:
182
+ Path to created binary file
183
+ """
184
+ if pattern is None:
185
+ # Create pseudo-random binary data
186
+ content = bytes(random.randint(0, 255) for _ in range(size))
187
+ else:
188
+ # Repeat pattern to reach size
189
+ repetitions = size // len(pattern) + 1
190
+ content = (pattern * repetitions)[:size]
191
+
192
+ with tempfile.NamedTemporaryFile(mode="wb", suffix=suffix, delete=False) as f:
193
+ f.write(content)
194
+ path = Path(f.name)
195
+
196
+ created_files.append(path)
197
+ return path
198
+
199
+ yield _make_binary
200
+
201
+ # Cleanup
202
+ for path in created_files:
203
+ safe_delete(path, missing_ok=True)
204
+
205
+
206
+ @pytest.fixture
207
+ def temp_csv_file():
208
+ """
209
+ Create temporary CSV files for testing.
210
+
211
+ Returns:
212
+ Function that creates CSV files.
213
+ """
214
+ created_files = []
215
+
216
+ def _make_csv(headers: list[str], rows: list[list], suffix: str = ".csv") -> Path:
217
+ """
218
+ Create a temporary CSV file.
219
+
220
+ Args:
221
+ headers: Column headers
222
+ rows: Data rows
223
+ suffix: File suffix
224
+
225
+ Returns:
226
+ Path to created CSV file
227
+ """
228
+ with tempfile.NamedTemporaryFile(
229
+ mode="w", suffix=suffix, delete=False, newline=""
230
+ ) as f:
231
+ writer = csv.writer(f)
232
+ writer.writerow(headers)
233
+ writer.writerows(rows)
234
+ path = Path(f.name)
235
+
236
+ created_files.append(path)
237
+ return path
238
+
239
+ yield _make_csv
240
+
241
+ # Cleanup
242
+ for path in created_files:
243
+ safe_delete(path, missing_ok=True)
244
+
245
+
246
+ @pytest.fixture
247
+ def temp_json_file():
248
+ """
249
+ Create temporary JSON files for testing.
250
+
251
+ Returns:
252
+ Function that creates JSON files.
253
+ """
254
+ created_files = []
255
+
256
+ def _make_json(data: dict | list, suffix: str = ".json", indent: int = 2) -> Path:
257
+ """
258
+ Create a temporary JSON file.
259
+
260
+ Args:
261
+ data: JSON data to write
262
+ suffix: File suffix
263
+ indent: JSON indentation
264
+
265
+ Returns:
266
+ Path to created JSON file
267
+ """
268
+ with tempfile.NamedTemporaryFile(mode="w", suffix=suffix, delete=False) as f:
269
+ json.dump(data, f, indent=indent)
270
+ path = Path(f.name)
271
+
272
+ created_files.append(path)
273
+ return path
274
+
275
+ yield _make_json
276
+
277
+ # Cleanup
278
+ for path in created_files:
279
+ safe_delete(path, missing_ok=True)
280
+
281
+
282
+ __all__ = [
283
+ "temp_binary_file",
284
+ "temp_csv_file",
285
+ "temp_file",
286
+ "temp_file_with_content",
287
+ "temp_json_file",
288
+ "temp_named_file",
289
+ ]
@@ -0,0 +1,107 @@
1
+ """
2
+ Directory-specific test fixtures.
3
+
4
+ Fixtures for creating temporary directories, nested structures,
5
+ and standard test directory layouts.
6
+ """
7
+
8
+ from collections.abc import Generator
9
+ from pathlib import Path
10
+ import tempfile
11
+
12
+ import pytest
13
+
14
+
15
+ @pytest.fixture
16
+ def temp_directory() -> Generator[Path, None, None]:
17
+ """
18
+ Create a temporary directory that's cleaned up after test.
19
+
20
+ Yields:
21
+ Path to the temporary directory.
22
+ """
23
+ with tempfile.TemporaryDirectory() as temp_dir:
24
+ yield Path(temp_dir)
25
+
26
+
27
+ @pytest.fixture
28
+ def test_files_structure() -> Generator[tuple[Path, Path], None, None]:
29
+ """
30
+ Create standard test file structure with files and subdirectories.
31
+
32
+ Creates:
33
+ - source/
34
+ - file1.txt (contains "Content 1")
35
+ - file2.txt (contains "Content 2")
36
+ - subdir/
37
+ - file3.txt (contains "Content 3")
38
+
39
+ Yields:
40
+ Tuple of (temp_path, source_path)
41
+ """
42
+ with tempfile.TemporaryDirectory() as temp_dir:
43
+ path = Path(temp_dir)
44
+ source = path / "source"
45
+ source.mkdir()
46
+
47
+ # Create test files
48
+ (source / "file1.txt").write_text("Content 1")
49
+ (source / "file2.txt").write_text("Content 2")
50
+
51
+ # Create subdirectory with files
52
+ subdir = source / "subdir"
53
+ subdir.mkdir()
54
+ (subdir / "file3.txt").write_text("Content 3")
55
+
56
+ yield path, source
57
+
58
+
59
+ @pytest.fixture
60
+ def nested_directory_structure() -> Generator[Path, None, None]:
61
+ """
62
+ Create a deeply nested directory structure for testing.
63
+
64
+ Creates:
65
+ - level1/
66
+ - level2/
67
+ - level3/
68
+ - deep_file.txt
69
+ - file_l2.txt
70
+ - file_l1.txt
71
+
72
+ Yields:
73
+ Path to the root of the structure.
74
+ """
75
+ with tempfile.TemporaryDirectory() as temp_dir:
76
+ root = Path(temp_dir)
77
+
78
+ # Create nested structure
79
+ deep_dir = root / "level1" / "level2" / "level3"
80
+ deep_dir.mkdir(parents=True)
81
+
82
+ # Add files at different levels
83
+ (root / "file_l1.txt").write_text("Level 1 file")
84
+ (root / "level1" / "file_l2.txt").write_text("Level 2 file")
85
+ (deep_dir / "deep_file.txt").write_text("Deep file")
86
+
87
+ yield root
88
+
89
+
90
+ @pytest.fixture
91
+ def empty_directory() -> Generator[Path, None, None]:
92
+ """
93
+ Create an empty temporary directory.
94
+
95
+ Yields:
96
+ Path to an empty directory.
97
+ """
98
+ with tempfile.TemporaryDirectory() as temp_dir:
99
+ yield Path(temp_dir)
100
+
101
+
102
+ __all__ = [
103
+ "empty_directory",
104
+ "nested_directory_structure",
105
+ "temp_directory",
106
+ "test_files_structure",
107
+ ]