10x-cloud-storage-manager 0.2.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 (26) hide show
  1. 10x_cloud_storage_manager-0.2.0/10x_cloud_storage_manager.egg-info/PKG-INFO +248 -0
  2. 10x_cloud_storage_manager-0.2.0/10x_cloud_storage_manager.egg-info/SOURCES.txt +24 -0
  3. 10x_cloud_storage_manager-0.2.0/10x_cloud_storage_manager.egg-info/dependency_links.txt +1 -0
  4. 10x_cloud_storage_manager-0.2.0/10x_cloud_storage_manager.egg-info/requires.txt +24 -0
  5. 10x_cloud_storage_manager-0.2.0/10x_cloud_storage_manager.egg-info/top_level.txt +1 -0
  6. 10x_cloud_storage_manager-0.2.0/LICENSE +21 -0
  7. 10x_cloud_storage_manager-0.2.0/MANIFEST.in +7 -0
  8. 10x_cloud_storage_manager-0.2.0/PKG-INFO +248 -0
  9. 10x_cloud_storage_manager-0.2.0/README.md +184 -0
  10. 10x_cloud_storage_manager-0.2.0/cloud_storage_manager/__init__.py +38 -0
  11. 10x_cloud_storage_manager-0.2.0/cloud_storage_manager/_distribution.py +4 -0
  12. 10x_cloud_storage_manager-0.2.0/cloud_storage_manager/aws_s3_storage.py +168 -0
  13. 10x_cloud_storage_manager-0.2.0/cloud_storage_manager/azure_storage.py +169 -0
  14. 10x_cloud_storage_manager-0.2.0/cloud_storage_manager/base_storage.py +156 -0
  15. 10x_cloud_storage_manager-0.2.0/cloud_storage_manager/exceptions.py +26 -0
  16. 10x_cloud_storage_manager-0.2.0/cloud_storage_manager/gcp_storage.py +226 -0
  17. 10x_cloud_storage_manager-0.2.0/cloud_storage_manager/storage_provider.py +72 -0
  18. 10x_cloud_storage_manager-0.2.0/cloud_storage_manager/storage_schema.py +73 -0
  19. 10x_cloud_storage_manager-0.2.0/pyproject.toml +70 -0
  20. 10x_cloud_storage_manager-0.2.0/setup.cfg +4 -0
  21. 10x_cloud_storage_manager-0.2.0/tests/test_aws.py +191 -0
  22. 10x_cloud_storage_manager-0.2.0/tests/test_azure.py +209 -0
  23. 10x_cloud_storage_manager-0.2.0/tests/test_factory.py +71 -0
  24. 10x_cloud_storage_manager-0.2.0/tests/test_gcp.py +164 -0
  25. 10x_cloud_storage_manager-0.2.0/tests/test_package.py +79 -0
  26. 10x_cloud_storage_manager-0.2.0/tests/test_schema.py +66 -0
