s3fs 2025.9.0__py3-none-any.whl → 2025.12.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.
- s3fs/_version.py +3 -3
- s3fs/core.py +70 -32
- s3fs/errors.py +1 -1
- s3fs/utils.py +2 -2
- {s3fs-2025.9.0.dist-info → s3fs-2025.12.0.dist-info}/METADATA +4 -9
- s3fs-2025.12.0.dist-info/RECORD +11 -0
- s3fs-2025.9.0.dist-info/RECORD +0 -11
- {s3fs-2025.9.0.dist-info → s3fs-2025.12.0.dist-info}/WHEEL +0 -0
- {s3fs-2025.9.0.dist-info → s3fs-2025.12.0.dist-info}/licenses/LICENSE.txt +0 -0
- {s3fs-2025.9.0.dist-info → s3fs-2025.12.0.dist-info}/top_level.txt +0 -0
s3fs/_version.py
CHANGED
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
|
|
9
9
|
version_json = '''
|
|
10
10
|
{
|
|
11
|
-
"date": "2025-
|
|
11
|
+
"date": "2025-12-03T10:32:02-0500",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "2025.
|
|
14
|
+
"full-revisionid": "65f394575b9667f33b59473dc28a8f1cf6708745",
|
|
15
|
+
"version": "2025.12.0"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
s3fs/core.py
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
1
|
import asyncio
|
|
3
2
|
import errno
|
|
4
3
|
import io
|
|
5
4
|
import logging
|
|
5
|
+
import math
|
|
6
6
|
import mimetypes
|
|
7
7
|
import os
|
|
8
8
|
import socket
|
|
9
|
-
from typing import Tuple, Optional
|
|
10
9
|
import weakref
|
|
11
10
|
import re
|
|
12
11
|
|
|
@@ -69,6 +68,8 @@ S3_RETRYABLE_ERRORS = (
|
|
|
69
68
|
ResponseParserError,
|
|
70
69
|
)
|
|
71
70
|
|
|
71
|
+
MAX_UPLOAD_PARTS = 10_000 # maximum number of parts for S3 multipart upload
|
|
72
|
+
|
|
72
73
|
if ClientPayloadError is not None:
|
|
73
74
|
S3_RETRYABLE_ERRORS += (ClientPayloadError,)
|
|
74
75
|
|
|
@@ -166,7 +167,7 @@ def _coalesce_version_id(*args):
|
|
|
166
167
|
if len(version_ids) > 1:
|
|
167
168
|
raise ValueError(
|
|
168
169
|
"Cannot coalesce version_ids where more than one are defined,"
|
|
169
|
-
" {}"
|
|
170
|
+
f" {version_ids}"
|
|
170
171
|
)
|
|
171
172
|
elif len(version_ids) == 0:
|
|
172
173
|
return None
|
|
@@ -174,6 +175,18 @@ def _coalesce_version_id(*args):
|
|
|
174
175
|
return version_ids.pop()
|
|
175
176
|
|
|
176
177
|
|
|
178
|
+
def calculate_chunksize(filesize, chunksize=None, max_parts=MAX_UPLOAD_PARTS) -> int:
|
|
179
|
+
if chunksize is None:
|
|
180
|
+
chunksize = 50 * 2**20 # default chunksize set to 50 MiB
|
|
181
|
+
required_chunks = math.ceil(filesize / chunksize)
|
|
182
|
+
# increase chunksize to fit within the max_parts limit
|
|
183
|
+
if required_chunks > max_parts:
|
|
184
|
+
# S3 supports uploading objects up to 5 TiB in size,
|
|
185
|
+
# so each chunk can be up to ~524 MiB.
|
|
186
|
+
chunksize = math.ceil(filesize / max_parts)
|
|
187
|
+
return chunksize
|
|
188
|
+
|
|
189
|
+
|
|
177
190
|
class S3FileSystem(AsyncFileSystem):
|
|
178
191
|
"""
|
|
179
192
|
Access S3 as if it were a file system.
|
|
@@ -440,7 +453,7 @@ class S3FileSystem(AsyncFileSystem):
|
|
|
440
453
|
s3_key = s3_components[1]
|
|
441
454
|
return bucket, s3_key
|
|
442
455
|
|
|
443
|
-
def split_path(self, path) ->
|
|
456
|
+
def split_path(self, path) -> tuple[str, str, str | None]:
|
|
444
457
|
"""
|
|
445
458
|
Normalise S3 path string into bucket and key.
|
|
446
459
|
|
|
@@ -764,6 +777,7 @@ class S3FileSystem(AsyncFileSystem):
|
|
|
764
777
|
else:
|
|
765
778
|
files.append(c)
|
|
766
779
|
files += dirs
|
|
780
|
+
files.sort(key=lambda f: f["name"])
|
|
767
781
|
except ClientError as e:
|
|
768
782
|
raise translate_boto_error(e)
|
|
769
783
|
|
|
@@ -887,38 +901,49 @@ class S3FileSystem(AsyncFileSystem):
|
|
|
887
901
|
sdirs = set()
|
|
888
902
|
thisdircache = {}
|
|
889
903
|
for o in out:
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
904
|
+
# not self._parent, because that strips "/" from placeholders
|
|
905
|
+
par = o["name"].rsplit("/", maxsplit=1)[0]
|
|
906
|
+
o["Key"] = o["name"]
|
|
907
|
+
name = o["name"]
|
|
908
|
+
while "/" in par:
|
|
909
|
+
if par not in sdirs:
|
|
910
|
+
sdirs.add(par)
|
|
911
|
+
d = False
|
|
912
|
+
if len(path) <= len(par):
|
|
913
|
+
d = {
|
|
914
|
+
"Key": par,
|
|
915
|
+
"Size": 0,
|
|
916
|
+
"name": par,
|
|
917
|
+
"StorageClass": "DIRECTORY",
|
|
918
|
+
"type": "directory",
|
|
919
|
+
"size": 0,
|
|
920
|
+
}
|
|
921
|
+
dirs.append(d)
|
|
922
|
+
thisdircache[par] = []
|
|
923
|
+
ppar = self._parent(par)
|
|
924
|
+
if ppar in thisdircache:
|
|
925
|
+
if d and d not in thisdircache[ppar]:
|
|
926
|
+
thisdircache[ppar].append(d)
|
|
927
|
+
if par in sdirs and not name.endswith("/"):
|
|
928
|
+
# exclude placeholdees, they do not belong in the directory listing
|
|
929
|
+
thisdircache[par].append(o)
|
|
930
|
+
par, name, o = par.rsplit("/", maxsplit=1)[0], par, d
|
|
931
|
+
if par in thisdircache or par in self.dircache:
|
|
932
|
+
break
|
|
911
933
|
|
|
912
934
|
# Explicitly add directories to their parents in the dircache
|
|
913
935
|
for d in dirs:
|
|
914
936
|
par = self._parent(d["name"])
|
|
915
|
-
|
|
937
|
+
# extra condition here (in any()) to deal with directory-marking files
|
|
938
|
+
if par in thisdircache and not any(
|
|
939
|
+
_["name"] == d["name"] for _ in thisdircache[par]
|
|
940
|
+
):
|
|
916
941
|
thisdircache[par].append(d)
|
|
917
942
|
|
|
918
943
|
if not prefix:
|
|
919
944
|
for k, v in thisdircache.items():
|
|
920
945
|
if k not in self.dircache and len(k) >= len(path):
|
|
921
|
-
self.dircache[k] = v
|
|
946
|
+
self.dircache[k] = sorted(v, key=lambda x: x["name"])
|
|
922
947
|
if withdirs:
|
|
923
948
|
out = sorted(out + dirs, key=lambda x: x["name"])
|
|
924
949
|
if detail:
|
|
@@ -1043,7 +1068,7 @@ class S3FileSystem(AsyncFileSystem):
|
|
|
1043
1068
|
files = await self._lsdir(
|
|
1044
1069
|
self._parent(path), refresh=refresh, versions=versions
|
|
1045
1070
|
)
|
|
1046
|
-
except
|
|
1071
|
+
except OSError:
|
|
1047
1072
|
pass
|
|
1048
1073
|
files = [
|
|
1049
1074
|
o
|
|
@@ -1230,7 +1255,7 @@ class S3FileSystem(AsyncFileSystem):
|
|
|
1230
1255
|
lpath,
|
|
1231
1256
|
rpath,
|
|
1232
1257
|
callback=_DEFAULT_CALLBACK,
|
|
1233
|
-
chunksize=
|
|
1258
|
+
chunksize=None,
|
|
1234
1259
|
max_concurrency=None,
|
|
1235
1260
|
mode="overwrite",
|
|
1236
1261
|
**kwargs,
|
|
@@ -1258,6 +1283,7 @@ class S3FileSystem(AsyncFileSystem):
|
|
|
1258
1283
|
if content_type is not None:
|
|
1259
1284
|
kwargs["ContentType"] = content_type
|
|
1260
1285
|
|
|
1286
|
+
chunksize = calculate_chunksize(size, chunksize=chunksize)
|
|
1261
1287
|
with open(lpath, "rb") as f0:
|
|
1262
1288
|
if size < min(5 * 2**30, 2 * chunksize):
|
|
1263
1289
|
chunk = f0.read()
|
|
@@ -1276,8 +1302,8 @@ class S3FileSystem(AsyncFileSystem):
|
|
|
1276
1302
|
key,
|
|
1277
1303
|
mpu,
|
|
1278
1304
|
f0,
|
|
1305
|
+
chunksize,
|
|
1279
1306
|
callback=callback,
|
|
1280
|
-
chunksize=chunksize,
|
|
1281
1307
|
max_concurrency=max_concurrency,
|
|
1282
1308
|
)
|
|
1283
1309
|
parts = [
|
|
@@ -1305,8 +1331,8 @@ class S3FileSystem(AsyncFileSystem):
|
|
|
1305
1331
|
key,
|
|
1306
1332
|
mpu,
|
|
1307
1333
|
f0,
|
|
1334
|
+
chunksize,
|
|
1308
1335
|
callback=_DEFAULT_CALLBACK,
|
|
1309
|
-
chunksize=50 * 2**20,
|
|
1310
1336
|
max_concurrency=None,
|
|
1311
1337
|
):
|
|
1312
1338
|
max_concurrency = max_concurrency or self.max_concurrency
|
|
@@ -1464,6 +1490,18 @@ class S3FileSystem(AsyncFileSystem):
|
|
|
1464
1490
|
pass
|
|
1465
1491
|
except ClientError as e:
|
|
1466
1492
|
raise translate_boto_error(e, set_cause=False)
|
|
1493
|
+
else:
|
|
1494
|
+
try:
|
|
1495
|
+
out = await self._call_s3("head_bucket", Bucket=bucket, **self.req_kw)
|
|
1496
|
+
return {
|
|
1497
|
+
"name": bucket,
|
|
1498
|
+
"type": "directory",
|
|
1499
|
+
"size": 0,
|
|
1500
|
+
"StorageClass": "DIRECTORY",
|
|
1501
|
+
"VersionId": out.get("VersionId"),
|
|
1502
|
+
}
|
|
1503
|
+
except ClientError as e:
|
|
1504
|
+
raise translate_boto_error(e, set_cause=False)
|
|
1467
1505
|
|
|
1468
1506
|
try:
|
|
1469
1507
|
# We check to see if the path is a directory by attempting to list its
|
|
@@ -2128,7 +2166,7 @@ class S3FileSystem(AsyncFileSystem):
|
|
|
2128
2166
|
path = self._parent(path)
|
|
2129
2167
|
|
|
2130
2168
|
async def _walk(self, path, maxdepth=None, **kwargs):
|
|
2131
|
-
if path in ["", "*"] + ["{}://"
|
|
2169
|
+
if path in ["", "*"] + [f"{p}://" for p in self.protocol]:
|
|
2132
2170
|
raise ValueError("Cannot crawl all of S3")
|
|
2133
2171
|
async for _ in super()._walk(path, maxdepth=maxdepth, **kwargs):
|
|
2134
2172
|
yield _
|
s3fs/errors.py
CHANGED
|
@@ -155,7 +155,7 @@ def translate_boto_error(error, message=None, set_cause=True, *args, **kwargs):
|
|
|
155
155
|
custom_exc = constructor(message, *args, **kwargs)
|
|
156
156
|
else:
|
|
157
157
|
# No match found, wrap this in an IOError with the appropriate message.
|
|
158
|
-
custom_exc =
|
|
158
|
+
custom_exc = OSError(errno.EIO, message or str(error), *args)
|
|
159
159
|
|
|
160
160
|
if set_cause:
|
|
161
161
|
custom_exc.__cause__ = error
|
s3fs/utils.py
CHANGED
|
@@ -118,7 +118,7 @@ def title_case(string):
|
|
|
118
118
|
return "".join(x.capitalize() for x in string.split("_"))
|
|
119
119
|
|
|
120
120
|
|
|
121
|
-
class ParamKwargsHelper
|
|
121
|
+
class ParamKwargsHelper:
|
|
122
122
|
"""
|
|
123
123
|
Utility class to help extract the subset of keys that an s3 method is
|
|
124
124
|
actually using
|
|
@@ -152,7 +152,7 @@ class ParamKwargsHelper(object):
|
|
|
152
152
|
return {k: v for k, v in d.items() if k in valid_keys}
|
|
153
153
|
|
|
154
154
|
|
|
155
|
-
class SSEParams
|
|
155
|
+
class SSEParams:
|
|
156
156
|
def __init__(
|
|
157
157
|
self,
|
|
158
158
|
server_side_encryption=None,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: s3fs
|
|
3
|
-
Version: 2025.
|
|
3
|
+
Version: 2025.12.0
|
|
4
4
|
Summary: Convenient Filesystem interface over S3
|
|
5
5
|
Home-page: http://github.com/fsspec/s3fs/
|
|
6
6
|
Maintainer: Martin Durant
|
|
@@ -11,21 +11,17 @@ Classifier: Development Status :: 4 - Beta
|
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
12
|
Classifier: License :: OSI Approved :: BSD License
|
|
13
13
|
Classifier: Operating System :: OS Independent
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.10
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
-
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Requires-Python: >= 3.10
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
21
|
License-File: LICENSE.txt
|
|
22
22
|
Requires-Dist: aiobotocore<3.0.0,>=2.5.4
|
|
23
|
-
Requires-Dist: fsspec==2025.
|
|
23
|
+
Requires-Dist: fsspec==2025.12.0
|
|
24
24
|
Requires-Dist: aiohttp!=4.0.0a0,!=4.0.0a1
|
|
25
|
-
Provides-Extra: awscli
|
|
26
|
-
Requires-Dist: aiobotocore[awscli]<3.0.0,>=2.5.4; extra == "awscli"
|
|
27
|
-
Provides-Extra: boto3
|
|
28
|
-
Requires-Dist: aiobotocore[boto3]<3.0.0,>=2.5.4; extra == "boto3"
|
|
29
25
|
Dynamic: classifier
|
|
30
26
|
Dynamic: description
|
|
31
27
|
Dynamic: description-content-type
|
|
@@ -35,7 +31,6 @@ Dynamic: license
|
|
|
35
31
|
Dynamic: license-file
|
|
36
32
|
Dynamic: maintainer
|
|
37
33
|
Dynamic: maintainer-email
|
|
38
|
-
Dynamic: provides-extra
|
|
39
34
|
Dynamic: requires-dist
|
|
40
35
|
Dynamic: requires-python
|
|
41
36
|
Dynamic: summary
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
s3fs/__init__.py,sha256=_6_Vs_vblhJSJw-62JVIBIM8kTKLhuwPFAIvt3hanls,160
|
|
2
|
+
s3fs/_version.py,sha256=XmBtxzsWJ-pBXPcK4HgA6ctXPj2yuE1Fa8aq9prxaT8,501
|
|
3
|
+
s3fs/core.py,sha256=XuAkb1H4ACeNNG_hPdL1Gu3oiQZZzeP0wRnE8BihaOY,93419
|
|
4
|
+
s3fs/errors.py,sha256=dhdN1nTI786q6Gy1hqqdfidcFQ5qeYaYwYSUjxYcDxg,7961
|
|
5
|
+
s3fs/mapping.py,sha256=FoqEdMne7LXUL4HgPN4j6WsMsrwxpb53GynDhXs9VRI,237
|
|
6
|
+
s3fs/utils.py,sha256=VvGSdzmgAsqZIOLU7MKtv0oEClm3YZQ78f4PMhBAX9E,5209
|
|
7
|
+
s3fs-2025.12.0.dist-info/licenses/LICENSE.txt,sha256=3DWZ-ma8G8_6I2g4vi-04V-EzkgBJE7sk9kOUVz8WNE,1505
|
|
8
|
+
s3fs-2025.12.0.dist-info/METADATA,sha256=hNXXt7WgQ8Fuk94tIsE2kHH1Zkis8-RvzrsrEmplLJk,1159
|
|
9
|
+
s3fs-2025.12.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
10
|
+
s3fs-2025.12.0.dist-info/top_level.txt,sha256=Lf6EI3TdjlPu7TN-92IIY6c-GdrHnkrKGb1n0iwKooI,5
|
|
11
|
+
s3fs-2025.12.0.dist-info/RECORD,,
|
s3fs-2025.9.0.dist-info/RECORD
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
s3fs/__init__.py,sha256=_6_Vs_vblhJSJw-62JVIBIM8kTKLhuwPFAIvt3hanls,160
|
|
2
|
-
s3fs/_version.py,sha256=gNVM-2xObjX4jpfgf_pafCudQXOz_08hacWkq2q4qTE,500
|
|
3
|
-
s3fs/core.py,sha256=qNTVMJfDF4sbaJmXZoTSB4yiLcmwDeVLPvn3ynsCjyI,91585
|
|
4
|
-
s3fs/errors.py,sha256=GepxwhJMNrCVKMWVE7WzEfuLMU62pibcD9xlr7RegSg,7961
|
|
5
|
-
s3fs/mapping.py,sha256=FoqEdMne7LXUL4HgPN4j6WsMsrwxpb53GynDhXs9VRI,237
|
|
6
|
-
s3fs/utils.py,sha256=33lK0sBH7uXTFnbO9gHjONn3RgF555koyVleK7EITlQ,5225
|
|
7
|
-
s3fs-2025.9.0.dist-info/licenses/LICENSE.txt,sha256=3DWZ-ma8G8_6I2g4vi-04V-EzkgBJE7sk9kOUVz8WNE,1505
|
|
8
|
-
s3fs-2025.9.0.dist-info/METADATA,sha256=LfrM7e2PCGpw7EkOyQ298FRRwXmf385pIKL1lLQCwXg,1358
|
|
9
|
-
s3fs-2025.9.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
10
|
-
s3fs-2025.9.0.dist-info/top_level.txt,sha256=Lf6EI3TdjlPu7TN-92IIY6c-GdrHnkrKGb1n0iwKooI,5
|
|
11
|
-
s3fs-2025.9.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|