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
@@ -1,523 +1,49 @@
1
1
  """
2
2
  File and Directory Test Fixtures.
3
3
 
4
+ Core file testing fixtures with re-exports from specialized modules.
4
5
  Common fixtures for testing file operations, creating temporary directories,
5
6
  and standard test file structures used across the provide-io ecosystem.
6
7
  """
7
8
 
8
- import tempfile
9
- from pathlib import Path
10
- from collections.abc import Generator
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 temp_file():
61
- """
62
- Create a temporary file factory with optional content.
63
-
64
- Returns:
65
- A function that creates temporary files with specified content and suffix.
66
- """
67
- created_files = []
68
-
69
- def _make_temp_file(content: str = "test content", suffix: str = ".txt") -> Path:
70
- """
71
- Create a temporary file.
72
-
73
- Args:
74
- content: Content to write to the file
75
- suffix: File suffix/extension
76
-
77
- Returns:
78
- Path to the created temporary file
79
- """
80
- with tempfile.NamedTemporaryFile(mode='w', suffix=suffix, delete=False) as f:
81
- f.write(content)
82
- path = Path(f.name)
83
- created_files.append(path)
84
- return path
85
-
86
- yield _make_temp_file
87
-
88
- # Cleanup all created files
89
- for path in created_files:
90
- path.unlink(missing_ok=True)
91
-
92
-
93
- @pytest.fixture
94
- def binary_file() -> Generator[Path, None, None]:
95
- """
96
- Create a temporary binary file for testing.
97
-
98
- Yields:
99
- Path to a binary file containing sample binary data.
100
- """
101
- with tempfile.NamedTemporaryFile(mode='wb', suffix='.bin', delete=False) as f:
102
- # Write some binary data
103
- f.write(b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09')
104
- f.write(b'\xFF\xFE\xFD\xFC\xFB\xFA\xF9\xF8\xF7\xF6')
105
- path = Path(f.name)
106
-
107
- yield path
108
- path.unlink(missing_ok=True)
109
-
110
-
111
- @pytest.fixture
112
- def nested_directory_structure() -> Generator[Path, None, None]:
113
- """
114
- Create a deeply nested directory structure for testing.
115
-
116
- Creates:
117
- - level1/
118
- - level2/
119
- - level3/
120
- - deep_file.txt
121
- - file_l2.txt
122
- - file_l1.txt
123
-
124
- Yields:
125
- Path to the root of the structure.
126
- """
127
- with tempfile.TemporaryDirectory() as temp_dir:
128
- root = Path(temp_dir)
129
-
130
- # Create nested structure
131
- deep_dir = root / "level1" / "level2" / "level3"
132
- deep_dir.mkdir(parents=True)
133
-
134
- # Add files at different levels
135
- (root / "file_l1.txt").write_text("Level 1 file")
136
- (root / "level1" / "file_l2.txt").write_text("Level 2 file")
137
- (deep_dir / "deep_file.txt").write_text("Deep file")
138
-
139
- yield root
140
-
141
-
142
- @pytest.fixture
143
- def empty_directory() -> Generator[Path, None, None]:
144
- """
145
- Create an empty temporary directory.
146
-
147
- Yields:
148
- Path to an empty directory.
149
- """
150
- with tempfile.TemporaryDirectory() as temp_dir:
151
- yield Path(temp_dir)
152
-
153
-
154
- @pytest.fixture
155
- def readonly_file() -> Generator[Path, None, None]:
156
- """
157
- Create a read-only file for permission testing.
158
-
159
- Yields:
160
- Path to a read-only file.
161
- """
162
- with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
163
- f.write("Read-only content")
164
- path = Path(f.name)
165
-
166
- # Make file read-only
167
- path.chmod(0o444)
168
-
169
- yield path
170
-
171
- # Restore write permission for cleanup
172
- path.chmod(0o644)
173
- path.unlink(missing_ok=True)
174
-
175
-
176
- @pytest.fixture
177
- def temp_named_file():
178
- """
179
- Create a named temporary file factory.
180
-
181
- Returns:
182
- Function that creates named temporary files.
183
- """
184
- created_files = []
185
-
186
- def _make_named_file(
187
- content: bytes | str = None,
188
- suffix: str = "",
189
- prefix: str = "tmp",
190
- dir: Path | str = None,
191
- mode: str = 'w+b',
192
- delete: bool = False
193
- ) -> Path:
194
- """
195
- Create a named temporary file.
196
-
197
- Args:
198
- content: Optional content to write
199
- suffix: File suffix
200
- prefix: File prefix
201
- dir: Directory for the file
202
- mode: File mode
203
- delete: Whether to delete on close
204
-
205
- Returns:
206
- Path to the created file
207
- """
208
- if isinstance(dir, Path):
209
- dir = str(dir)
210
-
211
- f = tempfile.NamedTemporaryFile(
212
- mode=mode,
213
- suffix=suffix,
214
- prefix=prefix,
215
- dir=dir,
216
- delete=delete
217
- )
218
-
219
- if content is not None:
220
- if isinstance(content, str):
221
- if 'b' in mode:
222
- f.write(content.encode())
223
- else:
224
- f.write(content)
225
- else:
226
- f.write(content)
227
- f.flush()
228
-
229
- path = Path(f.name)
230
- f.close()
231
-
232
- if not delete:
233
- created_files.append(path)
234
-
235
- return path
236
-
237
- yield _make_named_file
238
-
239
- # Cleanup
240
- for path in created_files:
241
- path.unlink(missing_ok=True)
242
-
243
-
244
- @pytest.fixture
245
- def temp_file_with_content():
246
- """
247
- Create temporary files with specific content.
248
-
249
- Returns:
250
- Function that creates files with content.
251
- """
252
- created_files = []
253
-
254
- def _make_file(
255
- content: str | bytes,
256
- suffix: str = ".txt",
257
- encoding: str = "utf-8"
258
- ) -> Path:
259
- """
260
- Create a temporary file with content.
261
-
262
- Args:
263
- content: Content to write
264
- suffix: File suffix
265
- encoding: Text encoding (for str content)
266
-
267
- Returns:
268
- Path to created file
269
- """
270
- with tempfile.NamedTemporaryFile(
271
- mode='wb' if isinstance(content, bytes) else 'w',
272
- suffix=suffix,
273
- delete=False,
274
- encoding=None if isinstance(content, bytes) else encoding
275
- ) as f:
276
- f.write(content)
277
- path = Path(f.name)
278
-
279
- created_files.append(path)
280
- return path
281
-
282
- yield _make_file
283
-
284
- # Cleanup
285
- for path in created_files:
286
- path.unlink(missing_ok=True)
287
-
288
-
289
- @pytest.fixture
290
- def temp_binary_file():
291
- """
292
- Create temporary binary files.
293
-
294
- Returns:
295
- Function that creates binary files.
296
- """
297
- created_files = []
298
-
299
- def _make_binary(
300
- size: int = 1024,
301
- pattern: bytes = None,
302
- suffix: str = ".bin"
303
- ) -> Path:
304
- """
305
- Create a temporary binary file.
306
-
307
- Args:
308
- size: File size in bytes
309
- pattern: Optional byte pattern to repeat
310
- suffix: File suffix
311
-
312
- Returns:
313
- Path to created binary file
314
- """
315
- if pattern is None:
316
- # Create pseudo-random binary data
317
- import random
318
- content = bytes(random.randint(0, 255) for _ in range(size))
319
- else:
320
- # Repeat pattern to reach size
321
- repetitions = size // len(pattern) + 1
322
- content = (pattern * repetitions)[:size]
323
-
324
- with tempfile.NamedTemporaryFile(
325
- mode='wb',
326
- suffix=suffix,
327
- delete=False
328
- ) as f:
329
- f.write(content)
330
- path = Path(f.name)
331
-
332
- created_files.append(path)
333
- return path
334
-
335
- yield _make_binary
336
-
337
- # Cleanup
338
- for path in created_files:
339
- path.unlink(missing_ok=True)
340
-
341
-
342
- @pytest.fixture
343
- def temp_csv_file():
344
- """
345
- Create temporary CSV files for testing.
346
-
347
- Returns:
348
- Function that creates CSV files.
349
- """
350
- import csv
351
- created_files = []
352
-
353
- def _make_csv(
354
- headers: list[str],
355
- rows: list[list],
356
- suffix: str = ".csv"
357
- ) -> Path:
358
- """
359
- Create a temporary CSV file.
360
-
361
- Args:
362
- headers: Column headers
363
- rows: Data rows
364
- suffix: File suffix
365
-
366
- Returns:
367
- Path to created CSV file
368
- """
369
- with tempfile.NamedTemporaryFile(
370
- mode='w',
371
- suffix=suffix,
372
- delete=False,
373
- newline=''
374
- ) as f:
375
- writer = csv.writer(f)
376
- writer.writerow(headers)
377
- writer.writerows(rows)
378
- path = Path(f.name)
379
-
380
- created_files.append(path)
381
- return path
382
-
383
- yield _make_csv
384
-
385
- # Cleanup
386
- for path in created_files:
387
- path.unlink(missing_ok=True)
388
-
389
-
390
- @pytest.fixture
391
- def temp_json_file():
392
- """
393
- Create temporary JSON files for testing.
394
-
395
- Returns:
396
- Function that creates JSON files.
397
- """
398
- import json
399
- created_files = []
400
-
401
- def _make_json(
402
- data: dict | list,
403
- suffix: str = ".json",
404
- indent: int = 2
405
- ) -> Path:
406
- """
407
- Create a temporary JSON file.
408
-
409
- Args:
410
- data: JSON data to write
411
- suffix: File suffix
412
- indent: JSON indentation
413
-
414
- Returns:
415
- Path to created JSON file
416
- """
417
- with tempfile.NamedTemporaryFile(
418
- mode='w',
419
- suffix=suffix,
420
- delete=False
421
- ) as f:
422
- json.dump(data, f, indent=indent)
423
- path = Path(f.name)
424
-
425
- created_files.append(path)
426
- return path
427
-
428
- yield _make_json
429
-
430
- # Cleanup
431
- for path in created_files:
432
- path.unlink(missing_ok=True)
433
-
434
-
435
- @pytest.fixture
436
- def temp_symlink():
437
- """
438
- Create temporary symbolic links for testing.
439
-
440
- Returns:
441
- Function that creates symbolic links.
442
- """
443
- created_links = []
444
-
445
- def _make_symlink(
446
- target: Path | str,
447
- link_name: Path | str = None
448
- ) -> Path:
449
- """
450
- Create a temporary symbolic link.
451
-
452
- Args:
453
- target: Target path for the symlink
454
- link_name: Optional link name (auto-generated if None)
455
-
456
- Returns:
457
- Path to created symlink
458
- """
459
- target = Path(target)
460
-
461
- if link_name is None:
462
- with tempfile.NamedTemporaryFile(delete=True) as f:
463
- link_name = Path(f.name + "_link")
464
- else:
465
- link_name = Path(link_name)
466
-
467
- link_name.symlink_to(target)
468
- created_links.append(link_name)
469
-
470
- return link_name
471
-
472
- yield _make_symlink
473
-
474
- # Cleanup
475
- for link in created_links:
476
- link.unlink(missing_ok=True)
477
-
478
-
479
- @pytest.fixture
480
- def temp_executable_file():
481
- """
482
- Create temporary executable files for testing.
483
-
484
- Returns:
485
- Function that creates executable files.
486
- """
487
- import stat
488
- created_files = []
489
-
490
- def _make_executable(
491
- content: str = "#!/bin/sh\necho 'test'\n",
492
- suffix: str = ".sh"
493
- ) -> Path:
494
- """
495
- Create a temporary executable file.
496
-
497
- Args:
498
- content: Script content
499
- suffix: File suffix
500
-
501
- Returns:
502
- Path to created executable file
503
- """
504
- with tempfile.NamedTemporaryFile(
505
- mode='w',
506
- suffix=suffix,
507
- delete=False
508
- ) as f:
509
- f.write(content)
510
- path = Path(f.name)
511
-
512
- # Make executable
513
- current = path.stat().st_mode
514
- path.chmod(current | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH)
515
-
516
- created_files.append(path)
517
- return path
518
-
519
- yield _make_executable
520
-
521
- # Cleanup
522
- for path in created_files:
523
- path.unlink(missing_ok=True)
9
+ # Re-export all fixtures from specialized modules
10
+ from provide.foundation.testing.file.content_fixtures import (
11
+ temp_binary_file,
12
+ temp_csv_file,
13
+ temp_file,
14
+ temp_file_with_content,
15
+ temp_json_file,
16
+ temp_named_file,
17
+ )
18
+ from provide.foundation.testing.file.directory_fixtures import (
19
+ empty_directory,
20
+ nested_directory_structure,
21
+ temp_directory,
22
+ test_files_structure,
23
+ )
24
+ from provide.foundation.testing.file.special_fixtures import (
25
+ binary_file,
26
+ readonly_file,
27
+ temp_executable_file,
28
+ temp_symlink,
29
+ )
30
+
31
+ __all__ = [
32
+ # Directory fixtures
33
+ "temp_directory",
34
+ "test_files_structure",
35
+ "nested_directory_structure",
36
+ "empty_directory",
37
+ # Special file fixtures
38
+ "binary_file",
39
+ "readonly_file",
40
+ "temp_symlink",
41
+ "temp_executable_file",
42
+ # Content-based fixtures
43
+ "temp_file",
44
+ "temp_named_file",
45
+ "temp_file_with_content",
46
+ "temp_binary_file",
47
+ "temp_csv_file",
48
+ "temp_json_file",
49
+ ]
@@ -0,0 +1,145 @@
1
+ """
2
+ Special file test fixtures.
3
+
4
+ Fixtures for creating specialized files like binary files, read-only files,
5
+ symbolic links, and executable files.
6
+ """
7
+
8
+ from collections.abc import Generator
9
+ from pathlib import Path
10
+ import stat
11
+ import tempfile
12
+
13
+ import pytest
14
+
15
+ from provide.foundation.file.safe import safe_delete
16
+
17
+
18
+ @pytest.fixture
19
+ def binary_file() -> Generator[Path, None, None]:
20
+ """
21
+ Create a temporary binary file for testing.
22
+
23
+ Yields:
24
+ Path to a binary file containing sample binary data.
25
+ """
26
+ with tempfile.NamedTemporaryFile(mode="wb", suffix=".bin", delete=False) as f:
27
+ # Write some binary data
28
+ f.write(b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09")
29
+ f.write(b"\xff\xfe\xfd\xfc\xfb\xfa\xf9\xf8\xf7\xf6")
30
+ path = Path(f.name)
31
+
32
+ yield path
33
+ safe_delete(path, missing_ok=True)
34
+
35
+
36
+ @pytest.fixture
37
+ def readonly_file() -> Generator[Path, None, None]:
38
+ """
39
+ Create a read-only file for permission testing.
40
+
41
+ Yields:
42
+ Path to a read-only file.
43
+ """
44
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
45
+ f.write("Read-only content")
46
+ path = Path(f.name)
47
+
48
+ # Make file read-only
49
+ path.chmod(0o444)
50
+
51
+ yield path
52
+
53
+ # Restore write permission for cleanup
54
+ path.chmod(0o644)
55
+ safe_delete(path, missing_ok=True)
56
+
57
+
58
+ @pytest.fixture
59
+ def temp_symlink():
60
+ """
61
+ Create temporary symbolic links for testing.
62
+
63
+ Returns:
64
+ Function that creates symbolic links.
65
+ """
66
+ created_links = []
67
+
68
+ def _make_symlink(target: Path | str, link_name: Path | str = None) -> Path:
69
+ """
70
+ Create a temporary symbolic link.
71
+
72
+ Args:
73
+ target: Target path for the symlink
74
+ link_name: Optional link name (auto-generated if None)
75
+
76
+ Returns:
77
+ Path to created symlink
78
+ """
79
+ target = Path(target)
80
+
81
+ if link_name is None:
82
+ with tempfile.NamedTemporaryFile(delete=True) as f:
83
+ link_name = Path(f.name + "_link")
84
+ else:
85
+ link_name = Path(link_name)
86
+
87
+ link_name.symlink_to(target)
88
+ created_links.append(link_name)
89
+
90
+ return link_name
91
+
92
+ yield _make_symlink
93
+
94
+ # Cleanup
95
+ for link in created_links:
96
+ safe_delete(link, missing_ok=True)
97
+
98
+
99
+ @pytest.fixture
100
+ def temp_executable_file():
101
+ """
102
+ Create temporary executable files for testing.
103
+
104
+ Returns:
105
+ Function that creates executable files.
106
+ """
107
+ created_files = []
108
+
109
+ def _make_executable(
110
+ content: str = "#!/bin/sh\necho 'test'\n", suffix: str = ".sh"
111
+ ) -> Path:
112
+ """
113
+ Create a temporary executable file.
114
+
115
+ Args:
116
+ content: Script content
117
+ suffix: File suffix
118
+
119
+ Returns:
120
+ Path to created executable file
121
+ """
122
+ with tempfile.NamedTemporaryFile(mode="w", suffix=suffix, delete=False) as f:
123
+ f.write(content)
124
+ path = Path(f.name)
125
+
126
+ # Make executable
127
+ current = path.stat().st_mode
128
+ path.chmod(current | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH)
129
+
130
+ created_files.append(path)
131
+ return path
132
+
133
+ yield _make_executable
134
+
135
+ # Cleanup
136
+ for path in created_files:
137
+ safe_delete(path, missing_ok=True)
138
+
139
+
140
+ __all__ = [
141
+ "binary_file",
142
+ "readonly_file",
143
+ "temp_executable_file",
144
+ "temp_symlink",
145
+ ]