boto3-assist 0.6.0__tar.gz → 0.7.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.
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/.gitignore +2 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/.vscode/settings.json +1 -2
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/PKG-INFO +1 -1
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/pyproject.toml +1 -1
- boto3_assist-0.7.0/run_unit_tests.sh +22 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/connection_tracker.py +4 -4
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/models/serializable_model.py +2 -2
- boto3_assist-0.7.0/src/boto3_assist/s3/s3.py +64 -0
- boto3_assist-0.7.0/src/boto3_assist/s3/s3_bucket.py +67 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/s3/s3_connection.py +1 -4
- boto3_assist-0.6.0/src/boto3_assist/s3/s3.py → boto3_assist-0.7.0/src/boto3_assist/s3/s3_object.py +109 -47
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/utilities/serialization_utility.py +131 -11
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/utilities/string_utility.py +30 -0
- boto3_assist-0.7.0/src/boto3_assist/version.py +1 -0
- {boto3_assist-0.6.0/tests/utilities → boto3_assist-0.7.0/tests/__top}/__init__.py +12 -3
- {boto3_assist-0.6.0/tests/dynamodb/models → boto3_assist-0.7.0/tests/dynamodb/dbmodels}/cms/page.py +1 -1
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/tests/dynamodb/dynamodb_model_base_test.py +3 -4
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/tests/dynamodb/dynamodb_model_projections_test.py +2 -2
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/tests/dynamodb/dynamodb_model_serializtion_test.py +2 -2
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/tests/dynamodb/dynamodb_moto_sorting_test.py +3 -5
- boto3_assist-0.7.0/tests/models/serializable_model_wide_test.py +246 -0
- boto3_assist-0.7.0/tests/s3/s3_file_delete_test.py +123 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/tests/s3/s3_file_upload_test.py +8 -6
- boto3_assist-0.7.0/tests/utilities/__init__.py +0 -0
- boto3_assist-0.7.0/tests/utilities/string_utility_test.py +56 -0
- boto3_assist-0.6.0/src/boto3_assist/version.py +0 -1
- boto3_assist-0.6.0/tests/__top/__init__.py +0 -16
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/.env.docker +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/.env.docker.001 +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/.env.docker.nosql.workbench +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/.env.unittest +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/.vscode/launch.json +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/.vscode/tasks.json +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/LICENSE-EXPLAINED.txt +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/LICENSE.txt +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/README.md +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/aws_regions_with_status.csv +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/aws_regions_with_status.json +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/devops/build.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/devops/readme.md +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/examples/__init__.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/examples/cloudwatch/log_report.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/examples/dynamodb/models/order_item_model.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/examples/dynamodb/models/order_model.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/examples/dynamodb/models/product_model.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/examples/dynamodb/models/user_model.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/examples/dynamodb/models/user_post_model.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/examples/dynamodb/order_example/main.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/examples/dynamodb/order_example/products.json +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/examples/dynamodb/services/order_item_service.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/examples/dynamodb/services/order_service.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/examples/dynamodb/services/product_service.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/examples/dynamodb/services/table_service.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/examples/dynamodb/services/user_post_service.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/examples/dynamodb/services/user_service.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/examples/dynamodb/services/user_service_client_example.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/examples/dynamodb/services/user_service_resource_example.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/examples/dynamodb/user_post_example/main.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/examples/ec2/regions_report.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/module-headers.txt +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/mypy.ini +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/requirements-dev.txt +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/requirements.txt +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/run-checks.sh +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/__init__.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/aws_lambda/event_info.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/boto3session.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/cloudwatch/cloudwatch_connection.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/cloudwatch/cloudwatch_connection_tracker.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/cloudwatch/cloudwatch_log_connection.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/cloudwatch/cloudwatch_logs.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/cloudwatch/cloudwatch_query.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/cognito/cognito_authorizer.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/cognito/cognito_connection.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/cognito/cognito_utility.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/cognito/jwks_cache.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/cognito/user.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/connection.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/dynamodb/dynamodb.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/dynamodb/dynamodb_connection.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/dynamodb/dynamodb_helpers.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/dynamodb/dynamodb_importer.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/dynamodb/dynamodb_index.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/dynamodb/dynamodb_iservice.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/dynamodb/dynamodb_key.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/dynamodb/dynamodb_model_base.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/dynamodb/dynamodb_model_base_interfaces.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/dynamodb/dynamodb_reindexer.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/dynamodb/dynamodb_reserved_words.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/dynamodb/dynamodb_reserved_words.txt +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/dynamodb/readme.md +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/dynamodb/troubleshooting.md +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/ec2/ec2_connection.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/environment_services/__init__.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/environment_services/environment_loader.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/environment_services/environment_variables.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/errors/custom_exceptions.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/http_status_codes.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/utilities/datetime_utility.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/utilities/dictionaroy_utility.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/utilities/file_operations.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/utilities/http_utility.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/utilities/logging_utility.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/utilities/numbers_utility.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/tests/__init__.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/tests/dynamodb/__init__.py +0 -0
- {boto3_assist-0.6.0/tests/dynamodb/models → boto3_assist-0.7.0/tests/dynamodb/dbmodels}/cms/base.py +0 -0
- {boto3_assist-0.6.0/tests/dynamodb/models → boto3_assist-0.7.0/tests/dynamodb/dbmodels}/cms/content_block.py +0 -0
- {boto3_assist-0.6.0/tests/dynamodb/models → boto3_assist-0.7.0/tests/dynamodb/dbmodels}/cms/template.py +0 -0
- {boto3_assist-0.6.0/tests/dynamodb/models → boto3_assist-0.7.0/tests/dynamodb/dbmodels}/simple_model.py +0 -0
- {boto3_assist-0.6.0/tests/dynamodb/models → boto3_assist-0.7.0/tests/dynamodb/dbmodels}/user_model.py +0 -0
- {boto3_assist-0.6.0/tests/dynamodb/models → boto3_assist-0.7.0/tests/dynamodb/dbmodels}/user_required_fields_model.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/tests/dynamodb/dynamodb_reindex_test.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/tests/examples_test/__init__.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/tests/examples_test/user_service_test.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/tests/lambda/__init__.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/tests/lambda/event_info_test.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/tests/models/__init__.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/tests/models/serializable_model_test.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/tests/s3/__init__.py +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/tests/s3/files/test.txt +0 -0
- {boto3_assist-0.6.0 → boto3_assist-0.7.0}/tests/utilities/serialization_utility_test.py +0 -0
|
@@ -12,8 +12,7 @@
|
|
|
12
12
|
"python.analysis.extraPaths": [
|
|
13
13
|
"${workspaceFolder}",
|
|
14
14
|
"${workspaceFolder}/examples",
|
|
15
|
-
"${workspaceFolder}/src",
|
|
16
|
-
"${workspaceFolder}/src/boto3_assist",
|
|
15
|
+
"${workspaceFolder}/src",
|
|
17
16
|
"${workspaceFolder}/tests",
|
|
18
17
|
"${workspaceFolder}/devops",
|
|
19
18
|
"${workspaceFolder}/devops/cdk",
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
python --version
|
|
4
|
+
python -m venv .unittest
|
|
5
|
+
source ./.unittest/bin/activate
|
|
6
|
+
which python
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
pip install --upgrade pip
|
|
10
|
+
pip install -r ./requirements.txt
|
|
11
|
+
pip install -r ./requirements-dev.txt
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
echo "running unit test"
|
|
15
|
+
python -m unittest discover -s tests -p "*_test.py"
|
|
16
|
+
|
|
17
|
+
if [ $? -eq 0 ]; then
|
|
18
|
+
echo "Tests passed successfully"
|
|
19
|
+
else
|
|
20
|
+
echo "No tests found or tests failed"
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
@@ -67,8 +67,8 @@ class ConnectionTracker:
|
|
|
67
67
|
|
|
68
68
|
if not self.issue_stack_trace:
|
|
69
69
|
stack_trace_message = (
|
|
70
|
-
f"
|
|
71
|
-
f"set the environment variable {self.__stack_trace_env_var} to true
|
|
70
|
+
f"📄 NOTE: To add additional information 👀 to the log and determine where additional connections are being created: "
|
|
71
|
+
f"set the environment variable 👉{self.__stack_trace_env_var}👈 to true ✅. \n"
|
|
72
72
|
)
|
|
73
73
|
else:
|
|
74
74
|
stack = "\n".join(traceback.format_stack())
|
|
@@ -83,8 +83,8 @@ class ConnectionTracker:
|
|
|
83
83
|
"instead of creating a new one. Connections are expensive in terms of time and latency. "
|
|
84
84
|
"If you are seeing performance issues, check how and where you are creating your "
|
|
85
85
|
"connections. You should be able to pass the connection to your other objects "
|
|
86
|
-
"and reuse your boto3 connections."
|
|
87
|
-
"\n
|
|
86
|
+
"and reuse your boto3 connections. "
|
|
87
|
+
"\n🧪 MOCK Testing may show this message as well, in which case you can dismiss this warning.🧪\n"
|
|
88
88
|
f"{stack_trace_message}"
|
|
89
89
|
)
|
|
90
90
|
|
|
@@ -5,5 +5,5 @@ MIT License. See Project Root for the license information.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
from boto3_assist.utilities.serialization_utility import SerializableModel
|
|
8
|
+
|
|
9
|
+
from boto3_assist.utilities.serialization_utility import SerializableModel # noqa: F401 # pylint: disable=unused-import
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Geek Cafe, LLC
|
|
3
|
+
Maintainers: Eric Wilson
|
|
4
|
+
MIT License. See Project Root for the license information.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Optional, cast
|
|
8
|
+
|
|
9
|
+
from aws_lambda_powertools import Logger
|
|
10
|
+
|
|
11
|
+
from boto3_assist.s3.s3_connection import S3Connection
|
|
12
|
+
from boto3_assist.s3.s3_object import S3Object
|
|
13
|
+
from boto3_assist.s3.s3_bucket import S3Bucket
|
|
14
|
+
|
|
15
|
+
logger = Logger(child=True)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class S3(S3Connection):
|
|
19
|
+
"""Common S3 Actions"""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
*,
|
|
24
|
+
aws_profile: Optional[str] = None,
|
|
25
|
+
aws_region: Optional[str] = None,
|
|
26
|
+
aws_end_point_url: Optional[str] = None,
|
|
27
|
+
aws_access_key_id: Optional[str] = None,
|
|
28
|
+
aws_secret_access_key: Optional[str] = None,
|
|
29
|
+
) -> None:
|
|
30
|
+
"""_summary_
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
aws_profile (Optional[str], optional): _description_. Defaults to None.
|
|
34
|
+
aws_region (Optional[str], optional): _description_. Defaults to None.
|
|
35
|
+
aws_end_point_url (Optional[str], optional): _description_. Defaults to None.
|
|
36
|
+
aws_access_key_id (Optional[str], optional): _description_. Defaults to None.
|
|
37
|
+
aws_secret_access_key (Optional[str], optional): _description_. Defaults to None.
|
|
38
|
+
"""
|
|
39
|
+
super().__init__(
|
|
40
|
+
aws_profile=aws_profile,
|
|
41
|
+
aws_region=aws_region,
|
|
42
|
+
aws_end_point_url=aws_end_point_url,
|
|
43
|
+
aws_access_key_id=aws_access_key_id,
|
|
44
|
+
aws_secret_access_key=aws_secret_access_key,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
self.__s3_object: S3Object | None = None
|
|
48
|
+
self.__s3_bucket: S3Bucket | None = None
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def object(self) -> S3Object:
|
|
52
|
+
"""s3 object"""
|
|
53
|
+
if self.__s3_object is None:
|
|
54
|
+
connection = cast(S3Connection, self)
|
|
55
|
+
self.__s3_object = S3Object(connection)
|
|
56
|
+
return self.__s3_object
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def bucket(self) -> S3Bucket:
|
|
60
|
+
"""s3 bucket"""
|
|
61
|
+
if self.__s3_bucket is None:
|
|
62
|
+
connection = cast(S3Connection, self)
|
|
63
|
+
self.__s3_bucket = S3Bucket(connection)
|
|
64
|
+
return self.__s3_bucket
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Geek Cafe, LLC
|
|
3
|
+
Maintainers: Eric Wilson
|
|
4
|
+
MIT License. See Project Root for the license information.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Dict
|
|
8
|
+
|
|
9
|
+
from aws_lambda_powertools import Logger
|
|
10
|
+
from botocore.exceptions import ClientError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
from boto3_assist.s3.s3_connection import S3Connection
|
|
14
|
+
|
|
15
|
+
logger = Logger(child=True)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class S3Bucket:
|
|
19
|
+
"""Common S3 Actions"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, connection: S3Connection):
|
|
22
|
+
self.connection = connection or S3Connection()
|
|
23
|
+
|
|
24
|
+
def create(self, *, bucket_name: str) -> Dict[str, Any]:
|
|
25
|
+
"""
|
|
26
|
+
Create an S3 bucket
|
|
27
|
+
:param bucket_name: Bucket to create
|
|
28
|
+
:return: True if bucket is created, else False
|
|
29
|
+
"""
|
|
30
|
+
try:
|
|
31
|
+
response = self.connection.client.create_bucket(Bucket=bucket_name)
|
|
32
|
+
logger.info(f"Bucket {bucket_name} created")
|
|
33
|
+
|
|
34
|
+
return dict(response)
|
|
35
|
+
except ClientError as e:
|
|
36
|
+
logger.exception(e)
|
|
37
|
+
raise e
|
|
38
|
+
|
|
39
|
+
def enable_versioning(self, *, bucket_name: str) -> None:
|
|
40
|
+
"""
|
|
41
|
+
Enable versioning on an S3 bucket
|
|
42
|
+
:param bucket_name: Bucket to enable versioning on
|
|
43
|
+
:return: None
|
|
44
|
+
"""
|
|
45
|
+
try:
|
|
46
|
+
self.connection.client.put_bucket_versioning(
|
|
47
|
+
Bucket=bucket_name, VersioningConfiguration={"Status": "Enabled"}
|
|
48
|
+
)
|
|
49
|
+
logger.info(f"Versioning enabled on bucket {bucket_name}")
|
|
50
|
+
except ClientError as e:
|
|
51
|
+
logger.exception(e)
|
|
52
|
+
raise e
|
|
53
|
+
|
|
54
|
+
def disable_versioning(self, *, bucket_name: str) -> None:
|
|
55
|
+
"""
|
|
56
|
+
Disable versioning on an S3 bucket
|
|
57
|
+
:param bucket_name: Bucket to disable versioning on
|
|
58
|
+
:return: None
|
|
59
|
+
"""
|
|
60
|
+
try:
|
|
61
|
+
self.connection.client.put_bucket_versioning(
|
|
62
|
+
Bucket=bucket_name, VersioningConfiguration={"Status": "Suspended"}
|
|
63
|
+
)
|
|
64
|
+
logger.info(f"Versioning disabled on bucket {bucket_name}")
|
|
65
|
+
except ClientError as e:
|
|
66
|
+
logger.exception(e)
|
|
67
|
+
raise e
|
|
@@ -8,10 +8,7 @@ from typing import Optional
|
|
|
8
8
|
from typing import TYPE_CHECKING
|
|
9
9
|
|
|
10
10
|
from aws_lambda_powertools import Logger
|
|
11
|
-
|
|
12
|
-
from boto3_assist.environment_services.environment_variables import (
|
|
13
|
-
EnvironmentVariables,
|
|
14
|
-
)
|
|
11
|
+
|
|
15
12
|
from boto3_assist.connection import Connection
|
|
16
13
|
|
|
17
14
|
if TYPE_CHECKING:
|
boto3_assist-0.6.0/src/boto3_assist/s3/s3.py → boto3_assist-0.7.0/src/boto3_assist/s3/s3_object.py
RENAMED
|
@@ -8,7 +8,7 @@ import os
|
|
|
8
8
|
import tempfile
|
|
9
9
|
import time
|
|
10
10
|
import io
|
|
11
|
-
from typing import Any, Dict,
|
|
11
|
+
from typing import Any, Dict, Optional, List
|
|
12
12
|
|
|
13
13
|
from aws_lambda_powertools import Logger
|
|
14
14
|
from botocore.exceptions import ClientError
|
|
@@ -19,61 +19,100 @@ from boto3_assist.utilities.datetime_utility import DatetimeUtility
|
|
|
19
19
|
from boto3_assist.utilities.file_operations import FileOperations
|
|
20
20
|
from boto3_assist.utilities.http_utility import HttpUtility
|
|
21
21
|
|
|
22
|
+
|
|
22
23
|
logger = Logger(child=True)
|
|
23
24
|
|
|
24
25
|
|
|
25
|
-
class
|
|
26
|
-
"""
|
|
26
|
+
class S3Object:
|
|
27
|
+
"""S3 Object Actions"""
|
|
27
28
|
|
|
28
|
-
def __init__(
|
|
29
|
-
self
|
|
30
|
-
*,
|
|
31
|
-
aws_profile: Optional[str] = None,
|
|
32
|
-
aws_region: Optional[str] = None,
|
|
33
|
-
aws_end_point_url: Optional[str] = None,
|
|
34
|
-
aws_access_key_id: Optional[str] = None,
|
|
35
|
-
aws_secret_access_key: Optional[str] = None,
|
|
36
|
-
) -> None:
|
|
37
|
-
"""_summary_
|
|
29
|
+
def __init__(self, connection: S3Connection):
|
|
30
|
+
self.connection = connection or S3Connection()
|
|
38
31
|
|
|
39
|
-
|
|
40
|
-
aws_profile (Optional[str], optional): _description_. Defaults to None.
|
|
41
|
-
aws_region (Optional[str], optional): _description_. Defaults to None.
|
|
42
|
-
aws_end_point_url (Optional[str], optional): _description_. Defaults to None.
|
|
43
|
-
aws_access_key_id (Optional[str], optional): _description_. Defaults to None.
|
|
44
|
-
aws_secret_access_key (Optional[str], optional): _description_. Defaults to None.
|
|
32
|
+
def delete(self, *, bucket_name: str, key: str) -> Dict[str, Any]:
|
|
45
33
|
"""
|
|
46
|
-
|
|
47
|
-
aws_profile=aws_profile,
|
|
48
|
-
aws_region=aws_region,
|
|
49
|
-
aws_end_point_url=aws_end_point_url,
|
|
50
|
-
aws_access_key_id=aws_access_key_id,
|
|
51
|
-
aws_secret_access_key=aws_secret_access_key,
|
|
52
|
-
)
|
|
34
|
+
Deletes an object key
|
|
53
35
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
:param bucket_name: Bucket to create
|
|
58
|
-
:return: True if bucket is created, else False
|
|
36
|
+
Args:
|
|
37
|
+
bucket_name (str): The AWS Bucket Name
|
|
38
|
+
key (str): The Object Key
|
|
59
39
|
"""
|
|
40
|
+
s3 = self.connection.client
|
|
41
|
+
# see if the object exists
|
|
60
42
|
try:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
except
|
|
64
|
-
|
|
65
|
-
|
|
43
|
+
response = s3.head_object(Bucket=bucket_name, Key=key)
|
|
44
|
+
response = s3.delete_object(Bucket=bucket_name, Key=key)
|
|
45
|
+
except s3.exceptions.NoSuchKey:
|
|
46
|
+
response = {"ResponseMetadata": {"HTTPStatusCode": 404}}
|
|
47
|
+
except s3.exceptions.ClientError as e:
|
|
48
|
+
if e.response.get("Error", {}).get("Code") == "404":
|
|
49
|
+
response = {"ResponseMetadata": {"HTTPStatusCode": 404}}
|
|
50
|
+
else:
|
|
51
|
+
raise e
|
|
52
|
+
|
|
53
|
+
return dict(response)
|
|
54
|
+
|
|
55
|
+
def delete_all_versions(
|
|
56
|
+
self, *, bucket_name: str, key: str, include_deleted: bool = False
|
|
57
|
+
) -> List[str]:
|
|
58
|
+
"""
|
|
59
|
+
Deletes an object key and all the versions for that object key
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
bucket_name (str): The AWS Bucket Name
|
|
63
|
+
key (str): The Object Kuye
|
|
64
|
+
include_deleted (bool, optional): Should deleted files be removed as well.
|
|
65
|
+
If True it will look for the object keys with the deleted marker and remove it.
|
|
66
|
+
Defaults to False.
|
|
67
|
+
"""
|
|
68
|
+
s3 = self.connection.client
|
|
69
|
+
paginator = s3.get_paginator("list_object_versions")
|
|
70
|
+
files: List[str] = []
|
|
71
|
+
|
|
72
|
+
for page in paginator.paginate(Bucket=bucket_name, Prefix=key):
|
|
73
|
+
# Delete object versions
|
|
74
|
+
if "Versions" in page:
|
|
75
|
+
for version in page["Versions"]:
|
|
76
|
+
s3.delete_object(
|
|
77
|
+
Bucket=bucket_name,
|
|
78
|
+
Key=version["Key"],
|
|
79
|
+
VersionId=version["VersionId"],
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
files.append(f"{version['Key']} - {version['VersionId']}")
|
|
83
|
+
|
|
84
|
+
if include_deleted:
|
|
85
|
+
# delete a previous files that may have just been a soft delete.
|
|
86
|
+
if "DeleteMarkers" in page:
|
|
87
|
+
for marker in page["DeleteMarkers"]:
|
|
88
|
+
s3.delete_object(
|
|
89
|
+
Bucket=bucket_name,
|
|
90
|
+
Key=marker["Key"],
|
|
91
|
+
VersionId=marker["VersionId"],
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
files.append(
|
|
95
|
+
f"{marker['Key']}:{marker['VersionId']}:delete-marker"
|
|
96
|
+
)
|
|
97
|
+
else:
|
|
98
|
+
response = self.delete(bucket_name=bucket_name, key=key)
|
|
99
|
+
if response["ResponseMetadata"]["HTTPStatusCode"] == 404:
|
|
100
|
+
return files
|
|
101
|
+
|
|
102
|
+
files.append(key)
|
|
103
|
+
|
|
104
|
+
return files
|
|
66
105
|
|
|
67
106
|
def generate_presigned_url(
|
|
68
107
|
self,
|
|
69
108
|
*,
|
|
70
109
|
bucket_name: str,
|
|
71
110
|
key_path: str,
|
|
72
|
-
user_id: str,
|
|
73
111
|
file_name: str,
|
|
74
|
-
meta_data: dict
|
|
75
|
-
expiration=3600,
|
|
76
|
-
method_type="POST",
|
|
112
|
+
meta_data: Optional[dict] = None,
|
|
113
|
+
expiration: int = 3600,
|
|
114
|
+
method_type: str = "POST",
|
|
115
|
+
user_id: Optional[str] = None,
|
|
77
116
|
) -> Dict[str, Any]:
|
|
78
117
|
"""
|
|
79
118
|
Create a signed URL for uploading a file to S3.
|
|
@@ -109,7 +148,7 @@ class S3(S3Connection):
|
|
|
109
148
|
|
|
110
149
|
signed_url: str | Dict[str, Any]
|
|
111
150
|
if method_type == "PUT":
|
|
112
|
-
signed_url = self.client.generate_presigned_url(
|
|
151
|
+
signed_url = self.connection.client.generate_presigned_url(
|
|
113
152
|
"put_object",
|
|
114
153
|
Params={
|
|
115
154
|
"Bucket": f"{bucket_name}",
|
|
@@ -124,13 +163,13 @@ class S3(S3Connection):
|
|
|
124
163
|
ExpiresIn=expiration, # URL is valid for x seconds
|
|
125
164
|
)
|
|
126
165
|
elif method_type == "POST":
|
|
127
|
-
signed_url = self.client.generate_presigned_post(
|
|
166
|
+
signed_url = self.connection.client.generate_presigned_post(
|
|
128
167
|
bucket_name,
|
|
129
168
|
key,
|
|
130
169
|
ExpiresIn=expiration, # URL is valid for x seconds
|
|
131
170
|
)
|
|
132
171
|
elif method_type == "GET":
|
|
133
|
-
signed_url = self.client.generate_presigned_url(
|
|
172
|
+
signed_url = self.connection.client.generate_presigned_url(
|
|
134
173
|
"get_object",
|
|
135
174
|
Params={
|
|
136
175
|
"Bucket": f"{bucket_name}",
|
|
@@ -175,7 +214,7 @@ class S3(S3Connection):
|
|
|
175
214
|
file_obj: bytes = (
|
|
176
215
|
file_obj.encode("utf-8") if isinstance(file_obj, str) else file_obj
|
|
177
216
|
)
|
|
178
|
-
self.client.upload_fileobj(
|
|
217
|
+
self.connection.client.upload_fileobj(
|
|
179
218
|
Fileobj=io.BytesIO(file_obj), Bucket=bucket, Key=key
|
|
180
219
|
)
|
|
181
220
|
|
|
@@ -219,7 +258,7 @@ class S3(S3Connection):
|
|
|
219
258
|
}
|
|
220
259
|
)
|
|
221
260
|
try:
|
|
222
|
-
self.client.upload_file(local_file_path, bucket, key)
|
|
261
|
+
self.connection.client.upload_file(local_file_path, bucket, key)
|
|
223
262
|
|
|
224
263
|
except ClientError as ce:
|
|
225
264
|
error = {
|
|
@@ -394,7 +433,9 @@ class S3(S3Connection):
|
|
|
394
433
|
error = None
|
|
395
434
|
|
|
396
435
|
try:
|
|
397
|
-
response = dict(
|
|
436
|
+
response = dict(
|
|
437
|
+
self.connection.client.get_object(Bucket=bucket_name, Key=key)
|
|
438
|
+
)
|
|
398
439
|
|
|
399
440
|
logger.debug(
|
|
400
441
|
{"metric_filter": "s3_download_response", "response": str(response)}
|
|
@@ -465,7 +506,7 @@ class S3(S3Connection):
|
|
|
465
506
|
|
|
466
507
|
error: str | None = None
|
|
467
508
|
try:
|
|
468
|
-
self.client.download_file(bucket, key, local_path)
|
|
509
|
+
self.connection.client.download_file(bucket, key, local_path)
|
|
469
510
|
|
|
470
511
|
except Exception as e: # pylint: disable=W0718
|
|
471
512
|
error = str(e)
|
|
@@ -542,3 +583,24 @@ class S3(S3Connection):
|
|
|
542
583
|
Decodes bytes to a string
|
|
543
584
|
"""
|
|
544
585
|
return file_obj.decode(encoding=encoding, errors=errors)
|
|
586
|
+
|
|
587
|
+
def list_versions(self, bucket: str, prefix: str = "") -> List[str]:
|
|
588
|
+
"""
|
|
589
|
+
List all versions of objects in an S3 bucket with a given prefix.
|
|
590
|
+
|
|
591
|
+
Args:
|
|
592
|
+
bucket (str): The name of the S3 bucket.
|
|
593
|
+
prefix (str, optional): The prefix to filter objects by. Defaults to "".
|
|
594
|
+
|
|
595
|
+
Returns:
|
|
596
|
+
list: A list of dictionaries containing information about each object version.
|
|
597
|
+
"""
|
|
598
|
+
versions = []
|
|
599
|
+
paginator = self.connection.client.get_paginator("list_object_versions")
|
|
600
|
+
page_iterator = paginator.paginate(Bucket=bucket, Prefix=prefix)
|
|
601
|
+
|
|
602
|
+
for page in page_iterator:
|
|
603
|
+
if "Versions" in page:
|
|
604
|
+
versions.extend(page["Versions"])
|
|
605
|
+
|
|
606
|
+
return versions
|
{boto3_assist-0.6.0 → boto3_assist-0.7.0}/src/boto3_assist/utilities/serialization_utility.py
RENAMED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
"""Serialization Utility"""
|
|
2
2
|
|
|
3
|
-
from datetime import datetime
|
|
4
|
-
from decimal import Decimal
|
|
5
|
-
from typing import Dict, List, TypeVar, Any
|
|
6
|
-
import json
|
|
7
|
-
import jsons
|
|
8
3
|
import datetime as dt
|
|
9
4
|
import decimal
|
|
10
5
|
import inspect
|
|
6
|
+
import json
|
|
11
7
|
import uuid
|
|
12
|
-
from
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from decimal import Decimal
|
|
10
|
+
from typing import Any, Dict, List, TypeVar
|
|
13
11
|
|
|
12
|
+
from aws_lambda_powertools import Logger
|
|
14
13
|
|
|
15
14
|
T = TypeVar("T")
|
|
16
15
|
|
|
@@ -53,6 +52,15 @@ class SerializableModel:
|
|
|
53
52
|
instance=self, serialize_fn=lambda x: x, include_none=True
|
|
54
53
|
)
|
|
55
54
|
|
|
55
|
+
def to_wide_dictionary(self) -> Dict:
|
|
56
|
+
"""
|
|
57
|
+
Dumps an object to dictionary structure
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
dump = Serialization.to_wide_dictionary(model=self)
|
|
61
|
+
|
|
62
|
+
return dump
|
|
63
|
+
|
|
56
64
|
|
|
57
65
|
class JsonEncoder(json.JSONEncoder):
|
|
58
66
|
"""
|
|
@@ -108,6 +116,32 @@ class Serialization:
|
|
|
108
116
|
|
|
109
117
|
return dump
|
|
110
118
|
|
|
119
|
+
@staticmethod
|
|
120
|
+
def to_wide_dictionary(model: object) -> Dict:
|
|
121
|
+
"""
|
|
122
|
+
Dumps an object to dictionary structure
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
dump = Serialization.to_dict(
|
|
126
|
+
instance=model, serialize_fn=lambda x: x, include_none=True
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# have a dictionary now let's flatten out
|
|
130
|
+
flat_dict = {}
|
|
131
|
+
for key, value in dump.items():
|
|
132
|
+
if isinstance(value, dict):
|
|
133
|
+
for sub_key, sub_value in value.items():
|
|
134
|
+
flat_dict[f"{key}_{sub_key}"] = sub_value
|
|
135
|
+
elif isinstance(value, list):
|
|
136
|
+
for i, sub_value in enumerate(value):
|
|
137
|
+
sub_dict = Serialization.to_wide_dictionary(sub_value)
|
|
138
|
+
for sub_key, sub_value in sub_dict.items():
|
|
139
|
+
flat_dict[f"{key}_{i}_{sub_key}"] = sub_value
|
|
140
|
+
else:
|
|
141
|
+
flat_dict[key] = value
|
|
142
|
+
|
|
143
|
+
return flat_dict
|
|
144
|
+
|
|
111
145
|
@staticmethod
|
|
112
146
|
def map(source: object, target: T, coerce: bool = True) -> T | None:
|
|
113
147
|
"""Map an object from one object to another"""
|
|
@@ -118,12 +152,92 @@ class Serialization:
|
|
|
118
152
|
source_dict = Serialization.convert_object_to_dict(source)
|
|
119
153
|
if not isinstance(source_dict, dict):
|
|
120
154
|
return None
|
|
121
|
-
return Serialization.
|
|
155
|
+
return Serialization._load_properties(
|
|
122
156
|
source=source_dict, target=target, coerce=coerce
|
|
123
157
|
)
|
|
124
158
|
|
|
125
159
|
@staticmethod
|
|
126
|
-
def
|
|
160
|
+
def to_wide_dictionary_list(
|
|
161
|
+
data: Dict[str, Any] | List[Dict[str, Any]],
|
|
162
|
+
remove_collisions: bool = True,
|
|
163
|
+
raise_error_on_collision: bool = False,
|
|
164
|
+
) -> List[Dict[str, Any]]:
|
|
165
|
+
"""
|
|
166
|
+
Converts a dictionary or list of dictionaries to a list of dictionaries.
|
|
167
|
+
|
|
168
|
+
:param data: Dictionary or list of dictionaries to be converted
|
|
169
|
+
:param remove_collisions: If True, removes duplicate keys from the dictionaries
|
|
170
|
+
:return: List of dictionaries
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
collisions = []
|
|
174
|
+
|
|
175
|
+
def recursive_flatten(prefix, obj):
|
|
176
|
+
"""
|
|
177
|
+
Recursively flattens a JSON object.
|
|
178
|
+
|
|
179
|
+
:param prefix: Current key prefix
|
|
180
|
+
:param obj: Object to flatten
|
|
181
|
+
:return: List of flattened dictionaries
|
|
182
|
+
"""
|
|
183
|
+
if isinstance(obj, list):
|
|
184
|
+
result = []
|
|
185
|
+
for _, item in enumerate(obj):
|
|
186
|
+
x = recursive_flatten("", item)
|
|
187
|
+
result.extend(x)
|
|
188
|
+
return result
|
|
189
|
+
elif isinstance(obj, dict):
|
|
190
|
+
result = [{}]
|
|
191
|
+
for key, value in obj.items():
|
|
192
|
+
sub_result = recursive_flatten(
|
|
193
|
+
f"{prefix}_{key}" if prefix else key, value
|
|
194
|
+
)
|
|
195
|
+
new_result = []
|
|
196
|
+
for entry in result:
|
|
197
|
+
for sub_entry in sub_result:
|
|
198
|
+
# remove any collisions
|
|
199
|
+
|
|
200
|
+
for k in entry:
|
|
201
|
+
if k in sub_entry:
|
|
202
|
+
if k not in collisions:
|
|
203
|
+
logger.debug(f"Collision detected: {k}")
|
|
204
|
+
collisions.append(k)
|
|
205
|
+
merged = entry.copy()
|
|
206
|
+
merged.update(sub_entry)
|
|
207
|
+
new_result.append(merged)
|
|
208
|
+
result = new_result
|
|
209
|
+
return result
|
|
210
|
+
else:
|
|
211
|
+
return [{prefix: obj}] if prefix else []
|
|
212
|
+
|
|
213
|
+
results = recursive_flatten("", data)
|
|
214
|
+
if remove_collisions:
|
|
215
|
+
results = Serialization.remove_collisions(results, collisions)
|
|
216
|
+
|
|
217
|
+
if raise_error_on_collision and len(collisions) > 0:
|
|
218
|
+
raise ValueError(f"Duplicate keys detected: {collisions}")
|
|
219
|
+
|
|
220
|
+
return results
|
|
221
|
+
|
|
222
|
+
@staticmethod
|
|
223
|
+
def remove_collisions(
|
|
224
|
+
data: List[Dict[str, Any]], collisions: List[str]
|
|
225
|
+
) -> List[Dict[str, Any]]:
|
|
226
|
+
"""
|
|
227
|
+
Removes collisions from a list of dictionaries.
|
|
228
|
+
|
|
229
|
+
:param data: List of dictionaries
|
|
230
|
+
:param collisions: List of collision keys
|
|
231
|
+
:return: List of dictionaries with collisions removed
|
|
232
|
+
"""
|
|
233
|
+
for c in collisions:
|
|
234
|
+
for r in data:
|
|
235
|
+
if c in r:
|
|
236
|
+
del r[c]
|
|
237
|
+
return data
|
|
238
|
+
|
|
239
|
+
@staticmethod
|
|
240
|
+
def _load_properties(
|
|
127
241
|
source: dict,
|
|
128
242
|
target: T,
|
|
129
243
|
coerce: bool = True,
|
|
@@ -183,9 +297,9 @@ class Serialization:
|
|
|
183
297
|
attr.clear()
|
|
184
298
|
attr.extend(value)
|
|
185
299
|
elif isinstance(attr, dict) and isinstance(value, dict):
|
|
186
|
-
Serialization.
|
|
300
|
+
Serialization._load_properties(value, attr, coerce=coerce)
|
|
187
301
|
elif hasattr(attr, "__dict__") and isinstance(value, dict):
|
|
188
|
-
Serialization.
|
|
302
|
+
Serialization._load_properties(value, attr, coerce=coerce)
|
|
189
303
|
else:
|
|
190
304
|
setattr(target, key, value)
|
|
191
305
|
except ValueError as e:
|
|
@@ -230,12 +344,18 @@ class Serialization:
|
|
|
230
344
|
|
|
231
345
|
@staticmethod
|
|
232
346
|
def to_dict(
|
|
233
|
-
instance: SerializableModel,
|
|
347
|
+
instance: SerializableModel | dict,
|
|
234
348
|
serialize_fn,
|
|
235
349
|
include_none: bool = True,
|
|
236
350
|
) -> Dict[str, Any]:
|
|
237
351
|
"""To Dict / Dictionary"""
|
|
238
352
|
|
|
353
|
+
if instance is None:
|
|
354
|
+
return {}
|
|
355
|
+
|
|
356
|
+
if isinstance(instance, dict):
|
|
357
|
+
return instance
|
|
358
|
+
|
|
239
359
|
def is_primitive(value):
|
|
240
360
|
"""Check if the value is a primitive data type."""
|
|
241
361
|
return isinstance(value, (str, int, bool, type(None)))
|