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.
Files changed (25) hide show
  1. {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0}/LICENSE +1 -1
  2. {cloudtoolbox-0.2.1/cloudtoolbox.egg-info → cloudtoolbox-0.4.0}/PKG-INFO +1 -1
  3. cloudtoolbox-0.4.0/cloud/amazon/s3.py +40 -0
  4. {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0}/cloud/amazon/sns.py +3 -2
  5. {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0}/cloud/amazon/sqs.py +3 -2
  6. cloudtoolbox-0.4.0/cloud/factory.py +53 -0
  7. {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0}/cloud/google/pubsub.py +2 -2
  8. cloudtoolbox-0.4.0/cloud/google/storage.py +42 -0
  9. cloudtoolbox-0.4.0/cloud/protocols.py +28 -0
  10. {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0/cloudtoolbox.egg-info}/PKG-INFO +1 -1
  11. {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0}/pyproject.toml +6 -16
  12. {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0}/tests/test_factory.py +60 -1
  13. cloudtoolbox-0.2.1/cloud/amazon/s3.py +0 -11
  14. cloudtoolbox-0.2.1/cloud/factory.py +0 -29
  15. cloudtoolbox-0.2.1/cloud/google/storage.py +0 -13
  16. cloudtoolbox-0.2.1/cloud/protocols.py +0 -11
  17. {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0}/README.md +0 -0
  18. {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0}/cloud/__init__.py +0 -0
  19. {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0}/cloud/amazon/__init__.py +0 -0
  20. {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0}/cloud/google/__init__.py +0 -0
  21. {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0}/cloudtoolbox.egg-info/SOURCES.txt +0 -0
  22. {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0}/cloudtoolbox.egg-info/dependency_links.txt +0 -0
  23. {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0}/cloudtoolbox.egg-info/requires.txt +0 -0
  24. {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0}/cloudtoolbox.egg-info/top_level.txt +0 -0
  25. {cloudtoolbox-0.2.1 → cloudtoolbox-0.4.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Noverde
3
+ Copyright (c) 2024 Dotz
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cloudtoolbox
3
- Version: 0.2.1
3
+ Version: 0.4.0
4
4
  Summary: Toolbox for the cloud.
5
5
  Author-email: Dotz Developers <devs-dotz@dotz.com>
6
6
  Project-URL: Repository, https://github.com/DotzInc/cloud-toolbox
@@ -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) -> None:
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) -> None:
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) -> None:
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: ...
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cloudtoolbox
3
- Version: 0.2.1
3
+ Version: 0.4.0
4
4
  Summary: Toolbox for the cloud.
5
5
  Author-email: Dotz Developers <devs-dotz@dotz.com>
6
6
  Project-URL: Repository, https://github.com/DotzInc/cloud-toolbox
@@ -4,12 +4,10 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cloudtoolbox"
7
- version = "0.2.1"
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