@@ -0,0 +1,248 @@
1
+ Metadata-Version: 2.4
2
+ Name: 10x-cloud-storage-manager
3
+ Version: 0.2.0
4
+ Summary: A unified async interface for AWS S3, Google Cloud Storage and Azure Blob Storage
5
+ Author-email: MUTHARASU A <mutharasu39@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2024 MUTHARASU a
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the “Software”), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is furnished
15
+ to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26
+ THE SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/10xHub/cloud-storage-manager
29
+ Project-URL: Bug Tracker, https://github.com/10xHub/cloud-storage-manager/issues
30
+ Keywords: aws,s3,gcp,azure,blob,cloud storage,file upload,async
31
+ Classifier: Development Status :: 3 - Alpha
32
+ Classifier: Intended Audience :: Developers
33
+ Classifier: License :: OSI Approved :: MIT License
34
+ Classifier: Programming Language :: Python :: 3
35
+ Classifier: Programming Language :: Python :: 3.10
36
+ Classifier: Programming Language :: Python :: 3.11
37
+ Classifier: Programming Language :: Python :: 3.12
38
+ Classifier: Programming Language :: Python :: 3.13
39
+ Classifier: Framework :: AsyncIO
40
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
41
+ Requires-Python: >=3.10
42
+ Description-Content-Type: text/markdown
43
+ License-File: LICENSE
44
+ Requires-Dist: aioboto3>=11.0.0
45
+ Requires-Dist: google-cloud-storage>=2.0.0
46
+ Requires-Dist: pydantic>=2.0.0
47
+ Provides-Extra: aws
48
+ Requires-Dist: aioboto3>=11.0.0; extra == "aws"
49
+ Provides-Extra: gcp
50
+ Requires-Dist: google-cloud-storage>=2.0.0; extra == "gcp"
51
+ Provides-Extra: azure
52
+ Requires-Dist: azure-storage-blob>=12.19.0; extra == "azure"
53
+ Requires-Dist: aiohttp>=3.8.0; extra == "azure"
54
+ Provides-Extra: all
55
+ Requires-Dist: 10x-cloud-storage-manager[aws,azure,gcp]; extra == "all"
56
+ Provides-Extra: dev
57
+ Requires-Dist: 10x-cloud-storage-manager[all]; extra == "dev"
58
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
59
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
60
+ Requires-Dist: packaging>=23.0; extra == "dev"
61
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
62
+ Requires-Dist: ruff>=0.5.0; extra == "dev"
63
+ Dynamic: license-file
64
+
65
+ # Cloud Storage Manager
66
+
67
+ One async interface over AWS S3, Google Cloud Storage and Azure Blob Storage. Application code
68
+ calls the same methods everywhere; switching cloud is a configuration change.
69
+
70
+ ## Features
71
+
72
+ - One interface for AWS S3, Google Cloud Storage and Azure Blob Storage
73
+ - Async operations
74
+ - Upload from a file or from bytes, download, list, delete, signed read URLs
75
+ - `upload_bytes` stores a content type, so a signed video URL plays in a browser
76
+ - Type-safe configuration using Pydantic
77
+ - A drop-in upgrade from 0.1.x (see below)
78
+
79
+ ## Installation
80
+
81
+ The base install covers AWS S3 and Google Cloud Storage, as 0.1.x did. Azure is an extra:
82
+
83
+ ```bash
84
+ pip install 10x-cloud-storage-manager # AWS S3 + Google Cloud Storage
85
+ pip install "10x-cloud-storage-manager[azure]" # adds Azure Blob Storage
86
+ pip install "10x-cloud-storage-manager[all]" # all three
87
+ ```
88
+
89
+ The import name is still `cloud_storage_manager`. From 0.2.0 the package is published as
90
+ `10x-cloud-storage-manager`; `cloud-storage-manager` on PyPI stops at 0.1.2.
91
+
92
+ Requires Python 3.10+.
93
+
94
+ ## Quick start
95
+
96
+ ```python
97
+ import asyncio
98
+ from cloud_storage_manager import CloudStorageFactory, StorageConfig
99
+
100
+
101
+ async def main(config: StorageConfig):
102
+ storage = CloudStorageFactory.from_config(config)
103
+
104
+ await storage.upload("local/clip.mp4", "recordings/clip.mp4")
105
+ await storage.upload_bytes(b"# Notes", "notes/a.md", content_type="text/markdown")
106
+
107
+ data = await storage.download_bytes("notes/a.md")
108
+ names = await storage.list_objects("recordings/")
109
+ sizes = await storage.list_object_sizes("recordings/") # {name: bytes}
110
+
111
+ url = await storage.get_public_url("recordings/clip.mp4", expiration=3600)
112
+
113
+ deleted = await storage.delete_if_exists("notes/a.md") # False if it was already gone
114
+ ```
115
+
116
+ `CloudStorageFactory.from_config` picks the provider from the config.
117
+ `CloudStorageFactory.get_storage(StorageProvider.GCP, config)` still works.
118
+
119
+ ## Configuration
120
+
121
+ `CloudStorageFactory.get_storage(provider, config)` uses the provider you name, so a config may
122
+ hold more than one. `config.provider` and `from_config` infer it, and raise `ValueError` unless
123
+ exactly one is set.
124
+
125
+ ### AWS S3
126
+
127
+ ```python
128
+ from cloud_storage_manager import AwsConfig, StorageConfig
129
+
130
+ config = StorageConfig(
131
+ aws=AwsConfig(
132
+ bucket_name="my-bucket",
133
+ access_key_id="...",
134
+ secret_access_key="...",
135
+ region="us-east-1", # stored but not used; boto resolves the region, as in 0.1.x
136
+ )
137
+ )
138
+ ```
139
+
140
+ ### Google Cloud Storage
141
+
142
+ ```python
143
+ from cloud_storage_manager import GcpConfig, StorageConfig
144
+
145
+ config = StorageConfig(
146
+ gcp=GcpConfig(
147
+ bucket_name="my-bucket",
148
+ project_id="my-project",
149
+ credentials={...}, # service account JSON as a dict
150
+ )
151
+ )
152
+ ```
153
+
154
+ ### Azure Blob Storage
155
+
156
+ Give exactly one of `account_key` or `connection_string`.
157
+
158
+ ```python
159
+ from cloud_storage_manager import AzureConfig, StorageConfig
160
+
161
+ config = StorageConfig(
162
+ azure=AzureConfig(
163
+ account_name="mystorageaccount",
164
+ container_name="my-container",
165
+ account_key="...",
166
+ # or: connection_string="DefaultEndpointsProtocol=https;AccountName=...;AccountKey=...;"
167
+ )
168
+ )
169
+ ```
170
+
171
+ `get_public_url` on Azure returns a read-only blob SAS URL, signed with the account key. A
172
+ connection string that carries a SAS instead of an `AccountKey` can read and write but cannot
173
+ sign URLs.
174
+
175
+ ## Interface
176
+
177
+ Every provider implements `BaseCloudStorage`:
178
+
179
+ | Method | Returns | Notes |
180
+ | --- | --- | --- |
181
+ | `upload(file_path, cloud_path)` | `str` | GCP and Azure type the object from its file name; S3 does not (0.1.x behaviour) |
182
+ | `upload_bytes(data, cloud_path, content_type=None)` | `str` | Content type guessed from `cloud_path` when omitted |
183
+ | `download_bytes(cloud_path)` | `bytes` | `NotFoundError` if missing |
184
+ | `list_objects(prefix)` | `list[str]` | Every page |
185
+ | `list_object_sizes(prefix)` | `dict[str, int]` | Name to size in bytes |
186
+ | `delete(cloud_path)` | `bool` | 0.1.x behaviour: a missing object raises `DeleteError` on GCP and Azure, returns `True` on S3 |
187
+ | `delete_if_exists(cloud_path)` | `bool` | `False` if it did not exist, on every cloud |
188
+ | `get_public_url(cloud_path, expiration=3600)` | `str` | Signed, read-only, expires after `expiration` seconds |
189
+
190
+ ## Error handling
191
+
192
+ Every error is a `CloudStorageError`: `UploadError`, `DownloadError`, `NotFoundError`,
193
+ `ListError`, `DeleteError`. The SDK's own exception is kept as `__cause__`.
194
+
195
+ ```python
196
+ from cloud_storage_manager import NotFoundError
197
+
198
+ try:
199
+ data = await storage.download_bytes("missing.txt")
200
+ except NotFoundError:
201
+ data = None
202
+ ```
203
+
204
+ ## Upgrading from 0.1.x
205
+
206
+ Change only the requirement: swap `cloud-storage-manager` for `10x-cloud-storage-manager`
207
+ (uninstall the old package first, since both ship the `cloud_storage_manager` module). Your code
208
+ does not change: every 0.1.x call behaves as before.
209
+
210
+ ```bash
211
+ pip uninstall -y cloud-storage-manager
212
+ pip install 10x-cloud-storage-manager
213
+ ```
214
+
215
+ - The base install pulls the same packages as 0.1.x; Azure is the `[azure]` extra.
216
+ - `StorageConfig` accepts every combination 0.1.x accepted.
217
+ - `upload`, `delete` and `get_public_url` behave as in 0.1.x on S3 and GCP; the new
218
+ `delete_if_exists` is the one to use for the same answer on every cloud.
219
+ - Only those three methods are abstract, so a subclass written for 0.1.x still instantiates.
220
+ - `AWSClientStorage` and `GcpClientStorage` are still importable from
221
+ `cloud_storage_manager.storage_provider`.
222
+ - Python 3.10+ (0.1.x already used 3.10 syntax and did not import on 3.8 or 3.9).
223
+
224
+ ## Development
225
+
226
+ ```bash
227
+ python -m venv .venv
228
+ source .venv/bin/activate
229
+ pip install -e ".[dev]"
230
+ pytest --cov
231
+ ruff check . && ruff format --check .
232
+ ```
233
+
234
+ Tests mock every cloud SDK and never call a real cloud.
235
+
236
+ ### Branches and releases
237
+
238
+ - `dev` is the default branch. Open feature PRs against it.
239
+ - `prod` is the release branch. **Every merge into `prod` publishes to PyPI** and creates a
240
+ `v<version>` GitHub release (`.github/workflows/release.yml`).
241
+ - Bump `[project].version` in `pyproject.toml` in the PR to `prod`. CI fails the PR if PyPI
242
+ already has that version.
243
+ - Publishing uses the `PYPI_API_TOKEN` secret on the `pypi` GitHub environment, which only
244
+ the `prod` branch can deploy to. Never upload to PyPI by hand.
245
+
246
+ ## License
247
+
248
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,24 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ pyproject.toml
5
+ 10x_cloud_storage_manager.egg-info/PKG-INFO
6
+ 10x_cloud_storage_manager.egg-info/SOURCES.txt
7
+ 10x_cloud_storage_manager.egg-info/dependency_links.txt
8
+ 10x_cloud_storage_manager.egg-info/requires.txt
9
+ 10x_cloud_storage_manager.egg-info/top_level.txt
10
+ cloud_storage_manager/__init__.py
11
+ cloud_storage_manager/_distribution.py
12
+ cloud_storage_manager/aws_s3_storage.py
13
+ cloud_storage_manager/azure_storage.py
14
+ cloud_storage_manager/base_storage.py
15
+ cloud_storage_manager/exceptions.py
16
+ cloud_storage_manager/gcp_storage.py
17
+ cloud_storage_manager/storage_provider.py
18
+ cloud_storage_manager/storage_schema.py
19
+ tests/test_aws.py
20
+ tests/test_azure.py
21
+ tests/test_factory.py
22
+ tests/test_gcp.py
23
+ tests/test_package.py
24
+ tests/test_schema.py
@@ -0,0 +1,24 @@
1
+ aioboto3>=11.0.0
2
+ google-cloud-storage>=2.0.0
3
+ pydantic>=2.0.0
4
+
5
+ [all]
6
+ 10x-cloud-storage-manager[aws,azure,gcp]
7
+
8
+ [aws]
9
+ aioboto3>=11.0.0
10
+
11
+ [azure]
12
+ azure-storage-blob>=12.19.0
13
+ aiohttp>=3.8.0
14
+
15
+ [dev]
16
+ 10x-cloud-storage-manager[all]
17
+ pytest>=7.0.0
18
+ pytest-asyncio>=0.23.0
19
+ packaging>=23.0
20
+ pytest-cov>=4.0.0
21
+ ruff>=0.5.0
22
+
23
+ [gcp]
24
+ google-cloud-storage>=2.0.0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 MUTHARASU a
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the “Software”), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is furnished
10
+ to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,7 @@
1
+ include README.md
2
+ include LICENSE
3
+ include pyproject.toml
4
+ recursive-include cloud_storage_manager *.py
5
+ global-exclude *.pyc
6
+ global-exclude __pycache__
7
+ global-exclude .DS_Store
@@ -0,0 +1,248 @@
1
+ Metadata-Version: 2.4
2
+ Name: 10x-cloud-storage-manager
3
+ Version: 0.2.0
4
+ Summary: A unified async interface for AWS S3, Google Cloud Storage and Azure Blob Storage
5
+ Author-email: MUTHARASU A <mutharasu39@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2024 MUTHARASU a
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the “Software”), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is furnished
15
+ to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26
+ THE SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/10xHub/cloud-storage-manager
29
+ Project-URL: Bug Tracker, https://github.com/10xHub/cloud-storage-manager/issues
30
+ Keywords: aws,s3,gcp,azure,blob,cloud storage,file upload,async
31
+ Classifier: Development Status :: 3 - Alpha
32
+ Classifier: Intended Audience :: Developers
33
+ Classifier: License :: OSI Approved :: MIT License
34
+ Classifier: Programming Language :: Python :: 3
35
+ Classifier: Programming Language :: Python :: 3.10
36
+ Classifier: Programming Language :: Python :: 3.11
37
+ Classifier: Programming Language :: Python :: 3.12
38
+ Classifier: Programming Language :: Python :: 3.13
39
+ Classifier: Framework :: AsyncIO
40
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
41
+ Requires-Python: >=3.10
42
+ Description-Content-Type: text/markdown
43
+ License-File: LICENSE
44
+ Requires-Dist: aioboto3>=11.0.0
45
+ Requires-Dist: google-cloud-storage>=2.0.0
46
+ Requires-Dist: pydantic>=2.0.0
47
+ Provides-Extra: aws
48
+ Requires-Dist: aioboto3>=11.0.0; extra == "aws"
49
+ Provides-Extra: gcp
50
+ Requires-Dist: google-cloud-storage>=2.0.0; extra == "gcp"
51
+ Provides-Extra: azure
52
+ Requires-Dist: azure-storage-blob>=12.19.0; extra == "azure"
53
+ Requires-Dist: aiohttp>=3.8.0; extra == "azure"
54
+ Provides-Extra: all
55
+ Requires-Dist: 10x-cloud-storage-manager[aws,azure,gcp]; extra == "all"
56
+ Provides-Extra: dev
57
+ Requires-Dist: 10x-cloud-storage-manager[all]; extra == "dev"
58
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
59
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
60
+ Requires-Dist: packaging>=23.0; extra == "dev"
61
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
62
+ Requires-Dist: ruff>=0.5.0; extra == "dev"
63
+ Dynamic: license-file
64
+
65
+ # Cloud Storage Manager
66
+
67
+ One async interface over AWS S3, Google Cloud Storage and Azure Blob Storage. Application code
68
+ calls the same methods everywhere; switching cloud is a configuration change.
69
+
70
+ ## Features
71
+
72
+ - One interface for AWS S3, Google Cloud Storage and Azure Blob Storage
73
+ - Async operations
74
+ - Upload from a file or from bytes, download, list, delete, signed read URLs
75
+ - `upload_bytes` stores a content type, so a signed video URL plays in a browser
76
+ - Type-safe configuration using Pydantic
77
+ - A drop-in upgrade from 0.1.x (see below)
78
+
79
+ ## Installation
80
+
81
+ The base install covers AWS S3 and Google Cloud Storage, as 0.1.x did. Azure is an extra:
82
+
83
+ ```bash
84
+ pip install 10x-cloud-storage-manager # AWS S3 + Google Cloud Storage
85
+ pip install "10x-cloud-storage-manager[azure]" # adds Azure Blob Storage
86
+ pip install "10x-cloud-storage-manager[all]" # all three
87
+ ```
88
+
89
+ The import name is still `cloud_storage_manager`. From 0.2.0 the package is published as
90
+ `10x-cloud-storage-manager`; `cloud-storage-manager` on PyPI stops at 0.1.2.
91
+
92
+ Requires Python 3.10+.
93
+
94
+ ## Quick start
95
+
96
+ ```python
97
+ import asyncio
98
+ from cloud_storage_manager import CloudStorageFactory, StorageConfig
99
+
100
+
101
+ async def main(config: StorageConfig):
102
+ storage = CloudStorageFactory.from_config(config)
103
+
104
+ await storage.upload("local/clip.mp4", "recordings/clip.mp4")
105
+ await storage.upload_bytes(b"# Notes", "notes/a.md", content_type="text/markdown")
106
+
107
+ data = await storage.download_bytes("notes/a.md")
108
+ names = await storage.list_objects("recordings/")
109
+ sizes = await storage.list_object_sizes("recordings/") # {name: bytes}
110
+
111
+ url = await storage.get_public_url("recordings/clip.mp4", expiration=3600)
112
+
113
+ deleted = await storage.delete_if_exists("notes/a.md") # False if it was already gone
114
+ ```
115
+
116
+ `CloudStorageFactory.from_config` picks the provider from the config.
117
+ `CloudStorageFactory.get_storage(StorageProvider.GCP, config)` still works.
118
+
119
+ ## Configuration
120
+
121
+ `CloudStorageFactory.get_storage(provider, config)` uses the provider you name, so a config may
122
+ hold more than one. `config.provider` and `from_config` infer it, and raise `ValueError` unless
123
+ exactly one is set.
124
+
125
+ ### AWS S3
126
+
127
+ ```python
128
+ from cloud_storage_manager import AwsConfig, StorageConfig
129
+
130
+ config = StorageConfig(
131
+ aws=AwsConfig(
132
+ bucket_name="my-bucket",
133
+ access_key_id="...",
134
+ secret_access_key="...",
135
+ region="us-east-1", # stored but not used; boto resolves the region, as in 0.1.x
136
+ )
137
+ )
138
+ ```
139
+
140
+ ### Google Cloud Storage
141
+
142
+ ```python
143
+ from cloud_storage_manager import GcpConfig, StorageConfig
144
+
145
+ config = StorageConfig(
146
+ gcp=GcpConfig(
147
+ bucket_name="my-bucket",
148
+ project_id="my-project",
149
+ credentials={...}, # service account JSON as a dict
150
+ )
151
+ )
152
+ ```
153
+
154
+ ### Azure Blob Storage
155
+
156
+ Give exactly one of `account_key` or `connection_string`.
157
+
158
+ ```python
159
+ from cloud_storage_manager import AzureConfig, StorageConfig
160
+
161
+ config = StorageConfig(
162
+ azure=AzureConfig(
163
+ account_name="mystorageaccount",
164
+ container_name="my-container",
165
+ account_key="...",
166
+ # or: connection_string="DefaultEndpointsProtocol=https;AccountName=...;AccountKey=...;"
167
+ )
168
+ )
169
+ ```
170
+
171
+ `get_public_url` on Azure returns a read-only blob SAS URL, signed with the account key. A
172
+ connection string that carries a SAS instead of an `AccountKey` can read and write but cannot
173
+ sign URLs.
174
+
175
+ ## Interface
176
+
177
+ Every provider implements `BaseCloudStorage`:
178
+
179
+ | Method | Returns | Notes |
180
+ | --- | --- | --- |
181
+ | `upload(file_path, cloud_path)` | `str` | GCP and Azure type the object from its file name; S3 does not (0.1.x behaviour) |
182
+ | `upload_bytes(data, cloud_path, content_type=None)` | `str` | Content type guessed from `cloud_path` when omitted |
183
+ | `download_bytes(cloud_path)` | `bytes` | `NotFoundError` if missing |
184
+ | `list_objects(prefix)` | `list[str]` | Every page |
185
+ | `list_object_sizes(prefix)` | `dict[str, int]` | Name to size in bytes |
186
+ | `delete(cloud_path)` | `bool` | 0.1.x behaviour: a missing object raises `DeleteError` on GCP and Azure, returns `True` on S3 |
187
+ | `delete_if_exists(cloud_path)` | `bool` | `False` if it did not exist, on every cloud |
188
+ | `get_public_url(cloud_path, expiration=3600)` | `str` | Signed, read-only, expires after `expiration` seconds |
189
+
190
+ ## Error handling
191
+
192
+ Every error is a `CloudStorageError`: `UploadError`, `DownloadError`, `NotFoundError`,
193
+ `ListError`, `DeleteError`. The SDK's own exception is kept as `__cause__`.
194
+
195
+ ```python
196
+ from cloud_storage_manager import NotFoundError
197
+
198
+ try:
199
+ data = await storage.download_bytes("missing.txt")
200
+ except NotFoundError:
201
+ data = None
202
+ ```
203
+
204
+ ## Upgrading from 0.1.x
205
+
206
+ Change only the requirement: swap `cloud-storage-manager` for `10x-cloud-storage-manager`
207
+ (uninstall the old package first, since both ship the `cloud_storage_manager` module). Your code
208
+ does not change: every 0.1.x call behaves as before.
209
+
210
+ ```bash
211
+ pip uninstall -y cloud-storage-manager
212
+ pip install 10x-cloud-storage-manager
213
+ ```
214
+
215
+ - The base install pulls the same packages as 0.1.x; Azure is the `[azure]` extra.
216
+ - `StorageConfig` accepts every combination 0.1.x accepted.
217
+ - `upload`, `delete` and `get_public_url` behave as in 0.1.x on S3 and GCP; the new
218
+ `delete_if_exists` is the one to use for the same answer on every cloud.
219
+ - Only those three methods are abstract, so a subclass written for 0.1.x still instantiates.
220
+ - `AWSClientStorage` and `GcpClientStorage` are still importable from
221
+ `cloud_storage_manager.storage_provider`.
222
+ - Python 3.10+ (0.1.x already used 3.10 syntax and did not import on 3.8 or 3.9).
223
+
224
+ ## Development
225
+
226
+ ```bash
227
+ python -m venv .venv
228
+ source .venv/bin/activate
229
+ pip install -e ".[dev]"
230
+ pytest --cov
231
+ ruff check . && ruff format --check .
232
+ ```
233
+
234
+ Tests mock every cloud SDK and never call a real cloud.
235
+
236
+ ### Branches and releases
237
+
238
+ - `dev` is the default branch. Open feature PRs against it.
239
+ - `prod` is the release branch. **Every merge into `prod` publishes to PyPI** and creates a
240
+ `v<version>` GitHub release (`.github/workflows/release.yml`).
241
+ - Bump `[project].version` in `pyproject.toml` in the PR to `prod`. CI fails the PR if PyPI
242
+ already has that version.
243
+ - Publishing uses the `PYPI_API_TOKEN` secret on the `pypi` GitHub environment, which only
244
+ the `prod` branch can deploy to. Never upload to PyPI by hand.
245
+
246
+ ## License
247
+
248
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.