lcdp-boto3-utils 1.6.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.
- lcdp_boto3_utils-1.6.0/PKG-INFO +16 -0
- lcdp_boto3_utils-1.6.0/lcdp_boto3_utils/__init__.py +0 -0
- lcdp_boto3_utils-1.6.0/lcdp_boto3_utils/exceptions/S3TransferException.py +3 -0
- lcdp_boto3_utils-1.6.0/lcdp_boto3_utils/exceptions/__init__.py +0 -0
- lcdp_boto3_utils-1.6.0/lcdp_boto3_utils/s3_manager.py +129 -0
- lcdp_boto3_utils-1.6.0/pyproject.toml +23 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lcdp-boto3-utils
|
|
3
|
+
Version: 1.6.0
|
|
4
|
+
Summary: Boto3 Utils
|
|
5
|
+
Author: Le Comptoir Des Pharmacies
|
|
6
|
+
Author-email: webmaster@lecomptoirdespharmacies.fr
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Requires-Dist: boto3 (>=1.26.83,<2.0.0)
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
import boto3
|
|
5
|
+
from botocore.exceptions import ClientError
|
|
6
|
+
|
|
7
|
+
from .exceptions.S3TransferException import S3TransferException
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class S3Manager:
|
|
11
|
+
"""Service for uploading, downloading and deleting payloads in an S3 bucket."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, bucket_name: str, region: str):
|
|
14
|
+
"""
|
|
15
|
+
Initialize an S3Manager for a specific bucket and region.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
bucket_name: Name of the S3 bucket where payloads are stored.
|
|
19
|
+
region: AWS region used to configure the underlying S3 client.
|
|
20
|
+
"""
|
|
21
|
+
self.bucket_name = bucket_name
|
|
22
|
+
self.s3_client = boto3.client("s3", region_name=region)
|
|
23
|
+
|
|
24
|
+
def _ensure_json_bytes(self, payload: Any) -> bytes:
|
|
25
|
+
"""
|
|
26
|
+
Normalize a payload to JSON-encoded bytes.
|
|
27
|
+
|
|
28
|
+
If the payload is already bytes, it is returned as-is.
|
|
29
|
+
If it is a string, it is encoded as UTF-8.
|
|
30
|
+
Otherwise, it is serialized to JSON then encoded as UTF-8.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
payload: Arbitrary payload (dict, list, str, bytes, etc.) to serialize.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
UTF-8 encoded JSON representation of the payload.
|
|
37
|
+
"""
|
|
38
|
+
if isinstance(payload, bytes):
|
|
39
|
+
return payload
|
|
40
|
+
if isinstance(payload, str):
|
|
41
|
+
return payload.encode("utf-8")
|
|
42
|
+
return json.dumps(payload, ensure_ascii=False, default=str).encode("utf-8")
|
|
43
|
+
|
|
44
|
+
def upload_payload(self, payload: Any, key: str) -> bool:
|
|
45
|
+
"""
|
|
46
|
+
Upload a payload to S3 and compute its checksum.
|
|
47
|
+
|
|
48
|
+
The payload is first converted to JSON bytes, then stored in the
|
|
49
|
+
configured bucket under the given key and `application/json` content type.[web:51][web:54]
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
payload: Content to upload (dict, list, str, bytes, etc.).
|
|
53
|
+
key: Object key to use when storing the payload in S3.
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
S3TransferException: If serialization or S3 upload fails.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
S3Result containing the S3 object key and the computed checksum.
|
|
60
|
+
"""
|
|
61
|
+
try:
|
|
62
|
+
body = self._ensure_json_bytes(payload)
|
|
63
|
+
except Exception as e:
|
|
64
|
+
raise S3TransferException(f"Failed to serialize {key}", e)
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
self.s3_client.put_object(
|
|
68
|
+
Bucket=self.bucket_name,
|
|
69
|
+
Key=key,
|
|
70
|
+
Body=body,
|
|
71
|
+
ContentType="application/json",
|
|
72
|
+
)
|
|
73
|
+
except ClientError as e:
|
|
74
|
+
raise S3TransferException(f"S3 upload failed for {key}", e)
|
|
75
|
+
except Exception as e:
|
|
76
|
+
raise S3TransferException(f"Unexpected error uploading {key}", e)
|
|
77
|
+
|
|
78
|
+
return True
|
|
79
|
+
|
|
80
|
+
def download_payload(self, bucket_name: str, s3_key: str) -> str:
|
|
81
|
+
"""
|
|
82
|
+
Download a payload from S3 and return it as a UTF-8 string.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
bucket_name: Name of the S3 bucket where payload is stored.
|
|
86
|
+
s3_key: Object key of the payload to download.
|
|
87
|
+
|
|
88
|
+
Raises:
|
|
89
|
+
S3TransferException: If the key is missing, the object does not exist,
|
|
90
|
+
or the download/parsing process fails.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
The payload content decoded as a UTF-8 string.
|
|
94
|
+
"""
|
|
95
|
+
if not s3_key:
|
|
96
|
+
raise S3TransferException("s3_key is mandatory")
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
response = self.s3_client.get_object(Bucket=bucket_name, Key=s3_key)
|
|
100
|
+
body = response["Body"].read()
|
|
101
|
+
return body.decode("utf-8")
|
|
102
|
+
except ClientError as e:
|
|
103
|
+
raise S3TransferException(f"Failed to download {s3_key} from S3", e)
|
|
104
|
+
except UnicodeDecodeError as e:
|
|
105
|
+
raise S3TransferException(f"Failed to decode {s3_key} body as utf-8", e)
|
|
106
|
+
except Exception as e:
|
|
107
|
+
raise S3TransferException(f"Unexpected error downloading {s3_key}", e)
|
|
108
|
+
|
|
109
|
+
def delete_payload(self, bucket_name: str, s3_key: str) -> bool:
|
|
110
|
+
"""
|
|
111
|
+
Delete a payload from S3.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
bucket_name: Name of the S3 bucket where payload is stored.
|
|
115
|
+
s3_key: Object key of the payload to delete. If empty or None,
|
|
116
|
+
the method performs no action.
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
if not s3_key:
|
|
120
|
+
raise S3TransferException("s3_key is mandatory")
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
self.s3_client.delete_object(
|
|
124
|
+
Bucket=bucket_name,
|
|
125
|
+
Key=s3_key
|
|
126
|
+
)
|
|
127
|
+
return True
|
|
128
|
+
except ClientError:
|
|
129
|
+
return False
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "lcdp-boto3-utils"
|
|
3
|
+
# https://github.com/python-poetry/poetry/issues/1208
|
|
4
|
+
version = "1.6.0"
|
|
5
|
+
description = "Boto3 Utils"
|
|
6
|
+
authors = ["Le Comptoir Des Pharmacies <webmaster@lecomptoirdespharmacies.fr>"]
|
|
7
|
+
|
|
8
|
+
[tool.poetry-dynamic-versioning]
|
|
9
|
+
enable = false
|
|
10
|
+
vcs = "git"
|
|
11
|
+
|
|
12
|
+
[tool.poetry.requires-plugins]
|
|
13
|
+
poetry-dynamic-versioning = { version = ">=1.0.0,<2.0.0", extras = ["plugin"] }
|
|
14
|
+
|
|
15
|
+
[tool.poetry.dependencies]
|
|
16
|
+
python = ">=3.8"
|
|
17
|
+
boto3 = "^1.26.83"
|
|
18
|
+
|
|
19
|
+
[tool.poetry.dev-dependencies]
|
|
20
|
+
|
|
21
|
+
[build-system]
|
|
22
|
+
requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"]
|
|
23
|
+
build-backend = "poetry_dynamic_versioning.backend"
|