cloudtoolbox 0.2.1__tar.gz → 0.4.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.
- {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0}/LICENSE +1 -1
- {cloudtoolbox-0.2.1/cloudtoolbox.egg-info → cloudtoolbox-0.4.0}/PKG-INFO +1 -1
- cloudtoolbox-0.4.0/cloud/amazon/s3.py +40 -0
- {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0}/cloud/amazon/sns.py +3 -2
- {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0}/cloud/amazon/sqs.py +3 -2
- cloudtoolbox-0.4.0/cloud/factory.py +53 -0
- {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0}/cloud/google/pubsub.py +2 -2
- cloudtoolbox-0.4.0/cloud/google/storage.py +42 -0
- cloudtoolbox-0.4.0/cloud/protocols.py +28 -0
- {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0/cloudtoolbox.egg-info}/PKG-INFO +1 -1
- {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0}/pyproject.toml +6 -16
- {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0}/tests/test_factory.py +60 -1
- cloudtoolbox-0.2.1/cloud/amazon/s3.py +0 -11
- cloudtoolbox-0.2.1/cloud/factory.py +0 -29
- cloudtoolbox-0.2.1/cloud/google/storage.py +0 -13
- cloudtoolbox-0.2.1/cloud/protocols.py +0 -11
- {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0}/README.md +0 -0
- {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0}/cloud/__init__.py +0 -0
- {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0}/cloud/amazon/__init__.py +0 -0
- {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0}/cloud/google/__init__.py +0 -0
- {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0}/cloudtoolbox.egg-info/SOURCES.txt +0 -0
- {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0}/cloudtoolbox.egg-info/dependency_links.txt +0 -0
- {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0}/cloudtoolbox.egg-info/requires.txt +0 -0
- {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0}/cloudtoolbox.egg-info/top_level.txt +0 -0
- {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0}/setup.cfg +0 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import boto3
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Client:
|
|
7
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
8
|
+
self.client = boto3.client("s3", *args, **kwargs)
|
|
9
|
+
|
|
10
|
+
def upload(self, bucket_name: str, destination_filename: str, source_filename: str) -> None:
|
|
11
|
+
self.client.upload_file(source_filename, bucket_name, destination_filename)
|
|
12
|
+
|
|
13
|
+
def download(self, bucket_name: str, source_filename: str, destination_filename: str) -> None:
|
|
14
|
+
self.client.download_file(bucket_name, source_filename, destination_filename)
|
|
15
|
+
|
|
16
|
+
def generate_presigned_url(
|
|
17
|
+
self,
|
|
18
|
+
bucket_name: str,
|
|
19
|
+
source_filename: str,
|
|
20
|
+
expiration: int,
|
|
21
|
+
) -> str:
|
|
22
|
+
url = self.client.generate_presigned_url(
|
|
23
|
+
ClientMethod="get_object",
|
|
24
|
+
Params={"Bucket": bucket_name, "Key": source_filename},
|
|
25
|
+
ExpiresIn=expiration,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
return url
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Uploader(Client):
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Downloader(Client):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class URLSigner(Client):
|
|
40
|
+
pass
|
|
@@ -7,5 +7,6 @@ class Publisher:
|
|
|
7
7
|
def __init__(self, *args: Any, **kwargs: Any):
|
|
8
8
|
self.client = boto3.client("sns", *args, **kwargs)
|
|
9
9
|
|
|
10
|
-
def publish(self, recipient: str, message: str, **attrs: Any) ->
|
|
11
|
-
self.client.publish(TargetArn=recipient, Message=message, **attrs)
|
|
10
|
+
def publish(self, recipient: str, message: str, **attrs: Any) -> str:
|
|
11
|
+
response = self.client.publish(TargetArn=recipient, Message=message, **attrs)
|
|
12
|
+
return response["MessageId"]
|
|
@@ -7,5 +7,6 @@ class Publisher:
|
|
|
7
7
|
def __init__(self, *args: Any, **kwargs: Any):
|
|
8
8
|
self.client = boto3.client("sqs", *args, **kwargs)
|
|
9
9
|
|
|
10
|
-
def publish(self, recipient: str, message: str, **attrs: Any) ->
|
|
11
|
-
self.client.send_message(QueueUrl=recipient, MessageBody=message, **attrs)
|
|
10
|
+
def publish(self, recipient: str, message: str, **attrs: Any) -> str:
|
|
11
|
+
response = self.client.send_message(QueueUrl=recipient, MessageBody=message, **attrs)
|
|
12
|
+
return response["MessageId"]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from typing import Any, Callable, Type
|
|
2
|
+
|
|
3
|
+
from cloud.protocols import MessagePublisher, StorageDownloader, StorageUploader, StorageURLSigner
|
|
4
|
+
|
|
5
|
+
error_message = "%(class)s does not implement %(protocol)s protocol"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def storage_uploader(
|
|
9
|
+
uploader_class: Type[StorageUploader], *args: Any, **kwargs: Any
|
|
10
|
+
) -> Callable[[], StorageUploader]:
|
|
11
|
+
error = error_message % {"class": uploader_class.__name__, "protocol": "StorageUploader"}
|
|
12
|
+
assert issubclass(uploader_class, StorageUploader), error
|
|
13
|
+
|
|
14
|
+
def maker() -> StorageUploader:
|
|
15
|
+
return uploader_class(*args, **kwargs)
|
|
16
|
+
|
|
17
|
+
return maker
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def storage_downloader(
|
|
21
|
+
downloader_class: Type[StorageDownloader], *args: Any, **kwargs: Any
|
|
22
|
+
) -> Callable[[], StorageDownloader]:
|
|
23
|
+
error = error_message % {"class": downloader_class.__name__, "protocol": "StorageDownloader"}
|
|
24
|
+
assert issubclass(downloader_class, StorageDownloader), error
|
|
25
|
+
|
|
26
|
+
def maker() -> StorageDownloader:
|
|
27
|
+
return downloader_class(*args, **kwargs)
|
|
28
|
+
|
|
29
|
+
return maker
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def storage_urlsigner(
|
|
33
|
+
urlsigner_class: Type[StorageURLSigner], *args: Any, **kwargs: Any
|
|
34
|
+
) -> Callable[[], StorageURLSigner]:
|
|
35
|
+
error = error_message % {"class": urlsigner_class.__name__, "protocol": "StorageURLSigner"}
|
|
36
|
+
assert issubclass(urlsigner_class, StorageURLSigner), error
|
|
37
|
+
|
|
38
|
+
def maker() -> StorageURLSigner:
|
|
39
|
+
return urlsigner_class(*args, **kwargs)
|
|
40
|
+
|
|
41
|
+
return maker
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def message_publisher(
|
|
45
|
+
publisher_class: Type[MessagePublisher], *args: Any, **kwargs: Any
|
|
46
|
+
) -> Callable[[], MessagePublisher]:
|
|
47
|
+
error = error_message % {"class": publisher_class.__name__, "protocol": "MessagePublisher"}
|
|
48
|
+
assert issubclass(publisher_class, MessagePublisher), error
|
|
49
|
+
|
|
50
|
+
def maker() -> MessagePublisher:
|
|
51
|
+
return publisher_class(*args, **kwargs)
|
|
52
|
+
|
|
53
|
+
return maker
|
|
@@ -7,6 +7,6 @@ class Publisher:
|
|
|
7
7
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
8
8
|
self.client = pubsub_v1.PublisherClient(*args, **kwargs)
|
|
9
9
|
|
|
10
|
-
def publish(self, recipient: str, message: str, **attrs: Any) ->
|
|
10
|
+
def publish(self, recipient: str, message: str, **attrs: Any) -> str:
|
|
11
11
|
future = self.client.publish(recipient, data=message.encode(), **attrs)
|
|
12
|
-
future.result()
|
|
12
|
+
return future.result()
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from google.cloud import storage
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Client:
|
|
8
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
9
|
+
self.client = storage.Client(*args, **kwargs)
|
|
10
|
+
|
|
11
|
+
def upload(self, bucket_name: str, destination_filename: str, source_filename: str) -> None:
|
|
12
|
+
blob = self.client.bucket(bucket_name).blob(destination_filename)
|
|
13
|
+
blob.upload_from_filename(source_filename)
|
|
14
|
+
|
|
15
|
+
def download(self, bucket_name: str, source_filename: str, destination_filename: str) -> None:
|
|
16
|
+
blob = self.client.bucket(bucket_name).blob(source_filename)
|
|
17
|
+
blob.download_to_filename(destination_filename)
|
|
18
|
+
|
|
19
|
+
def generate_presigned_url(
|
|
20
|
+
self,
|
|
21
|
+
bucket_name: str,
|
|
22
|
+
source_filename: str,
|
|
23
|
+
expiration: int,
|
|
24
|
+
) -> str:
|
|
25
|
+
blob = self.client.bucket(bucket_name).blob(source_filename)
|
|
26
|
+
url = blob.generate_signed_url(
|
|
27
|
+
expiration=datetime.timedelta(seconds=expiration),
|
|
28
|
+
version="v4",
|
|
29
|
+
)
|
|
30
|
+
return url
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Uploader(Client):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Downloader(Client):
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class URLSigner(Client):
|
|
42
|
+
pass
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from typing import Any, Protocol, runtime_checkable
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@runtime_checkable
|
|
5
|
+
class StorageUploader(Protocol):
|
|
6
|
+
def upload(self, bucket_name: str, destination_filename: str, source_filename: str) -> None: ...
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@runtime_checkable
|
|
10
|
+
class StorageDownloader(Protocol):
|
|
11
|
+
def download(
|
|
12
|
+
self, bucket_name: str, source_filename: str, destination_filename: str
|
|
13
|
+
) -> None: ...
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@runtime_checkable
|
|
17
|
+
class StorageURLSigner(Protocol):
|
|
18
|
+
def generate_presigned_url(
|
|
19
|
+
self,
|
|
20
|
+
bucket_name: str,
|
|
21
|
+
source_filename: str,
|
|
22
|
+
expiration: int,
|
|
23
|
+
) -> str: ...
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@runtime_checkable
|
|
27
|
+
class MessagePublisher(Protocol):
|
|
28
|
+
def publish(self, recipient: str, message: str, **attrs: Any) -> str: ...
|
|
@@ -4,12 +4,10 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cloudtoolbox"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "Toolbox for the cloud."
|
|
9
|
-
readme = {file = "README.md", content-type = "text/markdown"}
|
|
10
|
-
authors = [
|
|
11
|
-
{name = "Dotz Developers", email = "devs-dotz@dotz.com"},
|
|
12
|
-
]
|
|
9
|
+
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
|
+
authors = [{ name = "Dotz Developers", email = "devs-dotz@dotz.com" }]
|
|
13
11
|
keywords = ["cloud", "tools", "utilities", "google", "gcp", "amazon", "aws"]
|
|
14
12
|
classifiers = [
|
|
15
13
|
"Operating System :: OS Independent",
|
|
@@ -33,24 +31,16 @@ Repository = "https://github.com/DotzInc/cloud-toolbox"
|
|
|
33
31
|
[project.optional-dependencies]
|
|
34
32
|
amazon = ["boto3"]
|
|
35
33
|
google = ["google-cloud-pubsub", "google-cloud-storage"]
|
|
36
|
-
all = [
|
|
37
|
-
"boto3",
|
|
38
|
-
"google-cloud-pubsub",
|
|
39
|
-
"google-cloud-storage",
|
|
40
|
-
]
|
|
34
|
+
all = ["boto3", "google-cloud-pubsub", "google-cloud-storage"]
|
|
41
35
|
|
|
42
36
|
[tool.ruff]
|
|
43
|
-
exclude = [
|
|
44
|
-
".git",
|
|
45
|
-
".venv",
|
|
46
|
-
".ruff_cache",
|
|
47
|
-
]
|
|
37
|
+
exclude = [".git", ".venv", ".ruff_cache"]
|
|
48
38
|
|
|
49
39
|
line-length = 100
|
|
50
40
|
indent-width = 4
|
|
51
41
|
|
|
52
42
|
[tool.ruff.lint]
|
|
53
|
-
select = ["E", "W", "F", "I", "B", "C4", "UP","ARG001"]
|
|
43
|
+
select = ["E", "W", "F", "I", "B", "C4", "UP", "ARG001"]
|
|
54
44
|
ignore = ["E402", "E501", "W191", "B904"]
|
|
55
45
|
fixable = ["ALL"]
|
|
56
46
|
unfixable = []
|
|
@@ -4,7 +4,7 @@ from unittest import mock
|
|
|
4
4
|
from cloud import factory
|
|
5
5
|
from cloud.amazon import s3, sns, sqs
|
|
6
6
|
from cloud.google import pubsub, storage
|
|
7
|
-
from cloud.protocols import MessagePublisher, StorageUploader
|
|
7
|
+
from cloud.protocols import MessagePublisher, StorageDownloader, StorageUploader, StorageURLSigner
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class NotStorageUploader:
|
|
@@ -12,6 +12,26 @@ class NotStorageUploader:
|
|
|
12
12
|
pass
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
class NotStorageDownloader:
|
|
16
|
+
def download_file(self):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class NotStorageURLSigner:
|
|
21
|
+
def sign_url(self):
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class DummyStorageURLSigner:
|
|
26
|
+
def generate_presigned_url(
|
|
27
|
+
self,
|
|
28
|
+
bucket_name: str,
|
|
29
|
+
source_filename: str,
|
|
30
|
+
expiration: int,
|
|
31
|
+
) -> str:
|
|
32
|
+
return ""
|
|
33
|
+
|
|
34
|
+
|
|
15
35
|
class NotMessagePublisher:
|
|
16
36
|
def send_message(self):
|
|
17
37
|
pass
|
|
@@ -41,6 +61,45 @@ class TestStorageUploaderFactory(unittest.TestCase):
|
|
|
41
61
|
factory.storage_uploader(NotStorageUploader) # type: ignore
|
|
42
62
|
|
|
43
63
|
|
|
64
|
+
class TestStorageDownloaderFactory(unittest.TestCase):
|
|
65
|
+
@mock.patch("google.cloud.storage.Client")
|
|
66
|
+
def test_google_downloader(self, _):
|
|
67
|
+
Downloader = factory.storage_downloader(storage.Downloader)
|
|
68
|
+
downloader = Downloader()
|
|
69
|
+
|
|
70
|
+
self.assertIsInstance(downloader, StorageDownloader)
|
|
71
|
+
self.assertIsInstance(downloader, storage.Downloader)
|
|
72
|
+
|
|
73
|
+
@mock.patch("boto3.client")
|
|
74
|
+
def test_amazon_downloader(self, _):
|
|
75
|
+
Downloader = factory.storage_downloader(s3.Downloader)
|
|
76
|
+
downloader = Downloader()
|
|
77
|
+
|
|
78
|
+
self.assertIsInstance(downloader, StorageDownloader)
|
|
79
|
+
self.assertIsInstance(downloader, s3.Downloader)
|
|
80
|
+
|
|
81
|
+
def test_downloader_protocol_validation(self):
|
|
82
|
+
error = "NotStorageDownloader does not implement StorageDownloader protocol"
|
|
83
|
+
|
|
84
|
+
with self.assertRaisesRegex(AssertionError, error):
|
|
85
|
+
factory.storage_downloader(NotStorageDownloader) # type: ignore
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class TestStorageURLSigner(unittest.TestCase):
|
|
89
|
+
def test_urlsigner(self):
|
|
90
|
+
URLSigner = factory.storage_urlsigner(DummyStorageURLSigner)
|
|
91
|
+
urlsigner = URLSigner()
|
|
92
|
+
|
|
93
|
+
self.assertIsInstance(urlsigner, StorageURLSigner)
|
|
94
|
+
self.assertIsInstance(urlsigner, DummyStorageURLSigner)
|
|
95
|
+
|
|
96
|
+
def test_urlsigner_protocol_validation(self):
|
|
97
|
+
error = "NotStorageURLSigner does not implement StorageURLSigner protocol"
|
|
98
|
+
|
|
99
|
+
with self.assertRaisesRegex(AssertionError, error):
|
|
100
|
+
factory.storage_urlsigner(NotStorageURLSigner) # type: ignore
|
|
101
|
+
|
|
102
|
+
|
|
44
103
|
class TestMessagePublisherFactory(unittest.TestCase):
|
|
45
104
|
@mock.patch("google.cloud.pubsub_v1.PublisherClient")
|
|
46
105
|
def test_google_publisher(self, _):
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
from typing import Any
|
|
2
|
-
|
|
3
|
-
import boto3
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class Uploader:
|
|
7
|
-
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
8
|
-
self.client = boto3.client("s3", *args, **kwargs)
|
|
9
|
-
|
|
10
|
-
def upload(self, bucket_name: str, destination_filename: str, source_filename: str) -> None:
|
|
11
|
-
self.client.upload_file(source_filename, bucket_name, destination_filename)
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
from typing import Any, Callable, Type
|
|
2
|
-
|
|
3
|
-
from cloud.protocols import MessagePublisher, StorageUploader
|
|
4
|
-
|
|
5
|
-
error_message = "%(class)s does not implement %(protocol)s protocol"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def storage_uploader(
|
|
9
|
-
uploader_class: Type[StorageUploader], *args: Any, **kwargs: Any
|
|
10
|
-
) -> Callable[[], StorageUploader]:
|
|
11
|
-
error = error_message % {"class": uploader_class.__name__, "protocol": "StorageUploader"}
|
|
12
|
-
assert issubclass(uploader_class, StorageUploader), error
|
|
13
|
-
|
|
14
|
-
def maker() -> StorageUploader:
|
|
15
|
-
return uploader_class(*args, **kwargs)
|
|
16
|
-
|
|
17
|
-
return maker
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def message_publisher(
|
|
21
|
-
publisher_class: Type[MessagePublisher], *args: Any, **kwargs: Any
|
|
22
|
-
) -> Callable[[], MessagePublisher]:
|
|
23
|
-
error = error_message % {"class": publisher_class.__name__, "protocol": "MessagePublisher"}
|
|
24
|
-
assert issubclass(publisher_class, MessagePublisher), error
|
|
25
|
-
|
|
26
|
-
def maker() -> MessagePublisher:
|
|
27
|
-
return publisher_class(*args, **kwargs)
|
|
28
|
-
|
|
29
|
-
return maker
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
from typing import Any
|
|
2
|
-
|
|
3
|
-
from google.cloud import storage
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class Uploader:
|
|
7
|
-
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
8
|
-
self.client = storage.Client(*args, **kwargs)
|
|
9
|
-
|
|
10
|
-
def upload(self, bucket_name: str, destination_filename: str, source_filename: str) -> None:
|
|
11
|
-
bucket = self.client.bucket(bucket_name)
|
|
12
|
-
blob = bucket.blob(destination_filename)
|
|
13
|
-
blob.upload_from_filename(source_filename)
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
from typing import Any, Protocol, runtime_checkable
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
@runtime_checkable
|
|
5
|
-
class StorageUploader(Protocol):
|
|
6
|
-
def upload(self, bucket_name: str, destination_filename: str, source_filename: str) -> None: ...
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
@runtime_checkable
|
|
10
|
-
class MessagePublisher(Protocol):
|
|
11
|
-
def publish(self, recipient: str, message: str, **attrs: Any) -> None: ...
|
|
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
|