cloud-files 4.25.0__tar.gz → 4.27.0__tar.gz
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.
- {cloud-files-4.25.0 → cloud-files-4.27.0}/ChangeLog +11 -0
- {cloud-files-4.25.0 → cloud-files-4.27.0}/PKG-INFO +1 -1
- {cloud-files-4.25.0 → cloud-files-4.27.0}/automated_test.py +9 -0
- {cloud-files-4.25.0 → cloud-files-4.27.0}/cloud_files.egg-info/PKG-INFO +1 -1
- cloud-files-4.27.0/cloud_files.egg-info/pbr.json +1 -0
- {cloud-files-4.25.0 → cloud-files-4.27.0}/cloudfiles/cloudfiles.py +2 -1
- {cloud-files-4.25.0 → cloud-files-4.27.0}/cloudfiles/interfaces.py +37 -8
- {cloud-files-4.25.0 → cloud-files-4.27.0}/cloudfiles/paths.py +33 -10
- {cloud-files-4.25.0 → cloud-files-4.27.0}/cloudfiles/secrets.py +18 -0
- cloud-files-4.25.0/cloud_files.egg-info/pbr.json +0 -1
- {cloud-files-4.25.0 → cloud-files-4.27.0}/.github/workflows/test-suite.yml +0 -0
- {cloud-files-4.25.0 → cloud-files-4.27.0}/AUTHORS +0 -0
- {cloud-files-4.25.0 → cloud-files-4.27.0}/LICENSE +0 -0
- {cloud-files-4.25.0 → cloud-files-4.27.0}/MANIFEST.in +0 -0
- {cloud-files-4.25.0 → cloud-files-4.27.0}/README.md +0 -0
- {cloud-files-4.25.0 → cloud-files-4.27.0}/cloud_files.egg-info/SOURCES.txt +0 -0
- {cloud-files-4.25.0 → cloud-files-4.27.0}/cloud_files.egg-info/dependency_links.txt +0 -0
- {cloud-files-4.25.0 → cloud-files-4.27.0}/cloud_files.egg-info/entry_points.txt +0 -0
- {cloud-files-4.25.0 → cloud-files-4.27.0}/cloud_files.egg-info/not-zip-safe +0 -0
- {cloud-files-4.25.0 → cloud-files-4.27.0}/cloud_files.egg-info/requires.txt +0 -0
- {cloud-files-4.25.0 → cloud-files-4.27.0}/cloud_files.egg-info/top_level.txt +0 -0
- {cloud-files-4.25.0 → cloud-files-4.27.0}/cloudfiles/__init__.py +0 -0
- {cloud-files-4.25.0 → cloud-files-4.27.0}/cloudfiles/compression.py +0 -0
- {cloud-files-4.25.0 → cloud-files-4.27.0}/cloudfiles/connectionpools.py +0 -0
- {cloud-files-4.25.0 → cloud-files-4.27.0}/cloudfiles/exceptions.py +0 -0
- {cloud-files-4.25.0 → cloud-files-4.27.0}/cloudfiles/gcs.py +0 -0
- {cloud-files-4.25.0 → cloud-files-4.27.0}/cloudfiles/lib.py +0 -0
- {cloud-files-4.25.0 → cloud-files-4.27.0}/cloudfiles/resumable_tools.py +0 -0
- {cloud-files-4.25.0 → cloud-files-4.27.0}/cloudfiles/scheduler.py +0 -0
- {cloud-files-4.25.0 → cloud-files-4.27.0}/cloudfiles/threaded_queue.py +0 -0
- {cloud-files-4.25.0 → cloud-files-4.27.0}/cloudfiles/typing.py +0 -0
- {cloud-files-4.25.0 → cloud-files-4.27.0}/cloudfiles_cli/LICENSE +0 -0
- {cloud-files-4.25.0 → cloud-files-4.27.0}/cloudfiles_cli/__init__.py +0 -0
- {cloud-files-4.25.0 → cloud-files-4.27.0}/cloudfiles_cli/cloudfiles_cli.py +0 -0
- {cloud-files-4.25.0 → cloud-files-4.27.0}/requirements.txt +0 -0
- {cloud-files-4.25.0 → cloud-files-4.27.0}/setup.cfg +0 -0
- {cloud-files-4.25.0 → cloud-files-4.27.0}/setup.py +0 -0
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
CHANGES
|
|
2
2
|
=======
|
|
3
3
|
|
|
4
|
+
4.27.0
|
|
5
|
+
------
|
|
6
|
+
|
|
7
|
+
* feat: add middleauth+https paths indicate CAVE interface (#106)
|
|
8
|
+
|
|
9
|
+
4.26.0
|
|
10
|
+
------
|
|
11
|
+
|
|
12
|
+
* feat: make it possible to normalize e.g. zarr2://./helloworld
|
|
13
|
+
* feat: add all current neuroglancer formats to parsing list
|
|
14
|
+
|
|
4
15
|
4.25.0
|
|
5
16
|
------
|
|
6
17
|
|
|
@@ -564,6 +564,15 @@ def test_path_extraction():
|
|
|
564
564
|
'a/username2/b/c/d', None, None
|
|
565
565
|
))
|
|
566
566
|
|
|
567
|
+
def test_middleauth_path_extraction():
|
|
568
|
+
import cloudfiles.paths
|
|
569
|
+
path = cloudfiles.paths.extract('middleauth+https://example.com/wow/cool/')
|
|
570
|
+
assert path.format == 'precomputed'
|
|
571
|
+
assert path.protocol == 'middleauth+https'
|
|
572
|
+
assert path.bucket is None
|
|
573
|
+
assert path.path == 'wow/cool/'
|
|
574
|
+
assert path.host == "https://example.com"
|
|
575
|
+
|
|
567
576
|
@pytest.mark.parametrize("protocol", ('mem', 'file', 's3'))
|
|
568
577
|
def test_access_non_cannonical_minimal_path(s3, protocol):
|
|
569
578
|
from cloudfiles import CloudFiles, exceptions
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"git_version": "e4b04bf", "is_release": true}
|
|
@@ -44,7 +44,7 @@ from .scheduler import schedule_jobs
|
|
|
44
44
|
from .interfaces import (
|
|
45
45
|
FileInterface, HttpInterface,
|
|
46
46
|
S3Interface, GoogleCloudStorageInterface,
|
|
47
|
-
MemoryInterface
|
|
47
|
+
MemoryInterface, CaveInterface,
|
|
48
48
|
)
|
|
49
49
|
|
|
50
50
|
INTERFACES = {
|
|
@@ -54,6 +54,7 @@ INTERFACES = {
|
|
|
54
54
|
'http': HttpInterface,
|
|
55
55
|
'https': HttpInterface,
|
|
56
56
|
'mem': MemoryInterface,
|
|
57
|
+
'middleauth+https': CaveInterface,
|
|
57
58
|
}
|
|
58
59
|
for alias in ALIASES:
|
|
59
60
|
INTERFACES[alias] = S3Interface
|
|
@@ -24,7 +24,12 @@ from .compression import COMPRESSION_TYPES
|
|
|
24
24
|
from .connectionpools import S3ConnectionPool, GCloudBucketPool, MemoryPool, MEMORY_DATA
|
|
25
25
|
from .exceptions import MD5IntegrityError, CompressionError
|
|
26
26
|
from .lib import mkdir, sip, md5, validate_s3_multipart_etag
|
|
27
|
-
from .secrets import
|
|
27
|
+
from .secrets import (
|
|
28
|
+
http_credentials,
|
|
29
|
+
cave_credentials,
|
|
30
|
+
CLOUD_FILES_DIR,
|
|
31
|
+
CLOUD_FILES_LOCK_DIR,
|
|
32
|
+
)
|
|
28
33
|
|
|
29
34
|
COMPRESSION_EXTENSIONS = ('.gz', '.br', '.zstd','.bz2','.xz')
|
|
30
35
|
GZIP_TYPES = (True, 'gzip', 1)
|
|
@@ -731,6 +736,9 @@ class HttpInterface(StorageInterface):
|
|
|
731
736
|
if secrets and 'user' in secrets and 'password' in secrets:
|
|
732
737
|
self.session.auth = (secrets['user'], secrets['password'])
|
|
733
738
|
|
|
739
|
+
def default_headers(self):
|
|
740
|
+
return {}
|
|
741
|
+
|
|
734
742
|
def get_path_to_file(self, file_path):
|
|
735
743
|
return posixpath.join(self._path.host, self._path.path, file_path)
|
|
736
744
|
|
|
@@ -749,7 +757,8 @@ class HttpInterface(StorageInterface):
|
|
|
749
757
|
@retry
|
|
750
758
|
def head(self, file_path):
|
|
751
759
|
key = self.get_path_to_file(file_path)
|
|
752
|
-
|
|
760
|
+
headers = self.default_headers()
|
|
761
|
+
with self.session.head(key, headers=headers) as resp:
|
|
753
762
|
resp.raise_for_status()
|
|
754
763
|
return resp.headers
|
|
755
764
|
|
|
@@ -761,13 +770,14 @@ class HttpInterface(StorageInterface):
|
|
|
761
770
|
def get_file(self, file_path, start=None, end=None, part_size=None):
|
|
762
771
|
key = self.get_path_to_file(file_path)
|
|
763
772
|
|
|
773
|
+
headers = self.default_headers()
|
|
764
774
|
if start is not None or end is not None:
|
|
765
775
|
start = int(start) if start is not None else 0
|
|
766
776
|
end = int(end - 1) if end is not None else ''
|
|
767
|
-
headers
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
777
|
+
headers["Range"] = f"bytes={start}-{end}"
|
|
778
|
+
|
|
779
|
+
resp = self.session.get(key, headers=headers)
|
|
780
|
+
|
|
771
781
|
if resp.status_code in (404, 403):
|
|
772
782
|
return (None, None, None, None)
|
|
773
783
|
resp.close()
|
|
@@ -788,7 +798,8 @@ class HttpInterface(StorageInterface):
|
|
|
788
798
|
@retry
|
|
789
799
|
def exists(self, file_path):
|
|
790
800
|
key = self.get_path_to_file(file_path)
|
|
791
|
-
|
|
801
|
+
headers = self.default_headers()
|
|
802
|
+
with self.session.get(key, stream=True, headers=headers) as resp:
|
|
792
803
|
return resp.ok
|
|
793
804
|
|
|
794
805
|
def files_exist(self, file_paths):
|
|
@@ -805,11 +816,15 @@ class HttpInterface(StorageInterface):
|
|
|
805
816
|
if prefix and prefix[-1] != '/':
|
|
806
817
|
prefix += '/'
|
|
807
818
|
|
|
819
|
+
headers = self.default_headers()
|
|
820
|
+
|
|
808
821
|
@retry
|
|
809
822
|
def request(token):
|
|
823
|
+
nonlocal headers
|
|
810
824
|
results = self.session.get(
|
|
811
825
|
f"https://storage.googleapis.com/storage/v1/b/{bucket}/o",
|
|
812
826
|
params={ "prefix": prefix, "pageToken": token },
|
|
827
|
+
headers=headers,
|
|
813
828
|
)
|
|
814
829
|
results.raise_for_status()
|
|
815
830
|
results.close()
|
|
@@ -832,12 +847,13 @@ class HttpInterface(StorageInterface):
|
|
|
832
847
|
baseurl = posixpath.join(self._path.host, self._path.path)
|
|
833
848
|
|
|
834
849
|
directories = ['']
|
|
850
|
+
headers = self.default_headers()
|
|
835
851
|
|
|
836
852
|
while directories:
|
|
837
853
|
directory = directories.pop()
|
|
838
854
|
url = posixpath.join(baseurl, directory)
|
|
839
855
|
|
|
840
|
-
resp = requests.get(url)
|
|
856
|
+
resp = requests.get(url, headers=headers)
|
|
841
857
|
resp.raise_for_status()
|
|
842
858
|
|
|
843
859
|
if 'text/html' not in resp.headers["Content-Type"]:
|
|
@@ -1200,3 +1216,16 @@ class S3Interface(StorageInterface):
|
|
|
1200
1216
|
with S3_BUCKET_POOL_LOCK:
|
|
1201
1217
|
pool = S3_POOL[S3ConnectionPoolParams(service, self._path.bucket, self._request_payer)]
|
|
1202
1218
|
pool.release_connection(self._conn)
|
|
1219
|
+
|
|
1220
|
+
class CaveInterface(HttpInterface):
|
|
1221
|
+
"""
|
|
1222
|
+
CAVE is an internal system that powers proofreading
|
|
1223
|
+
systems in Seung Lab. If you have no idea what this
|
|
1224
|
+
is, don't worry about it.
|
|
1225
|
+
see: https://github.com/CAVEconnectome
|
|
1226
|
+
"""
|
|
1227
|
+
def default_headers(self):
|
|
1228
|
+
cred = cave_credentials()
|
|
1229
|
+
return {
|
|
1230
|
+
"Authorization": f"Bearer {cred['token']}",
|
|
1231
|
+
}
|
|
@@ -26,10 +26,16 @@ ALIASES_FROM_FILE = None
|
|
|
26
26
|
ALIASES = {}
|
|
27
27
|
BASE_ALLOWED_PROTOCOLS = [
|
|
28
28
|
'gs', 'file', 's3',
|
|
29
|
-
'http', 'https', 'mem'
|
|
29
|
+
'http', 'https', 'mem',
|
|
30
|
+
'middleauth+https', 'ngauth+https',
|
|
30
31
|
]
|
|
31
32
|
ALLOWED_PROTOCOLS = list(BASE_ALLOWED_PROTOCOLS)
|
|
32
|
-
ALLOWED_FORMATS = [
|
|
33
|
+
ALLOWED_FORMATS = [
|
|
34
|
+
'graphene', 'precomputed', 'boss',
|
|
35
|
+
'n5', 'zarr', 'zarr2', 'zarr3',
|
|
36
|
+
'brainmaps', 'deepzoom', 'nggraph',
|
|
37
|
+
'render', 'vtk', 'nifti', 'dvid',
|
|
38
|
+
]
|
|
33
39
|
|
|
34
40
|
def update_aliases_from_file():
|
|
35
41
|
global ALIASES_FROM_FILE
|
|
@@ -64,7 +70,13 @@ def cloudpath_error(cloudpath):
|
|
|
64
70
|
def mkregexp():
|
|
65
71
|
fmt_capture = r'|'.join(ALLOWED_FORMATS)
|
|
66
72
|
fmt_capture = "(?:(?P<fmt>{})://)".format(fmt_capture)
|
|
67
|
-
|
|
73
|
+
|
|
74
|
+
allowed_protos = [
|
|
75
|
+
p.replace('+', r'\+')
|
|
76
|
+
for p in ALLOWED_PROTOCOLS
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
proto_capture = r'|'.join(allowed_protos)
|
|
68
80
|
proto_capture = "(?:(?P<proto>{})://)".format(proto_capture)
|
|
69
81
|
regexp = "{}?{}?".format(fmt_capture, proto_capture)
|
|
70
82
|
return regexp
|
|
@@ -147,11 +159,16 @@ for alias, host in OFFICIAL_ALIASES.items():
|
|
|
147
159
|
## Other Path Library Functions
|
|
148
160
|
|
|
149
161
|
def normalize(path):
|
|
150
|
-
proto =
|
|
162
|
+
fmt, proto, endpoint, cloudpath, alias = extract_format_protocol(
|
|
163
|
+
path, allow_defaults=False
|
|
164
|
+
)
|
|
165
|
+
|
|
151
166
|
if proto is None:
|
|
152
167
|
proto = "file"
|
|
153
|
-
|
|
154
|
-
|
|
168
|
+
cloudpath = toabs(cloudpath)
|
|
169
|
+
fmt = f"{fmt}://" if fmt else ""
|
|
170
|
+
path = f"{fmt}{proto}://{cloudpath}"
|
|
171
|
+
|
|
155
172
|
return path
|
|
156
173
|
|
|
157
174
|
def dirname(path):
|
|
@@ -264,7 +281,7 @@ def pop_protocol(cloudpath):
|
|
|
264
281
|
|
|
265
282
|
return (protocol, cloudpath)
|
|
266
283
|
|
|
267
|
-
def extract_format_protocol(cloudpath:str) -> tuple:
|
|
284
|
+
def extract_format_protocol(cloudpath:str, allow_defaults=True) -> tuple:
|
|
268
285
|
error = UnsupportedProtocolError(cloudpath_error(cloudpath))
|
|
269
286
|
|
|
270
287
|
alias, cloudpath = resolve_alias(cloudpath)
|
|
@@ -276,12 +293,18 @@ def extract_format_protocol(cloudpath:str) -> tuple:
|
|
|
276
293
|
groups = m.groups()
|
|
277
294
|
cloudpath = re.sub(CLOUDPATH_REGEXP, '', cloudpath, count=1)
|
|
278
295
|
|
|
279
|
-
fmt = m.group('fmt')
|
|
296
|
+
fmt = m.group('fmt')
|
|
297
|
+
if not fmt and allow_defaults:
|
|
298
|
+
fmt = 'precomputed'
|
|
280
299
|
proto = m.group('proto')
|
|
281
300
|
endpoint = None
|
|
282
301
|
|
|
283
|
-
|
|
284
|
-
|
|
302
|
+
tmp_proto = None
|
|
303
|
+
if proto is not None:
|
|
304
|
+
tmp_proto = proto.replace("middleauth+", "").replace("ngauth+", "")
|
|
305
|
+
|
|
306
|
+
if tmp_proto in ('http', 'https'):
|
|
307
|
+
cloudpath = tmp_proto + "://" + cloudpath
|
|
285
308
|
parse = urllib.parse.urlparse(cloudpath)
|
|
286
309
|
endpoint = parse.scheme + "://" + parse.netloc
|
|
287
310
|
cloudpath = cloudpath.replace(endpoint, '', 1)
|
|
@@ -137,6 +137,24 @@ def aws_credentials(bucket = '', service = 'aws', skip_files=False):
|
|
|
137
137
|
AWS_CREDENTIALS_CACHE[service][bucket] = aws_credentials
|
|
138
138
|
return aws_credentials
|
|
139
139
|
|
|
140
|
+
CAVE_CREDENTIALS = None
|
|
141
|
+
def cave_credentials():
|
|
142
|
+
global CAVE_CREDENTIALS
|
|
143
|
+
default_file_path = 'cave-secret.json'
|
|
144
|
+
path = secretpath(default_file_path)
|
|
145
|
+
|
|
146
|
+
if CAVE_CREDENTIALS:
|
|
147
|
+
return CAVE_CREDENTIALS
|
|
148
|
+
|
|
149
|
+
if os.path.exists(path):
|
|
150
|
+
with open(path, 'rt') as f:
|
|
151
|
+
CAVE_CREDENTIALS = json.loads(f.read())
|
|
152
|
+
else:
|
|
153
|
+
CAVE_CREDENTIALS = None
|
|
154
|
+
|
|
155
|
+
return CAVE_CREDENTIALS
|
|
156
|
+
|
|
157
|
+
|
|
140
158
|
HTTP_CREDENTIALS = None
|
|
141
159
|
def http_credentials():
|
|
142
160
|
global HTTP_CREDENTIALS
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"git_version": "7dcc163", "is_release": true}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|