Skip to content

Commit 98b01ad

Browse files
Merge pull request #10 from kerthcet/feat/add-oss-s3
Support download_dir
2 parents 6ed04ec + 5e475fb commit 98b01ad

5 files changed

Lines changed: 106 additions & 40 deletions

File tree

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,24 @@ client = StoreFactory.new_client(
2222
provider=<provider>, endpoint=<endpoint>, bucket=<bucket>
2323
)
2424

25+
# Create a directory
26+
client.create_dir(dir_name)
27+
28+
# Delete a directory with all its files
29+
client.delete_dir(dir_name)
30+
2531
# Upload
2632
client.upload(src, dest)
2733

34+
# Upload a directory with all its files
35+
client.upload_dir(src_dir, dest_dir)
36+
2837
# Download
2938
client.download(src, dest)
3039

40+
# Download a directory with all its files
41+
client.download_dir(src_dir, dest_dir)
42+
3143
# Exists
3244
client.exists(filename)
3345

omnistore/objstore/aliyun_oss.py

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
from operator import truediv
2+
from pathlib import Path
3+
14
import oss2
25
from oss2.credentials import EnvironmentVariableCredentialsProvider
36

@@ -6,44 +9,53 @@
69

710
class OSS(ObjStore):
811
def __init__(self, endpoint: str, bucket: str):
9-
"""
10-
Construct a new client to communicate with the provider.
11-
"""
12-
1312
# Make sure environments OSS_ACCESS_KEY_ID and OSS_ACCESS_KEY_SECRET are exist.
1413
auth = oss2.ProviderAuth(EnvironmentVariableCredentialsProvider())
1514
self._bucket = oss2.Bucket(auth, endpoint, bucket)
1615

17-
def upload(self, src: str, dest: str):
18-
"""
19-
Upload will upload the obj to the provider.
20-
"""
16+
def create_dir(self, dirname: str):
17+
self._bucket.put_object(dirname, "")
18+
19+
def delete_dir(self, dirname: str):
20+
for obj in oss2.ObjectIterator(self._bucket, prefix=dirname):
21+
self._bucket.delete_object(obj.key)
2122

23+
def upload(self, src: str, dest: str):
2224
oss2.resumable_upload(self._bucket, dest, src)
2325

24-
def download(self, src: str, dest: str):
25-
"""
26-
Download will download the required obj from the provider.
27-
"""
26+
def upload_dir(self, src_dir: str, dest_dir: str):
27+
for file in Path(src_dir).rglob("*"):
28+
if file.is_file():
29+
self.upload(file, dest_dir + "/" + file.name)
30+
continue
2831

32+
if file.is_dir():
33+
# TODO: Support uploading subdirectory.
34+
print("Don't support uploading subdirectory yet")
35+
36+
def download(self, src: str, dest: str):
2937
oss2.resumable_download(self._bucket, src, dest)
3038

31-
def delete(self, filename: str):
32-
"""
33-
Delete will delete the obj from the provider.
34-
"""
39+
def download_dir(self, src_dir: str, dest_dir: str):
40+
if not src_dir.endswith("/"):
41+
src_dir += "/"
42+
43+
if not dest_dir.endswith("/"):
44+
dest_dir += "/"
45+
46+
path = Path(dest_dir)
47+
if not path.exists():
48+
path.mkdir(parents=True, exist_ok=True)
49+
50+
for obj in oss2.ObjectIterator(self._bucket, prefix=src_dir, delimiter="/"):
51+
if obj.is_prefix(): # If this is a folder
52+
# TODO: This is enough for download models, but not enough for general download usage.
53+
print(f"Don't support downloading subdirectory: {obj.key} yet")
54+
else: # If this is a file
55+
self.download(obj.key, dest_dir + obj.key.split("/")[-1])
3556

57+
def delete(self, filename: str):
3658
return self._bucket.delete_object(filename)
3759

3860
def exists(self, filename: str):
39-
"""
40-
Exists checks whether the obj exists in the provider.
41-
"""
42-
4361
return self._bucket.object_exists(filename)
44-
45-
def accessible(self) -> bool:
46-
"""
47-
Accessible checks whether the obj is visitable.
48-
"""
49-
raise NotImplementedError("OSS not implemented")

omnistore/store.py

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,58 @@
22

