fsspec 2024.3.1__py3-none-any.whl → 2024.5.0__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 (86) hide show
  1. fsspec/__init__.py +2 -3
  2. fsspec/_version.py +14 -19
  3. fsspec/caching.py +83 -14
  4. fsspec/compression.py +1 -0
  5. fsspec/core.py +32 -8
  6. fsspec/exceptions.py +1 -0
  7. fsspec/generic.py +1 -1
  8. fsspec/gui.py +1 -1
  9. fsspec/implementations/arrow.py +0 -2
  10. fsspec/implementations/cache_mapper.py +1 -2
  11. fsspec/implementations/cache_metadata.py +7 -7
  12. fsspec/implementations/dirfs.py +2 -2
  13. fsspec/implementations/http.py +9 -9
  14. fsspec/implementations/local.py +78 -45
  15. fsspec/implementations/memory.py +9 -0
  16. fsspec/implementations/smb.py +3 -1
  17. fsspec/implementations/tests/__init__.py +0 -0
  18. fsspec/implementations/tests/cassettes/test_dbfs/test_dbfs_file_listing.yaml +112 -0
  19. fsspec/implementations/tests/cassettes/test_dbfs/test_dbfs_mkdir.yaml +582 -0
  20. fsspec/implementations/tests/cassettes/test_dbfs/test_dbfs_read_pyarrow_non_partitioned.yaml +873 -0
  21. fsspec/implementations/tests/cassettes/test_dbfs/test_dbfs_read_range.yaml +458 -0
  22. fsspec/implementations/tests/cassettes/test_dbfs/test_dbfs_read_range_chunked.yaml +1355 -0
  23. fsspec/implementations/tests/cassettes/test_dbfs/test_dbfs_write_and_read.yaml +795 -0
  24. fsspec/implementations/tests/cassettes/test_dbfs/test_dbfs_write_pyarrow_non_partitioned.yaml +613 -0
  25. fsspec/implementations/tests/conftest.py +39 -0
  26. fsspec/implementations/tests/local/__init__.py +0 -0
  27. fsspec/implementations/tests/local/local_fixtures.py +18 -0
  28. fsspec/implementations/tests/local/local_test.py +14 -0
  29. fsspec/implementations/tests/memory/__init__.py +0 -0
  30. fsspec/implementations/tests/memory/memory_fixtures.py +27 -0
  31. fsspec/implementations/tests/memory/memory_test.py +14 -0
  32. fsspec/implementations/tests/out.zip +0 -0
  33. fsspec/implementations/tests/test_archive.py +382 -0
  34. fsspec/implementations/tests/test_arrow.py +259 -0
  35. fsspec/implementations/tests/test_cached.py +1306 -0
  36. fsspec/implementations/tests/test_common.py +35 -0
  37. fsspec/implementations/tests/test_dask.py +29 -0
  38. fsspec/implementations/tests/test_data.py +20 -0
  39. fsspec/implementations/tests/test_dbfs.py +268 -0
  40. fsspec/implementations/tests/test_dirfs.py +588 -0
  41. fsspec/implementations/tests/test_ftp.py +178 -0
  42. fsspec/implementations/tests/test_git.py +76 -0
  43. fsspec/implementations/tests/test_http.py +577 -0
  44. fsspec/implementations/tests/test_jupyter.py +57 -0
  45. fsspec/implementations/tests/test_libarchive.py +33 -0
  46. fsspec/implementations/tests/test_local.py +1285 -0
  47. fsspec/implementations/tests/test_memory.py +382 -0
  48. fsspec/implementations/tests/test_reference.py +720 -0
  49. fsspec/implementations/tests/test_sftp.py +233 -0
  50. fsspec/implementations/tests/test_smb.py +139 -0
  51. fsspec/implementations/tests/test_tar.py +243 -0
  52. fsspec/implementations/tests/test_webhdfs.py +197 -0
  53. fsspec/implementations/tests/test_zip.py +134 -0
  54. fsspec/implementations/webhdfs.py +1 -3
  55. fsspec/parquet.py +0 -8
  56. fsspec/registry.py +4 -0
  57. fsspec/spec.py +21 -4
  58. fsspec/tests/__init__.py +0 -0
  59. fsspec/tests/abstract/mv.py +57 -0
  60. fsspec/tests/conftest.py +188 -0
  61. fsspec/tests/data/listing.html +1 -0
  62. fsspec/tests/test_api.py +498 -0
  63. fsspec/tests/test_async.py +230 -0
  64. fsspec/tests/test_caches.py +255 -0
  65. fsspec/tests/test_callbacks.py +89 -0
  66. fsspec/tests/test_compression.py +164 -0
  67. fsspec/tests/test_config.py +129 -0
  68. fsspec/tests/test_core.py +466 -0
  69. fsspec/tests/test_downstream.py +40 -0
  70. fsspec/tests/test_file.py +200 -0
  71. fsspec/tests/test_fuse.py +147 -0
  72. fsspec/tests/test_generic.py +90 -0
  73. fsspec/tests/test_gui.py +23 -0
  74. fsspec/tests/test_mapping.py +228 -0
  75. fsspec/tests/test_parquet.py +140 -0
  76. fsspec/tests/test_registry.py +134 -0
  77. fsspec/tests/test_spec.py +1167 -0
  78. fsspec/tests/test_utils.py +478 -0
  79. fsspec/utils.py +0 -2
  80. fsspec-2024.5.0.dist-info/METADATA +273 -0
  81. fsspec-2024.5.0.dist-info/RECORD +111 -0
  82. {fsspec-2024.3.1.dist-info → fsspec-2024.5.0.dist-info}/WHEEL +1 -2
  83. fsspec-2024.3.1.dist-info/METADATA +0 -167
  84. fsspec-2024.3.1.dist-info/RECORD +0 -54
  85. fsspec-2024.3.1.dist-info/top_level.txt +0 -1
  86. {fsspec-2024.3.1.dist-info → fsspec-2024.5.0.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,1167 @@
1
+ import glob
2
+ import json
3
+ import os
4
+ import pickle
5
+ import subprocess
6
+ import sys
7
+ from collections import defaultdict
8
+ from pathlib import Path
9
+
10
+ import numpy as np
11
+ import pytest
12
+
13
+ import fsspec
14
+ from fsspec.implementations.ftp import FTPFileSystem
15
+ from fsspec.implementations.http import HTTPFileSystem
16
+ from fsspec.implementations.local import LocalFileSystem
17
+ from fsspec.spec import AbstractBufferedFile, AbstractFileSystem
18
+
19
+ PATHS_FOR_GLOB_TESTS = (
20
+ {"name": "test0.json", "type": "file", "size": 100},
21
+ {"name": "test0.yaml", "type": "file", "size": 100},
22
+ {"name": "test0", "type": "directory", "size": 0},
23
+ {"name": "test0/test0.json", "type": "file", "size": 100},
24
+ {"name": "test0/test0.yaml", "type": "file", "size": 100},
25
+ {"name": "test0/test1", "type": "directory", "size": 0},
26
+ {"name": "test0/test1/test0.json", "type": "file", "size": 100},
27
+ {"name": "test0/test1/test0.yaml", "type": "file", "size": 100},
28
+ {"name": "test0/test1/test2", "type": "directory", "size": 0},
29
+ {"name": "test0/test1/test2/test0.json", "type": "file", "size": 100},
30
+ {"name": "test0/test1/test2/test0.yaml", "type": "file", "size": 100},
31
+ {"name": "test0/test2", "type": "directory", "size": 0},
32
+ {"name": "test0/test2/test0.json", "type": "file", "size": 100},
33
+ {"name": "test0/test2/test0.yaml", "type": "file", "size": 100},
34
+ {"name": "test0/test2/test1", "type": "directory", "size": 0},
35
+ {"name": "test0/test2/test1/test0.json", "type": "file", "size": 100},
36
+ {"name": "test0/test2/test1/test0.yaml", "type": "file", "size": 100},
37
+ {"name": "test0/test2/test1/test3", "type": "directory", "size": 0},
38
+ {"name": "test0/test2/test1/test3/test0.json", "type": "file", "size": 100},
39
+ {"name": "test0/test2/test1/test3/test0.yaml", "type": "file", "size": 100},
40
+ {"name": "test1.json", "type": "file", "size": 100},
41
+ {"name": "test1.yaml", "type": "file", "size": 100},
42
+ {"name": "test1", "type": "directory", "size": 0},
43
+ {"name": "test1/test0.json", "type": "file", "size": 100},
44
+ {"name": "test1/test0.yaml", "type": "file", "size": 100},
45
+ {"name": "test1/test0", "type": "directory", "size": 0},
46
+ {"name": "test1/test0/test0.json", "type": "file", "size": 100},
47
+ {"name": "test1/test0/test0.yaml", "type": "file", "size": 100},
48
+ {"name": "special_chars", "type": "directory", "size": 0},
49
+ {"name": "special_chars/f\\oo.txt", "type": "file", "size": 100},
50
+ {"name": "special_chars/f.oo.txt", "type": "file", "size": 100},
51
+ {"name": "special_chars/f+oo.txt", "type": "file", "size": 100},
52
+ {"name": "special_chars/f(oo.txt", "type": "file", "size": 100},
53
+ {"name": "special_chars/f)oo.txt", "type": "file", "size": 100},
54
+ {"name": "special_chars/f|oo.txt", "type": "file", "size": 100},
55
+ {"name": "special_chars/f^oo.txt", "type": "file", "size": 100},
56
+ {"name": "special_chars/f$oo.txt", "type": "file", "size": 100},
57
+ {"name": "special_chars/f{oo.txt", "type": "file", "size": 100},
58
+ {"name": "special_chars/f}oo.txt", "type": "file", "size": 100},
59
+ )
60
+
61
+ GLOB_POSIX_TESTS = {
62
+ "argnames": ("path", "expected"),
63
+ "argvalues": [
64
+ ("nonexistent", []),
65
+ ("test0.json", ["test0.json"]),
66
+ ("test0", ["test0"]),
67
+ ("test0/", ["test0"]),
68
+ ("test1/test0.yaml", ["test1/test0.yaml"]),
69
+ ("test0/test[1-2]", ["test0/test1", "test0/test2"]),
70
+ ("test0/test[1-2]/", ["test0/test1", "test0/test2"]),
71
+ (
72
+ "test0/test[1-2]/*",
73
+ [
74
+ "test0/test1/test0.json",
75
+ "test0/test1/test0.yaml",
76
+ "test0/test1/test2",
77
+ "test0/test2/test0.json",
78
+ "test0/test2/test0.yaml",
79
+ "test0/test2/test1",
80
+ ],
81
+ ),
82
+ (
83
+ "test0/test[1-2]/*.[j]*",
84
+ ["test0/test1/test0.json", "test0/test2/test0.json"],
85
+ ),
86
+ ("special_chars/f\\oo.*", ["special_chars/f\\oo.txt"]),
87
+ ("special_chars/f.oo.*", ["special_chars/f.oo.txt"]),
88
+ ("special_chars/f+oo.*", ["special_chars/f+oo.txt"]),
89
+ ("special_chars/f(oo.*", ["special_chars/f(oo.txt"]),
90
+ ("special_chars/f)oo.*", ["special_chars/f)oo.txt"]),
91
+ ("special_chars/f|oo.*", ["special_chars/f|oo.txt"]),
92
+ ("special_chars/f^oo.*", ["special_chars/f^oo.txt"]),
93
+ ("special_chars/f$oo.*", ["special_chars/f$oo.txt"]),
94
+ ("special_chars/f{oo.*", ["special_chars/f{oo.txt"]),
95
+ ("special_chars/f}oo.*", ["special_chars/f}oo.txt"]),
96
+ (
97
+ "*",
98
+ [
99
+ "special_chars",
100
+ "test0.json",
101
+ "test0.yaml",
102
+ "test0",
103
+ "test1.json",
104
+ "test1.yaml",
105
+ "test1",
106
+ ],
107
+ ),
108
+ ("*.yaml", ["test0.yaml", "test1.yaml"]),
109
+ (
110
+ "**",
111
+ [
112
+ "special_chars",
113
+ "special_chars/f$oo.txt",
114
+ "special_chars/f(oo.txt",
115
+ "special_chars/f)oo.txt",
116
+ "special_chars/f+oo.txt",
117
+ "special_chars/f.oo.txt",
118
+ "special_chars/f\\oo.txt",
119
+ "special_chars/f^oo.txt",
120
+ "special_chars/f{oo.txt",
121
+ "special_chars/f|oo.txt",
122
+ "special_chars/f}oo.txt",
123
+ "test0.json",
124
+ "test0.yaml",
125
+ "test0",
126
+ "test0/test0.json",
127
+ "test0/test0.yaml",
128
+ "test0/test1",
129
+ "test0/test1/test0.json",
130
+ "test0/test1/test0.yaml",
131
+ "test0/test1/test2",
132
+ "test0/test1/test2/test0.json",
133
+ "test0/test1/test2/test0.yaml",
134
+ "test0/test2",
135
+ "test0/test2/test0.json",
136
+ "test0/test2/test0.yaml",
137
+ "test0/test2/test1",
138
+ "test0/test2/test1/test0.json",
139
+ "test0/test2/test1/test0.yaml",
140
+ "test0/test2/test1/test3",
141
+ "test0/test2/test1/test3/test0.json",
142
+ "test0/test2/test1/test3/test0.yaml",
143
+ "test1.json",
144
+ "test1.yaml",
145
+ "test1",
146
+ "test1/test0.json",
147
+ "test1/test0.yaml",
148
+ "test1/test0",
149
+ "test1/test0/test0.json",
150
+ "test1/test0/test0.yaml",
151
+ ],
152
+ ),
153
+ ("*/", ["special_chars", "test0", "test1"]),
154
+ (
155
+ "**/",
156
+ [
157
+ "special_chars",
158
+ "test0",
159
+ "test0/test1",
160
+ "test0/test1/test2",
161
+ "test0/test2",
162
+ "test0/test2/test1",
163
+ "test0/test2/test1/test3",
164
+ "test1",
165
+ "test1/test0",
166
+ ],
167
+ ),
168
+ ("*/*.yaml", ["test0/test0.yaml", "test1/test0.yaml"]),
169
+ (
170
+ "**/*.yaml",
171
+ [
172
+ "test0.yaml",
173
+ "test0/test0.yaml",
174
+ "test0/test1/test0.yaml",
175
+ "test0/test1/test2/test0.yaml",
176
+ "test0/test2/test0.yaml",
177
+ "test0/test2/test1/test0.yaml",
178
+ "test0/test2/test1/test3/test0.yaml",
179
+ "test1.yaml",
180
+ "test1/test0.yaml",
181
+ "test1/test0/test0.yaml",
182
+ ],
183
+ ),
184
+ (
185
+ "*/test1/*",
186
+ ["test0/test1/test0.json", "test0/test1/test0.yaml", "test0/test1/test2"],
187
+ ),
188
+ ("*/test1/*.yaml", ["test0/test1/test0.yaml"]),
189
+ (
190
+ "**/test1/*",
191
+ [
192
+ "test0/test1/test0.json",
193
+ "test0/test1/test0.yaml",
194
+ "test0/test1/test2",
195
+ "test0/test2/test1/test0.json",
196
+ "test0/test2/test1/test0.yaml",
197
+ "test0/test2/test1/test3",
198
+ "test1/test0.json",
199
+ "test1/test0.yaml",
200
+ "test1/test0",
201
+ ],
202
+ ),
203
+ (
204
+ "**/test1/*.yaml",
205
+ [
206
+ "test0/test1/test0.yaml",
207
+ "test0/test2/test1/test0.yaml",
208
+ "test1/test0.yaml",
209
+ ],
210
+ ),
211
+ ("*/test1/*/", ["test0/test1/test2"]),
212
+ (
213
+ "**/test1/*/",
214
+ ["test0/test1/test2", "test0/test2/test1/test3", "test1/test0"],
215
+ ),
216
+ (
217
+ "*/test1/**",
218
+ [
219
+ "test0/test1",
220
+ "test0/test1/test0.json",
221
+ "test0/test1/test0.yaml",
222
+ "test0/test1/test2",
223
+ "test0/test1/test2/test0.json",
224
+ "test0/test1/test2/test0.yaml",
225
+ ],
226
+ ),
227
+ (
228
+ "**/test1/**",
229
+ [
230
+ "test0/test1",
231
+ "test0/test1/test0.json",
232
+ "test0/test1/test0.yaml",
233
+ "test0/test1/test2",
234
+ "test0/test1/test2/test0.json",
235
+ "test0/test1/test2/test0.yaml",
236
+ "test0/test2/test1",
237
+ "test0/test2/test1/test0.json",
238
+ "test0/test2/test1/test0.yaml",
239
+ "test0/test2/test1/test3",
240
+ "test0/test2/test1/test3/test0.json",
241
+ "test0/test2/test1/test3/test0.yaml",
242
+ "test1",
243
+ "test1/test0.json",
244
+ "test1/test0.yaml",
245
+ "test1/test0",
246
+ "test1/test0/test0.json",
247
+ "test1/test0/test0.yaml",
248
+ ],
249
+ ),
250
+ ("*/test1/**/", ["test0/test1", "test0/test1/test2"]),
251
+ (
252
+ "**/test1/**/",
253
+ [
254
+ "test0/test1",
255
+ "test0/test1/test2",
256
+ "test0/test2/test1",
257
+ "test0/test2/test1/test3",
258
+ "test1",
259
+ "test1/test0",
260
+ ],
261
+ ),
262
+ (
263
+ "test0/*",
264
+ ["test0/test0.json", "test0/test0.yaml", "test0/test1", "test0/test2"],
265
+ ),
266
+ ("test0/*.yaml", ["test0/test0.yaml"]),
267
+ (
268
+ "test0/**",
269
+ [
270
+ "test0",
271
+ "test0/test0.json",
272
+ "test0/test0.yaml",
273
+ "test0/test1",
274
+ "test0/test1/test0.json",
275
+ "test0/test1/test0.yaml",
276
+ "test0/test1/test2",
277
+ "test0/test1/test2/test0.json",
278
+ "test0/test1/test2/test0.yaml",
279
+ "test0/test2",
280
+ "test0/test2/test0.json",
281
+ "test0/test2/test0.yaml",
282
+ "test0/test2/test1",
283
+ "test0/test2/test1/test0.json",
284
+ "test0/test2/test1/test0.yaml",
285
+ "test0/test2/test1/test3",
286
+ "test0/test2/test1/test3/test0.json",
287
+ "test0/test2/test1/test3/test0.yaml",
288
+ ],
289
+ ),
290
+ ("test0/*/", ["test0/test1", "test0/test2"]),
291
+ (
292
+ "test0/**/",
293
+ [
294
+ "test0",
295
+ "test0/test1",
296
+ "test0/test1/test2",
297
+ "test0/test2",
298
+ "test0/test2/test1",
299
+ "test0/test2/test1/test3",
300
+ ],
301
+ ),
302
+ ("test0/*/*.yaml", ["test0/test1/test0.yaml", "test0/test2/test0.yaml"]),
303
+ (
304
+ "test0/**/*.yaml",
305
+ [
306
+ "test0/test0.yaml",
307
+ "test0/test1/test0.yaml",
308
+ "test0/test1/test2/test0.yaml",
309
+ "test0/test2/test0.yaml",
310
+ "test0/test2/test1/test0.yaml",
311
+ "test0/test2/test1/test3/test0.yaml",
312
+ ],
313
+ ),
314
+ (
315
+ "test0/*/test1/*",
316
+ [
317
+ "test0/test2/test1/test0.json",
318
+ "test0/test2/test1/test0.yaml",
319
+ "test0/test2/test1/test3",
320
+ ],
321
+ ),
322
+ ("test0/*/test1/*.yaml", ["test0/test2/test1/test0.yaml"]),
323
+ (
324
+ "test0/**/test1/*",
325
+ [
326
+ "test0/test1/test0.json",
327
+ "test0/test1/test0.yaml",
328
+ "test0/test1/test2",
329
+ "test0/test2/test1/test0.json",
330
+ "test0/test2/test1/test0.yaml",
331
+ "test0/test2/test1/test3",
332
+ ],
333
+ ),
334
+ (
335
+ "test0/**/test1/*.yaml",
336
+ ["test0/test1/test0.yaml", "test0/test2/test1/test0.yaml"],
337
+ ),
338
+ ("test0/*/test1/*/", ["test0/test2/test1/test3"]),
339
+ ("test0/**/test1/*/", ["test0/test1/test2", "test0/test2/test1/test3"]),
340
+ (
341
+ "test0/*/test1/**",
342
+ [
343
+ "test0/test2/test1",
344
+ "test0/test2/test1/test0.json",
345
+ "test0/test2/test1/test0.yaml",
346
+ "test0/test2/test1/test3",
347
+ "test0/test2/test1/test3/test0.json",
348
+ "test0/test2/test1/test3/test0.yaml",
349
+ ],
350
+ ),
351
+ (
352
+ "test0/**/test1/**",
353
+ [
354
+ "test0/test1",
355
+ "test0/test1/test0.json",
356
+ "test0/test1/test0.yaml",
357
+ "test0/test1/test2",
358
+ "test0/test1/test2/test0.json",
359
+ "test0/test1/test2/test0.yaml",
360
+ "test0/test2/test1",
361
+ "test0/test2/test1/test0.json",
362
+ "test0/test2/test1/test0.yaml",
363
+ "test0/test2/test1/test3",
364
+ "test0/test2/test1/test3/test0.json",
365
+ "test0/test2/test1/test3/test0.yaml",
366
+ ],
367
+ ),
368
+ ("test0/*/test1/**/", ["test0/test2/test1", "test0/test2/test1/test3"]),
369
+ (
370
+ "test0/**/test1/**/",
371
+ [
372
+ "test0/test1",
373
+ "test0/test1/test2",
374
+ "test0/test2/test1",
375
+ "test0/test2/test1/test3",
376
+ ],
377
+ ),
378
+ ],
379
+ }
380
+
381
+
382
+ class DummyTestFS(AbstractFileSystem):
383
+ protocol = "mock"
384
+ _file_class = AbstractBufferedFile
385
+ _fs_contents = (
386
+ {"name": "top_level", "type": "directory"},
387
+ {"name": "top_level/second_level", "type": "directory"},
388
+ {"name": "top_level/second_level/date=2019-10-01", "type": "directory"},
389
+ {
390
+ "name": "top_level/second_level/date=2019-10-01/a.parquet",
391
+ "type": "file",
392
+ "size": 100,
393
+ },
394
+ {
395
+ "name": "top_level/second_level/date=2019-10-01/b.parquet",
396
+ "type": "file",
397
+ "size": 100,
398
+ },
399
+ {"name": "top_level/second_level/date=2019-10-02", "type": "directory"},
400
+ {
401
+ "name": "top_level/second_level/date=2019-10-02/a.parquet",
402
+ "type": "file",
403
+ "size": 100,
404
+ },
405
+ {"name": "top_level/second_level/date=2019-10-04", "type": "directory"},
406
+ {
407
+ "name": "top_level/second_level/date=2019-10-04/a.parquet",
408
+ "type": "file",
409
+ "size": 100,
410
+ },
411
+ {"name": "misc", "type": "directory"},
412
+ {"name": "misc/foo.txt", "type": "file", "size": 100},
413
+ )
414
+
415
+ def __init__(self, fs_content=None, **kwargs):
416
+ if fs_content is not None:
417
+ self._fs_contents = fs_content
418
+ super().__init__(**kwargs)
419
+
420
+ def __getitem__(self, name):
421
+ for item in self._fs_contents:
422
+ if item["name"] == name:
423
+ return item
424
+ raise IndexError(f"{name} not found!")
425
+
426
+ def ls(self, path, detail=True, refresh=True, **kwargs):
427
+ if kwargs.pop("strip_proto", True):
428
+ path = self._strip_protocol(path)
429
+
430
+ files = not refresh and self._ls_from_cache(path)
431
+ if not files:
432
+ files = [
433
+ file for file in self._fs_contents if path == self._parent(file["name"])
434
+ ]
435
+ files.sort(key=lambda file: file["name"])
436
+ self.dircache[path.rstrip("/")] = files
437
+
438
+ if detail:
439
+ return files
440
+ return [file["name"] for file in files]
441
+
442
+ @classmethod
443
+ def get_test_paths(cls, start_with=""):
444
+ """Helper to return directory and file paths with no details"""
445
+ all = [
446
+ file["name"]
447
+ for file in cls._fs_contents
448
+ if file["name"].startswith(start_with)
449
+ ]
450
+ return all
451
+
452
+ def _open(
453
+ self,
454
+ path,
455
+ mode="rb",
456
+ block_size=None,
457
+ autocommit=True,
458
+ cache_options=None,
459
+ **kwargs,
460
+ ):
461
+ return self._file_class(
462
+ self,
463
+ path,
464
+ mode,
465
+ block_size,
466
+ autocommit,
467
+ cache_options=cache_options,
468
+ **kwargs,
469
+ )
470
+
471
+
472
+ @pytest.mark.parametrize(
473
+ ["test_paths", "recursive", "maxdepth", "expected"],
474
+ [
475
+ (
476
+ (
477
+ "top_level/second_level",
478
+ "top_level/sec*",
479
+ "top_level/sec*vel",
480
+ "top_level/*",
481
+ ),
482
+ True,
483
+ None,
484
+ [
485
+ "top_level/second_level",
486
+ "top_level/second_level/date=2019-10-01",
487
+ "top_level/second_level/date=2019-10-01/a.parquet",
488
+ "top_level/second_level/date=2019-10-01/b.parquet",
489
+ "top_level/second_level/date=2019-10-02",
490
+ "top_level/second_level/date=2019-10-02/a.parquet",
491
+ "top_level/second_level/date=2019-10-04",
492
+ "top_level/second_level/date=2019-10-04/a.parquet",
493
+ ],
494
+ ),
495
+ (
496
+ (
497
+ "top_level/second_level",
498
+ "top_level/sec*",
499
+ "top_level/sec*vel",
500
+ "top_level/*",
501
+ ),
502
+ False,
503
+ None,
504
+ [
505
+ "top_level/second_level",
506
+ ],
507
+ ),
508
+ (
509
+ ("top_level/second_level",),
510
+ True,
511
+ 1,
512
+ [
513
+ "top_level/second_level",
514
+ "top_level/second_level/date=2019-10-01",
515
+ "top_level/second_level/date=2019-10-02",
516
+ "top_level/second_level/date=2019-10-04",
517
+ ],
518
+ ),
519
+ (
520
+ ("top_level/second_level",),
521
+ True,
522
+ 2,
523
+ [
524
+ "top_level/second_level",
525
+ "top_level/second_level/date=2019-10-01",
526
+ "top_level/second_level/date=2019-10-01/a.parquet",
527
+ "top_level/second_level/date=2019-10-01/b.parquet",
528
+ "top_level/second_level/date=2019-10-02",
529
+ "top_level/second_level/date=2019-10-02/a.parquet",
530
+ "top_level/second_level/date=2019-10-04",
531
+ "top_level/second_level/date=2019-10-04/a.parquet",
532
+ ],
533
+ ),
534
+ (
535
+ ("top_level/*", "top_level/sec*", "top_level/sec*vel", "top_level/*"),
536
+ True,
537
+ 1,
538
+ ["top_level/second_level"],
539
+ ),
540
+ (
541
+ ("top_level/*", "top_level/sec*", "top_level/sec*vel", "top_level/*"),
542
+ True,
543
+ 2,
544
+ [
545
+ "top_level/second_level",
546
+ "top_level/second_level/date=2019-10-01",
547
+ "top_level/second_level/date=2019-10-02",
548
+ "top_level/second_level/date=2019-10-04",
549
+ ],
550
+ ),
551
+ (
552
+ ("top_level/**",),
553
+ False,
554
+ None,
555
+ [
556
+ "top_level",
557
+ "top_level/second_level",
558
+ "top_level/second_level/date=2019-10-01",
559
+ "top_level/second_level/date=2019-10-01/a.parquet",
560
+ "top_level/second_level/date=2019-10-01/b.parquet",
561
+ "top_level/second_level/date=2019-10-02",
562
+ "top_level/second_level/date=2019-10-02/a.parquet",
563
+ "top_level/second_level/date=2019-10-04",
564
+ "top_level/second_level/date=2019-10-04/a.parquet",
565
+ ],
566
+ ),
567
+ (
568
+ ("top_level/**",),
569
+ True,
570
+ None,
571
+ [
572
+ "top_level",
573
+ "top_level/second_level",
574
+ "top_level/second_level/date=2019-10-01",
575
+ "top_level/second_level/date=2019-10-01/a.parquet",
576
+ "top_level/second_level/date=2019-10-01/b.parquet",
577
+ "top_level/second_level/date=2019-10-02",
578
+ "top_level/second_level/date=2019-10-02/a.parquet",
579
+ "top_level/second_level/date=2019-10-04",
580
+ "top_level/second_level/date=2019-10-04/a.parquet",
581
+ ],
582
+ ),
583
+ (("top_level/**",), True, 1, ["top_level", "top_level/second_level"]),
584
+ (
585
+ ("top_level/**",),
586
+ True,
587
+ 2,
588
+ [
589
+ "top_level",
590
+ "top_level/second_level",
591
+ "top_level/second_level/date=2019-10-01",
592
+ "top_level/second_level/date=2019-10-01/a.parquet",
593
+ "top_level/second_level/date=2019-10-01/b.parquet",
594
+ "top_level/second_level/date=2019-10-02",
595
+ "top_level/second_level/date=2019-10-02/a.parquet",
596
+ "top_level/second_level/date=2019-10-04",
597
+ "top_level/second_level/date=2019-10-04/a.parquet",
598
+ ],
599
+ ),
600
+ (
601
+ ("top_level/**/a.*",),
602
+ False,
603
+ None,
604
+ [
605
+ "top_level/second_level/date=2019-10-01/a.parquet",
606
+ "top_level/second_level/date=2019-10-02/a.parquet",
607
+ "top_level/second_level/date=2019-10-04/a.parquet",
608
+ ],
609
+ ),
610
+ (
611
+ ("top_level/**/a.*",),
612
+ True,
613
+ None,
614
+ [
615
+ "top_level/second_level/date=2019-10-01/a.parquet",
616
+ "top_level/second_level/date=2019-10-02/a.parquet",
617
+ "top_level/second_level/date=2019-10-04/a.parquet",
618
+ ],
619
+ ),
620
+ (
621
+ ("top_level/**/second_level/date=2019-10-02",),
622
+ False,
623
+ 2,
624
+ [
625
+ "top_level/second_level/date=2019-10-02",
626
+ ],
627
+ ),
628
+ (
629
+ ("top_level/**/second_level/date=2019-10-02",),
630
+ True,
631
+ 2,
632
+ [
633
+ "top_level/second_level/date=2019-10-02",
634
+ "top_level/second_level/date=2019-10-02/a.parquet",
635
+ ],
636
+ ),
637
+ [("misc/foo.txt", "misc/*.txt"), False, None, ["misc/foo.txt"]],
638
+ [("misc/foo.txt", "misc/*.txt"), True, None, ["misc/foo.txt"]],
639
+ (
640
+ ("",),
641
+ False,
642
+ None,
643
+ [DummyTestFS.root_marker],
644
+ ),
645
+ (
646
+ ("",),
647
+ True,
648
+ None,
649
+ DummyTestFS.get_test_paths() + [DummyTestFS.root_marker],
650
+ ),
651
+ [
652
+ (Path("misc/foo.txt"),),
653
+ False,
654
+ None,
655
+ [f"misc{os.sep}foo.txt"],
656
+ ],
657
+ ],
658
+ )
659
+ def test_expand_path(test_paths, recursive, maxdepth, expected):
660
+ """Test a number of paths and then their combination which should all yield
661
+ the same set of expanded paths"""
662
+ test_fs = DummyTestFS()
663
+
664
+ # test single query
665
+ for test_path in test_paths:
666
+ paths = test_fs.expand_path(test_path, recursive=recursive, maxdepth=maxdepth)
667
+ assert sorted(paths) == sorted(expected)
668
+
669
+ # test with all queries
670
+ paths = test_fs.expand_path(
671
+ list(test_paths), recursive=recursive, maxdepth=maxdepth
672
+ )
673
+ assert sorted(paths) == sorted(expected)
674
+
675
+
676
+ def test_expand_paths_with_wrong_args():
677
+ test_fs = DummyTestFS()
678
+
679
+ with pytest.raises(ValueError):
680
+ test_fs.expand_path("top_level", recursive=True, maxdepth=0)
681
+ with pytest.raises(ValueError):
682
+ test_fs.expand_path("top_level", maxdepth=0)
683
+ with pytest.raises(FileNotFoundError):
684
+ test_fs.expand_path("top_level/**/second_level/date=2019-10-02", maxdepth=1)
685
+ with pytest.raises(FileNotFoundError):
686
+ test_fs.expand_path("nonexistent/*")
687
+
688
+
689
+ @pytest.mark.xfail
690
+ def test_find():
691
+ """Test .find() method on debian server (ftp, https) with constant folder"""
692
+ filesystem, host, test_path = (
693
+ FTPFileSystem,
694
+ "ftp.fau.de",
695
+ "ftp://ftp.fau.de/debian-cd/current/amd64/log/success",
696
+ )
697
+ test_fs = filesystem(host)
698
+ filenames_ftp = test_fs.find(test_path)
699
+ assert filenames_ftp
700
+
701
+ filesystem, host, test_path = (
702
+ HTTPFileSystem,
703
+ "https://ftp.fau.de",
704
+ "https://ftp.fau.de/debian-cd/current/amd64/log/success",
705
+ )
706
+ test_fs = filesystem()
707
+ filenames_http = test_fs.find(test_path)
708
+ roots = [f.rsplit("/", 1)[-1] for f in filenames_http]
709
+
710
+ assert all(f.rsplit("/", 1)[-1] in roots for f in filenames_ftp)
711
+
712
+
713
+ def test_find_details():
714
+ test_fs = DummyTestFS()
715
+ filenames = test_fs.find("/")
716
+ details = test_fs.find("/", detail=True)
717
+ for filename in filenames:
718
+ assert details[filename] == test_fs.info(filename)
719
+
720
+
721
+ def test_find_file():
722
+ test_fs = DummyTestFS()
723
+
724
+ filename = "misc/foo.txt"
725
+ assert test_fs.find(filename) == [filename]
726
+ assert test_fs.find(filename, detail=True) == {filename: {}}
727
+
728
+
729
+ def test_cache():
730
+ fs = DummyTestFS()
731
+ fs2 = DummyTestFS()
732
+ assert fs is fs2
733
+
734
+ assert DummyTestFS.current() is fs
735
+ assert len(fs._cache) == 1
736
+ del fs2
737
+ assert len(fs._cache) == 1
738
+ del fs
739
+
740
+ # keeps and internal reference, doesn't get collected
741
+ assert len(DummyTestFS._cache) == 1
742
+
743
+ DummyTestFS.clear_instance_cache()
744
+ assert len(DummyTestFS._cache) == 0
745
+
746
+
747
+ def test_current():
748
+ fs = DummyTestFS()
749
+ fs2 = DummyTestFS(arg=1)
750
+
751
+ assert fs is not fs2
752
+ assert DummyTestFS.current() is fs2
753
+
754
+ DummyTestFS()
755
+ assert DummyTestFS.current() is fs
756
+
757
+
758
+ def test_alias():
759
+ with pytest.warns(FutureWarning, match="add_aliases"):
760
+ DummyTestFS(add_aliases=True)
761
+
762
+
763
+ def test_add_docs_warns():
764
+ with pytest.warns(FutureWarning, match="add_docs"):
765
+ AbstractFileSystem(add_docs=True)
766
+
767
+
768
+ def test_cache_options():
769
+ fs = DummyTestFS()
770
+ f = AbstractBufferedFile(fs, "misc/foo.txt", cache_type="bytes")
771
+ assert f.cache.trim
772
+
773
+ # TODO: dummy buffered file
774
+ f = AbstractBufferedFile(
775
+ fs, "misc/foo.txt", cache_type="bytes", cache_options={"trim": False}
776
+ )
777
+ assert f.cache.trim is False
778
+
779
+ f = fs.open("misc/foo.txt", cache_type="bytes", cache_options={"trim": False})
780
+ assert f.cache.trim is False
781
+
782
+
783
+ def test_trim_kwarg_warns():
784
+ fs = DummyTestFS()
785
+ with pytest.warns(FutureWarning, match="cache_options"):
786
+ AbstractBufferedFile(fs, "misc/foo.txt", cache_type="bytes", trim=False)
787
+
788
+
789
+ def tests_file_open_error(monkeypatch):
790
+ class InitiateError(ValueError): ...
791
+
792
+ class UploadError(ValueError): ...
793
+
794
+ class DummyBufferedFile(AbstractBufferedFile):
795
+ can_initiate = False
796
+
797
+ def _initiate_upload(self):
798
+ if not self.can_initiate:
799
+ raise InitiateError
800
+
801
+ def _upload_chunk(self, final=False):
802
+ raise UploadError
803
+
804
+ monkeypatch.setattr(DummyTestFS, "_file_class", DummyBufferedFile)
805
+
806
+ fs = DummyTestFS()
807
+ with pytest.raises(InitiateError):
808
+ with fs.open("misc/foo.txt", "wb") as stream:
809
+ stream.write(b"hello" * stream.blocksize * 2)
810
+
811
+ with pytest.raises(UploadError):
812
+ with fs.open("misc/foo.txt", "wb") as stream:
813
+ stream.can_initiate = True
814
+ stream.write(b"hello" * stream.blocksize * 2)
815
+
816
+
817
+ def test_eq():
818
+ fs = DummyTestFS()
819
+ result = fs == 1
820
+ assert result is False
821
+
822
+ f = AbstractBufferedFile(fs, "misc/foo.txt", cache_type="bytes")
823
+ result = f == 1
824
+ assert result is False
825
+
826
+
827
+ def test_pickle_multiple():
828
+ a = DummyTestFS(1)
829
+ b = DummyTestFS(2, bar=1)
830
+
831
+ x = pickle.dumps(a)
832
+ y = pickle.dumps(b)
833
+
834
+ del a, b
835
+ DummyTestFS.clear_instance_cache()
836
+
837
+ result = pickle.loads(x)
838
+ assert result.storage_args == (1,)
839
+ assert result.storage_options == {}
840
+
841
+ result = pickle.loads(y)
842
+ assert result.storage_args == (2,)
843
+ assert result.storage_options == {"bar": 1}
844
+
845
+
846
+ def test_json():
847
+ a = DummyTestFS(1)
848
+ b = DummyTestFS(2, bar=1)
849
+
850
+ outa = a.to_json()
851
+ outb = b.to_json()
852
+
853
+ assert json.loads(outb) # is valid JSON
854
+ assert a != b
855
+ assert "bar" in outb
856
+
857
+ assert DummyTestFS.from_json(outa) is a
858
+ assert DummyTestFS.from_json(outb) is b
859
+
860
+
861
+ def test_ls_from_cache():
862
+ fs = DummyTestFS()
863
+ uncached_results = fs.ls("top_level/second_level/", refresh=True)
864
+
865
+ assert fs.ls("top_level/second_level/", refresh=False) == uncached_results
866
+
867
+ # _strip_protocol removes everything by default though
868
+ # for the sake of testing the _ls_from_cache interface
869
+ # directly, we need run one time more without that call
870
+ # to actually verify that our stripping in the client
871
+ # function works.
872
+ assert (
873
+ fs.ls("top_level/second_level/", refresh=False, strip_proto=False)
874
+ == uncached_results
875
+ )
876
+
877
+
878
+ @pytest.mark.parametrize(
879
+ "dt",
880
+ [
881
+ np.int8,
882
+ np.int16,
883
+ np.int32,
884
+ np.int64,
885
+ np.uint8,
886
+ np.uint16,
887
+ np.uint32,
888
+ np.uint64,
889
+ np.float32,
890
+ np.float64,
891
+ ],
892
+ )
893
+ def test_readinto_with_numpy(tmpdir, dt):
894
+ store_path = str(tmpdir / "test_arr.npy")
895
+ arr = np.arange(10, dtype=dt)
896
+ arr.tofile(store_path)
897
+
898
+ arr2 = np.empty_like(arr)
899
+ with fsspec.open(store_path, "rb") as f:
900
+ f.readinto(arr2)
901
+
902
+ assert np.array_equal(arr, arr2)
903
+
904
+
905
+ @pytest.mark.parametrize(
906
+ "dt",
907
+ [
908
+ np.int8,
909
+ np.int16,
910
+ np.int32,
911
+ np.int64,
912
+ np.uint8,
913
+ np.uint16,
914
+ np.uint32,
915
+ np.uint64,
916
+ np.float32,
917
+ np.float64,
918
+ ],
919
+ )
920
+ def test_readinto_with_multibyte(ftp_writable, tmpdir, dt):
921
+ host, port, user, pw = ftp_writable
922
+ ftp = FTPFileSystem(host=host, port=port, username=user, password=pw)
923
+
924
+ with ftp.open("/out", "wb") as fp:
925
+ arr = np.arange(10, dtype=dt)
926
+ fp.write(arr.tobytes())
927
+
928
+ with ftp.open("/out", "rb") as fp:
929
+ arr2 = np.empty_like(arr)
930
+ fp.readinto(arr2)
931
+
932
+ assert np.array_equal(arr, arr2)
933
+
934
+
935
+ class DummyOpenFS(DummyTestFS):
936
+ blocksize = 10
937
+
938
+ def _open(self, path, mode="rb", **kwargs):
939
+ stream = open(path, mode)
940
+ stream.size = os.stat(path).st_size
941
+ return stream
942
+
943
+
944
+ class BasicCallback(fsspec.Callback):
945
+ def __init__(self, **kwargs):
946
+ self.events = []
947
+ super().__init__(**kwargs)
948
+
949
+ def set_size(self, size):
950
+ self.events.append(("set_size", size))
951
+
952
+ def relative_update(self, inc=1):
953
+ self.events.append(("relative_update", inc))
954
+
955
+
956
+ def imitate_transfer(size, chunk, *, file=True):
957
+ events = [("set_size", size)]
958
+ events.extend(("relative_update", size // chunk) for _ in range(chunk))
959
+ if file:
960
+ # The reason that there is a relative_update(0) at the
961
+ # end is that, we don't have an early exit on the
962
+ # implementations of get_file/put_file so it needs to
963
+ # go through the callback to get catch by the while's
964
+ # condition and then it will stop the transfer.
965
+ events.append(("relative_update", 0))
966
+
967
+ return events
968
+
969
+
970
+ def get_files(tmpdir, amount=10):
971
+ src, dest, base = [], [], []
972
+ for index in range(amount):
973
+ src_path = tmpdir / f"src_{index}.txt"
974
+ src_path.write_text("x" * 50, "utf-8")
975
+
976
+ src.append(str(src_path))
977
+ dest.append(str(tmpdir / f"dst_{index}.txt"))
978
+ base.append(str(tmpdir / f"file_{index}.txt"))
979
+ return src, dest, base
980
+
981
+
982
+ def test_dummy_callbacks_file(tmpdir):
983
+ fs = DummyOpenFS()
984
+ callback = BasicCallback()
985
+
986
+ file = tmpdir / "file.txt"
987
+ source = tmpdir / "tmp.txt"
988
+ destination = tmpdir / "tmp2.txt"
989
+
990
+ size = 100
991
+ source.write_text("x" * 100, "utf-8")
992
+
993
+ fs.put_file(source, file, callback=callback)
994
+
995
+ # -1 here since put_file no longer has final zero-size put
996
+ assert callback.events == imitate_transfer(size, 10)[:-1]
997
+ callback.events.clear()
998
+
999
+ fs.get_file(file, destination, callback=callback)
1000
+ assert callback.events == imitate_transfer(size, 10)
1001
+ callback.events.clear()
1002
+
1003
+ assert destination.read_text("utf-8") == "x" * 100
1004
+
1005
+
1006
+ def test_dummy_callbacks_files(tmpdir):
1007
+ fs = DummyOpenFS()
1008
+ callback = BasicCallback()
1009
+ src, dest, base = get_files(tmpdir)
1010
+
1011
+ fs.put(src, base, callback=callback)
1012
+ assert callback.events == imitate_transfer(10, 10, file=False)
1013
+ callback.events.clear()
1014
+
1015
+ fs.get(base, dest, callback=callback)
1016
+ assert callback.events == imitate_transfer(10, 10, file=False)
1017
+
1018
+
1019
+ class BranchableCallback(BasicCallback):
1020
+ def __init__(self, source, dest=None, events=None, **kwargs):
1021
+ super().__init__(**kwargs)
1022
+ if dest:
1023
+ self.key = source, dest
1024
+ else:
1025
+ self.key = (source,)
1026
+ self.events = events or defaultdict(list)
1027
+
1028
+ def branch(self, path_1, path_2, kwargs):
1029
+ from fsspec.implementations.local import make_path_posix
1030
+
1031
+ path_1 = make_path_posix(path_1)
1032
+ path_2 = make_path_posix(path_2)
1033
+ kwargs["callback"] = BranchableCallback(path_1, path_2, events=self.events)
1034
+
1035
+ def set_size(self, size):
1036
+ self.events[self.key].append(("set_size", size))
1037
+
1038
+ def relative_update(self, inc=1):
1039
+ self.events[self.key].append(("relative_update", inc))
1040
+
1041
+
1042
+ def test_dummy_callbacks_files_branched(tmpdir):
1043
+ fs = DummyOpenFS()
1044
+ src, dest, base = get_files(tmpdir)
1045
+
1046
+ callback = BranchableCallback("top-level")
1047
+
1048
+ def check_events(lpaths, rpaths):
1049
+ from fsspec.implementations.local import make_path_posix
1050
+
1051
+ base_keys = zip(make_path_posix(lpaths), make_path_posix(rpaths))
1052
+ assert set(callback.events.keys()) == {("top-level",), *base_keys}
1053
+ assert callback.events["top-level",] == imitate_transfer(10, 10, file=False)
1054
+
1055
+ for key in base_keys:
1056
+ assert callback.events[key] == imitate_transfer(50, 5)
1057
+
1058
+ fs.put(src, base, callback=callback)
1059
+ check_events(src, base)
1060
+ callback.events.clear()
1061
+
1062
+ fs.get(base, dest, callback=callback)
1063
+ check_events(base, dest)
1064
+ callback.events.clear()
1065
+
1066
+
1067
+ def _clean_paths(paths, prefix=""):
1068
+ """
1069
+ Helper to cleanup paths results by doing the following:
1070
+ - remove the prefix provided from all paths
1071
+ - remove the trailing slashes from all paths
1072
+ - remove duplicates paths
1073
+ - sort all paths
1074
+ """
1075
+ paths_list = paths
1076
+ if isinstance(paths, dict):
1077
+ paths_list = list(paths)
1078
+ paths_list = [p.replace(prefix, "").strip("/") for p in sorted(set(paths_list))]
1079
+ if isinstance(paths, dict):
1080
+ return {p: paths[p] for p in paths_list}
1081
+ return paths_list
1082
+
1083
+
1084
+ @pytest.fixture(scope="function")
1085
+ def glob_fs():
1086
+ return DummyTestFS(fs_content=PATHS_FOR_GLOB_TESTS)
1087
+
1088
+
1089
+ @pytest.fixture(scope="function")
1090
+ def glob_files_folder(tmp_path):
1091
+ local_fs = LocalFileSystem(auto_mkdir=True)
1092
+ local_fake_dir = str(tmp_path)
1093
+ for path_info in PATHS_FOR_GLOB_TESTS:
1094
+ if path_info["type"] == "file":
1095
+ local_fs.touch(path=f"{str(tmp_path)}/{path_info['name']}")
1096
+ return local_fake_dir
1097
+
1098
+
1099
+ @pytest.mark.skipif(
1100
+ sys.platform.startswith("win"),
1101
+ reason="no need to run python glob posix tests on windows",
1102
+ )
1103
+ @pytest.mark.parametrize(
1104
+ GLOB_POSIX_TESTS["argnames"],
1105
+ GLOB_POSIX_TESTS["argvalues"],
1106
+ )
1107
+ def test_posix_tests_python_glob(path, expected, glob_files_folder):
1108
+ """
1109
+ Tests against python glob to check if our posix tests are accurate.
1110
+ """
1111
+ os.chdir(glob_files_folder)
1112
+
1113
+ python_output = glob.glob(pathname=path, recursive=True)
1114
+ assert _clean_paths(python_output, glob_files_folder) == _clean_paths(expected)
1115
+
1116
+
1117
+ @pytest.mark.skipif(
1118
+ sys.platform.startswith("win"),
1119
+ reason="no need to run bash stat posix tests on windows",
1120
+ )
1121
+ @pytest.mark.parametrize(
1122
+ GLOB_POSIX_TESTS["argnames"],
1123
+ GLOB_POSIX_TESTS["argvalues"],
1124
+ )
1125
+ def test_posix_tests_bash_stat(path, expected, glob_files_folder):
1126
+ """
1127
+ Tests against bash stat to check if our posix tests are accurate.
1128
+ """
1129
+ try:
1130
+ subprocess.check_output(["bash", "-c", "shopt -s globstar"])
1131
+ except FileNotFoundError:
1132
+ pytest.skip("bash is not available")
1133
+ except subprocess.CalledProcessError:
1134
+ pytest.skip("globstar option is not available")
1135
+
1136
+ bash_path = (
1137
+ path.replace("\\", "\\\\")
1138
+ .replace("$", "\\$")
1139
+ .replace("(", "\\(")
1140
+ .replace(")", "\\)")
1141
+ .replace("|", "\\|")
1142
+ )
1143
+ bash_output = subprocess.run(
1144
+ [
1145
+ "bash",
1146
+ "-c",
1147
+ f"cd {glob_files_folder} && shopt -s globstar && stat -c %N {bash_path}",
1148
+ ],
1149
+ capture_output=True,
1150
+ check=False,
1151
+ )
1152
+ # Remove the last element always empty
1153
+ bash_output = bash_output.stdout.decode("utf-8").replace("'", "").split("\n")[:-1]
1154
+ assert _clean_paths(bash_output, glob_files_folder) == _clean_paths(expected)
1155
+
1156
+
1157
+ @pytest.mark.parametrize(
1158
+ GLOB_POSIX_TESTS["argnames"],
1159
+ GLOB_POSIX_TESTS["argvalues"],
1160
+ )
1161
+ def test_glob_posix_rules(path, expected, glob_fs):
1162
+ output = glob_fs.glob(path=f"mock://{path}")
1163
+ assert _clean_paths(output) == _clean_paths(expected)
1164
+
1165
+ detailed_output = glob_fs.glob(path=f"mock://{path}", detail=True)
1166
+ for name, info in _clean_paths(detailed_output).items():
1167
+ assert info == glob_fs[name]