33

44
class Store(ABC):
5+
@abstractmethod
6+
def create_dir(self, dirname: str):
7+
"""
8+
create_dir will create a dir in the provider.
9+
"""
10+
pass
11+
12+
@abstractmethod
13+
def delete_dir(self, dirname: str):
14+
"""
15+
delete_dir will delete a dir from the provider, together with all the files under the directory.
16+
"""
17+
pass
18+
519
@abstractmethod
620
def upload(self, src: str, dest: str):
721
"""
8-
Upload will upload the obj to the provider.
22+
upload will upload the file from src to dest, both parameters are file names.
23+
"""
24+
pass
25+
26+
@abstractmethod
27+
def upload_dir(self, src_dir: str, dest_dir: str):
28+
"""
29+
upload_dir will upload the folder from src_dir to dest_dir, both parameters are folder names.
930
"""
1031
pass
1132

1233
@abstractmethod
1334
def download(self, src: str, dest: str):
1435
"""
15-
download will download the required obj from the provider.
16-
Non-null callback will be invocated when download finished.
36+
download will download the file from src to dest, both parameters are file names.
1737
"""
1838
pass
1939

2040
@abstractmethod
21-
def delete(self, filename: str):
41+
def download_dir(self, src_dir: str, dest_dir: str):
2242
"""
23-
Delete will delete the obj from the provider.
43+
download_dir will download the folder from src_dir to dest_dir, both parameters are folder names.
2444
"""
2545
pass
2646

2747
@abstractmethod
28-
def exists(self, filename: str):
48+
def delete(self, filename: str):
2949
"""
30-
Exists checks whether the obj exists in the provider.
50+
delete will delete the file from the provider.
3151
"""
3252
pass
3353

3454
@abstractmethod
35-
def accessible(self) -> bool:
55+
def exists(self, filename: str):
3656
"""
37-
Accessible checks whether the obj is visitable.
57+
exists checks whether the file exists in the provider.
3858
"""
3959
pass

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "omnistore"
3-
version = "0.0.2"
3+
version = "0.0.3"
44
description = "An unified python client to communicate with various kinds of object-store providers."
55
authors = ["InftyAI Community"]
66
license = "MIT"

tests/integration_tests/objstore/test_aliyun_oss.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,12 @@ def setup_and_teardown(self):
1515
except Exception as e:
1616
print(f"An error occurred: {e}")
1717

18-
with open("./test-tmp/foo.txt", "w") as file:
19-
file.write("test")
20-
2118
yield
2219

2320
print("Tearing down the test environment.")
2421
shutil.rmtree("./test-tmp")
2522

26-
def test_oss_operations(self):
23+
def test_upload_and_download_files(self):
2724
endpoint = os.getenv("ENDPOINT")
2825
bucket = os.getenv("BUCKET")
2926

@@ -32,6 +29,9 @@ def test_oss_operations(self):
3229
)
3330
assert False == client.exists("foo.txt")
3431

32+
with open("./test-tmp/foo.txt", "w") as file:
33+
file.write("test")
34+
3535
client.upload("./test-tmp/foo.txt", "foo.txt")
3636
assert True == client.exists("foo.txt")
3737

@@ -40,3 +40,25 @@ def test_oss_operations(self):
4040

4141
client.delete("foo.txt")
4242
assert False == client.exists("foo.txt")
43+
44+
def test_upload_and_download_dir(self):
45+
endpoint = os.getenv("ENDPOINT")
46+
bucket = os.getenv("BUCKET")
47+
48+
client = StoreFactory.new_client(
49+
provider=OBJECT_STORE_OSS, endpoint=endpoint, bucket=bucket
50+
)
51+
assert False == client.exists("/test/foo.txt")
52+
53+
os.makedirs("./test-tmp/test", exist_ok=True)
54+
with open("./test-tmp/test/foo.txt", "w") as file:
55+
file.write("test")
56+
57+
client.upload_dir("./test-tmp/test", "test")
58+
assert True == client.exists("test/foo.txt")
59+
60+
client.download_dir("test", "./test-tmp/test")
61+
assert True == os.path.exists("./test-tmp/test/foo.txt")
62+
63+
client.delete_dir("test")
64+
assert False == client.exists("test/foo.txt")

0 commit comments

Comments
 (0)