boto3-assist 0.6.1__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.1 → boto3_assist-0.7.0}/PKG-INFO +1 -1
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/pyproject.toml +1 -1
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/connection_tracker.py +4 -4
- 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.1 → boto3_assist-0.7.0}/src/boto3_assist/s3/s3_connection.py +1 -4
- boto3_assist-0.6.1/src/boto3_assist/s3/s3.py → boto3_assist-0.7.0/src/boto3_assist/s3/s3_object.py +109 -47
- {boto3_assist-0.6.1 → 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.1 → boto3_assist-0.7.0}/tests/__top/__init__.py +12 -6
- boto3_assist-0.7.0/tests/s3/s3_file_delete_test.py +123 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/tests/s3/s3_file_upload_test.py +8 -6
- boto3_assist-0.7.0/tests/utilities/string_utility_test.py +56 -0
- boto3_assist-0.6.1/src/boto3_assist/version.py +0 -1
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/.env.docker +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/.env.docker.001 +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/.env.docker.nosql.workbench +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/.env.unittest +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/.gitignore +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/.vscode/launch.json +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/.vscode/settings.json +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/.vscode/tasks.json +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/LICENSE-EXPLAINED.txt +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/LICENSE.txt +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/README.md +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/aws_regions_with_status.csv +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/aws_regions_with_status.json +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/devops/build.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/devops/readme.md +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/examples/__init__.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/examples/cloudwatch/log_report.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/examples/dynamodb/models/order_item_model.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/examples/dynamodb/models/order_model.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/examples/dynamodb/models/product_model.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/examples/dynamodb/models/user_model.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/examples/dynamodb/models/user_post_model.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/examples/dynamodb/order_example/main.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/examples/dynamodb/order_example/products.json +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/examples/dynamodb/services/order_item_service.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/examples/dynamodb/services/order_service.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/examples/dynamodb/services/product_service.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/examples/dynamodb/services/table_service.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/examples/dynamodb/services/user_post_service.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/examples/dynamodb/services/user_service.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/examples/dynamodb/services/user_service_client_example.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/examples/dynamodb/services/user_service_resource_example.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/examples/dynamodb/user_post_example/main.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/examples/ec2/regions_report.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/module-headers.txt +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/mypy.ini +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/requirements-dev.txt +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/requirements.txt +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/run-checks.sh +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/run_unit_tests.sh +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/__init__.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/aws_lambda/event_info.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/boto3session.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/cloudwatch/cloudwatch_connection.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/cloudwatch/cloudwatch_connection_tracker.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/cloudwatch/cloudwatch_log_connection.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/cloudwatch/cloudwatch_logs.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/cloudwatch/cloudwatch_query.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/cognito/cognito_authorizer.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/cognito/cognito_connection.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/cognito/cognito_utility.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/cognito/jwks_cache.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/cognito/user.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/connection.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/dynamodb/dynamodb.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/dynamodb/dynamodb_connection.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/dynamodb/dynamodb_helpers.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/dynamodb/dynamodb_importer.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/dynamodb/dynamodb_index.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/dynamodb/dynamodb_iservice.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/dynamodb/dynamodb_key.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/dynamodb/dynamodb_model_base.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/dynamodb/dynamodb_model_base_interfaces.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/dynamodb/dynamodb_reindexer.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/dynamodb/dynamodb_reserved_words.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/dynamodb/dynamodb_reserved_words.txt +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/dynamodb/readme.md +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/dynamodb/troubleshooting.md +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/ec2/ec2_connection.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/environment_services/__init__.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/environment_services/environment_loader.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/environment_services/environment_variables.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/errors/custom_exceptions.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/http_status_codes.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/models/serializable_model.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/utilities/datetime_utility.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/utilities/dictionaroy_utility.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/utilities/file_operations.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/utilities/http_utility.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/utilities/logging_utility.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/utilities/numbers_utility.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/utilities/serialization_utility.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/tests/__init__.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/tests/dynamodb/__init__.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/tests/dynamodb/dbmodels/cms/base.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/tests/dynamodb/dbmodels/cms/content_block.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/tests/dynamodb/dbmodels/cms/page.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/tests/dynamodb/dbmodels/cms/template.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/tests/dynamodb/dbmodels/simple_model.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/tests/dynamodb/dbmodels/user_model.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/tests/dynamodb/dbmodels/user_required_fields_model.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/tests/dynamodb/dynamodb_model_base_test.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/tests/dynamodb/dynamodb_model_projections_test.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/tests/dynamodb/dynamodb_model_serializtion_test.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/tests/dynamodb/dynamodb_moto_sorting_test.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/tests/dynamodb/dynamodb_reindex_test.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/tests/examples_test/__init__.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/tests/examples_test/user_service_test.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/tests/lambda/__init__.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/tests/lambda/event_info_test.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/tests/models/__init__.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/tests/models/serializable_model_test.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/tests/models/serializable_model_wide_test.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/tests/s3/__init__.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/tests/s3/files/test.txt +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/tests/utilities/__init__.py +0 -0
- {boto3_assist-0.6.1 → boto3_assist-0.7.0}/tests/utilities/serialization_utility_test.py +0 -0
|
@@ -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
|
|
|
@@ -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.1/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
|
|
@@ -195,6 +195,36 @@ class StringUtility:
|
|
|
195
195
|
hash_object.update(encoded_string)
|
|
196
196
|
return hash_object.hexdigest()
|
|
197
197
|
|
|
198
|
+
@staticmethod
|
|
199
|
+
def generate_idempotent_uuid(
|
|
200
|
+
namespace: uuid.UUID | str, unique_string: str, case_sensitive: bool = False
|
|
201
|
+
) -> str:
|
|
202
|
+
"""
|
|
203
|
+
Generates an idempotnent UUID, which is useful for creates
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
namespace (GUID | str): A namespace for your id, it must be a UUID or a string in a UUID format
|
|
207
|
+
unique_string (str): A unique string like an email address, a tenant name.
|
|
208
|
+
Use a combination for more granularity:
|
|
209
|
+
tenant-name:email
|
|
210
|
+
vendor:product-name
|
|
211
|
+
vendor:product-id
|
|
212
|
+
etc
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
str: a string representation of a UUID
|
|
216
|
+
"""
|
|
217
|
+
if isinstance(namespace, str):
|
|
218
|
+
namespace = uuid.UUID(namespace)
|
|
219
|
+
|
|
220
|
+
if not unique_string:
|
|
221
|
+
raise ValueError("unique_string cannot be empty")
|
|
222
|
+
|
|
223
|
+
if not case_sensitive:
|
|
224
|
+
unique_string = unique_string.lower()
|
|
225
|
+
|
|
226
|
+
return str(uuid.uuid5(namespace, unique_string))
|
|
227
|
+
|
|
198
228
|
@staticmethod
|
|
199
229
|
def get_size_in_kb(input_string: str | dict) -> float:
|
|
200
230
|
"""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '0.7.0'
|
|
@@ -8,12 +8,18 @@ import os
|
|
|
8
8
|
import sys
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
VERBOSE: bool = os.getenv("VERBOSE") or False
|
|
12
|
+
|
|
13
|
+
if VERBOSE:
|
|
14
|
+
print("👋 init test paths for __top")
|
|
15
|
+
|
|
16
|
+
|
|
13
17
|
root_directory = Path(__file__).resolve().parent.parent.parent
|
|
14
18
|
src_directory = os.path.join(root_directory, "src")
|
|
15
|
-
|
|
19
|
+
# inject src path to python search path
|
|
16
20
|
sys.path.insert(0, src_directory)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
print(
|
|
21
|
+
|
|
22
|
+
if VERBOSE:
|
|
23
|
+
print("")
|
|
24
|
+
for p in sys.path:
|
|
25
|
+
print(f"👉 {p}")
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import unittest
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import moto
|
|
6
|
+
|
|
7
|
+
from boto3_assist.environment_services.environment_loader import EnvironmentLoader
|
|
8
|
+
from boto3_assist.s3.s3 import S3
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@moto.mock_aws
|
|
12
|
+
class S3FileDeleteTest(unittest.TestCase):
|
|
13
|
+
"""Test S3 File Upload"""
|
|
14
|
+
|
|
15
|
+
def setUp(self):
|
|
16
|
+
"""Setup"""
|
|
17
|
+
ev: EnvironmentLoader = EnvironmentLoader()
|
|
18
|
+
# NOTE: you need to make sure the the env file below exists or you will get an error
|
|
19
|
+
ev.load_environment_file(file_name=".env.unittest")
|
|
20
|
+
|
|
21
|
+
def test_delete_file(self):
|
|
22
|
+
"""Test uploading a file"""
|
|
23
|
+
s3 = S3()
|
|
24
|
+
bucket_name: str = "test-bucket"
|
|
25
|
+
s3.bucket.create(bucket_name=bucket_name)
|
|
26
|
+
s3.bucket.enable_versioning(bucket_name=bucket_name)
|
|
27
|
+
local_file_path: Path = Path(
|
|
28
|
+
os.path.join(os.path.dirname(__file__), "files", "test.txt")
|
|
29
|
+
)
|
|
30
|
+
if not os.path.exists(local_file_path):
|
|
31
|
+
raise FileNotFoundError(f"File not found: {local_file_path}")
|
|
32
|
+
|
|
33
|
+
for _ in range(0, 5):
|
|
34
|
+
# upload the same file over and over, it should create different versions
|
|
35
|
+
s3.object.upload_file(
|
|
36
|
+
bucket=bucket_name, key="test.txt", local_file_path=local_file_path
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
files = s3.object.delete_all_versions(bucket_name=bucket_name, key="test.txt")
|
|
40
|
+
|
|
41
|
+
self.assertEqual(len(files), 5)
|
|
42
|
+
|
|
43
|
+
def test_delete_file_including_delete_markers(self):
|
|
44
|
+
"""Test uploading a file"""
|
|
45
|
+
s3 = S3()
|
|
46
|
+
bucket_name: str = "unittest-bucket"
|
|
47
|
+
test_file_name = "test.txt"
|
|
48
|
+
key = "test_with_delete.txt"
|
|
49
|
+
s3.bucket.create(bucket_name=bucket_name)
|
|
50
|
+
s3.bucket.enable_versioning(bucket_name=bucket_name)
|
|
51
|
+
local_file_path: Path = Path(
|
|
52
|
+
os.path.join(os.path.dirname(__file__), "files", test_file_name)
|
|
53
|
+
)
|
|
54
|
+
if not os.path.exists(local_file_path):
|
|
55
|
+
raise FileNotFoundError(f"File not found: {local_file_path}")
|
|
56
|
+
|
|
57
|
+
for _ in range(0, 5):
|
|
58
|
+
# upload the same file over and over, it should create different versions
|
|
59
|
+
s3.object.upload_file(
|
|
60
|
+
bucket=bucket_name, key=key, local_file_path=local_file_path
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
files = s3.object.delete_all_versions(
|
|
64
|
+
bucket_name=bucket_name, key=key, include_deleted=True
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# we should have ten here, the original 5 and the deleted 5
|
|
68
|
+
self.assertEqual(len(files), 5)
|
|
69
|
+
|
|
70
|
+
files = s3.object.delete_all_versions(
|
|
71
|
+
bucket_name=bucket_name, key=key, include_deleted=True
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# we shouldn't have any more files here
|
|
75
|
+
self.assertEqual(len(files), 0)
|
|
76
|
+
|
|
77
|
+
def test_delete_file_including_delete_markers_2(self):
|
|
78
|
+
"""Test uploading a file"""
|
|
79
|
+
s3 = S3()
|
|
80
|
+
bucket_name: str = "unittest-bucket"
|
|
81
|
+
test_file_name = "test.txt"
|
|
82
|
+
key = "test_with_delete.txt"
|
|
83
|
+
|
|
84
|
+
s3.bucket.create(bucket_name=bucket_name)
|
|
85
|
+
|
|
86
|
+
s3.bucket.enable_versioning(bucket_name=bucket_name)
|
|
87
|
+
local_file_path: Path = Path(
|
|
88
|
+
os.path.join(os.path.dirname(__file__), "files", test_file_name)
|
|
89
|
+
)
|
|
90
|
+
if not os.path.exists(local_file_path):
|
|
91
|
+
raise FileNotFoundError(f"File not found: {local_file_path}")
|
|
92
|
+
|
|
93
|
+
for _ in range(0, 5):
|
|
94
|
+
# upload the same file over and over, it should create different versions
|
|
95
|
+
s3.object.upload_file(
|
|
96
|
+
bucket=bucket_name, key=key, local_file_path=local_file_path
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# delete the latest which should add a deleted marker
|
|
100
|
+
s3.object.delete(bucket_name=bucket_name, key=key)
|
|
101
|
+
|
|
102
|
+
files = s3.object.delete_all_versions(
|
|
103
|
+
bucket_name=bucket_name, key=key, include_deleted=True
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# we should have ten here, the original 5 and the 1 deleted marker
|
|
107
|
+
self.assertEqual(len(files), 6)
|
|
108
|
+
|
|
109
|
+
files = s3.object.delete_all_versions(
|
|
110
|
+
bucket_name=bucket_name, key=key, include_deleted=True
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# we shouldn't have any more files here
|
|
114
|
+
self.assertEqual(len(files), 0)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def main():
|
|
118
|
+
"""Main"""
|
|
119
|
+
unittest.main()
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
if __name__ == "__main__":
|
|
123
|
+
main()
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import unittest
|
|
3
|
-
import moto
|
|
4
3
|
from pathlib import Path
|
|
5
|
-
|
|
4
|
+
|
|
5
|
+
import moto
|
|
6
|
+
|
|
6
7
|
from boto3_assist.environment_services.environment_loader import EnvironmentLoader
|
|
8
|
+
from boto3_assist.s3.s3 import S3
|
|
7
9
|
from boto3_assist.utilities.file_operations import FileOperations
|
|
8
10
|
|
|
9
11
|
|
|
@@ -21,14 +23,14 @@ class S3FileUploadTest(unittest.TestCase):
|
|
|
21
23
|
"""Test uploading a file"""
|
|
22
24
|
s3 = S3()
|
|
23
25
|
bucket_name: str = "test-bucket"
|
|
24
|
-
s3.
|
|
26
|
+
s3.bucket.create(bucket_name=bucket_name)
|
|
25
27
|
local_file_path: Path = Path(
|
|
26
28
|
os.path.join(os.path.dirname(__file__), "files", "test.txt")
|
|
27
29
|
)
|
|
28
30
|
if not os.path.exists(local_file_path):
|
|
29
31
|
raise FileNotFoundError(f"File not found: {local_file_path}")
|
|
30
32
|
|
|
31
|
-
s3.upload_file(
|
|
33
|
+
s3.object.upload_file(
|
|
32
34
|
bucket=bucket_name, key="test.txt", local_file_path=local_file_path
|
|
33
35
|
)
|
|
34
36
|
|
|
@@ -36,7 +38,7 @@ class S3FileUploadTest(unittest.TestCase):
|
|
|
36
38
|
"""Test uploading a file"""
|
|
37
39
|
s3 = S3()
|
|
38
40
|
bucket_name: str = "test-bucket"
|
|
39
|
-
s3.
|
|
41
|
+
s3.bucket.create(bucket_name=bucket_name)
|
|
40
42
|
local_file_path: Path = Path(
|
|
41
43
|
os.path.join(os.path.dirname(__file__), "files", "test.txt")
|
|
42
44
|
)
|
|
@@ -45,4 +47,4 @@ class S3FileUploadTest(unittest.TestCase):
|
|
|
45
47
|
|
|
46
48
|
data = FileOperations.read_file(local_file_path)
|
|
47
49
|
|
|
48
|
-
s3.upload_file_obj(bucket=bucket_name, key="test.txt", file_obj=data)
|
|
50
|
+
s3.object.upload_file_obj(bucket=bucket_name, key="test.txt", file_obj=data)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Geek Cafe, LLC
|
|
3
|
+
Maintainers: Eric Wilson
|
|
4
|
+
MIT License. See Project Root for the license information.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import unittest
|
|
8
|
+
from datetime import datetime, UTC
|
|
9
|
+
from datetime import timedelta
|
|
10
|
+
from typing import cast
|
|
11
|
+
|
|
12
|
+
from boto3_assist.utilities.string_utility import StringUtility
|
|
13
|
+
import uuid
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class StringUtilityUnitTest(unittest.TestCase):
|
|
17
|
+
"String Utility Tests"
|
|
18
|
+
|
|
19
|
+
def test_uuid_idempotency(self):
|
|
20
|
+
"""Testing Idempotnent UUID generation."""
|
|
21
|
+
# must be consistent
|
|
22
|
+
namespace: uuid.UUID = uuid.UUID("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
|
|
23
|
+
|
|
24
|
+
idempotent_id_john_smith: str = StringUtility.generate_idempotent_uuid(
|
|
25
|
+
str(namespace), "tenant-one:john.smith@tenant-one.com"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# should always get this GUID
|
|
29
|
+
self.assertEqual(
|
|
30
|
+
idempotent_id_john_smith, "3e68597f-3a32-5f82-a028-010f34eccfe6"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
idempotent_id_john_smith_camel: str = StringUtility.generate_idempotent_uuid(
|
|
34
|
+
namespace, "tenant-one:John.Smith@tenant-one.com", case_sensitive=True
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# should always get this GUID
|
|
38
|
+
self.assertEqual(
|
|
39
|
+
idempotent_id_john_smith_camel, "06daa067-77e1-56c1-80d5-8ee8306d0298"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
idempotent_id_john_smith_case_insensitive: str = (
|
|
43
|
+
StringUtility.generate_idempotent_uuid(
|
|
44
|
+
namespace,
|
|
45
|
+
"tenant-one:John.Smith@tenant-one.com",
|
|
46
|
+
case_sensitive=False,
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
self.assertEqual(
|
|
50
|
+
idempotent_id_john_smith_case_insensitive,
|
|
51
|
+
"3e68597f-3a32-5f82-a028-010f34eccfe6",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
self.assertEqual(
|
|
55
|
+
idempotent_id_john_smith, idempotent_id_john_smith_case_insensitive
|
|
56
|
+
)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = '0.6.1'
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{boto3_assist-0.6.1 → boto3_assist-0.7.0}/examples/dynamodb/services/user_service_client_example.py
RENAMED
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/cloudwatch/cloudwatch_connection.py
RENAMED
|
File without changes
|
|
File without changes
|
{boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/cloudwatch/cloudwatch_log_connection.py
RENAMED
|
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
|
|
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
|
|
File without changes
|
{boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/dynamodb/dynamodb_reserved_words.py
RENAMED
|
File without changes
|
{boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/dynamodb/dynamodb_reserved_words.txt
RENAMED
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{boto3_assist-0.6.1 → boto3_assist-0.7.0}/src/boto3_assist/utilities/serialization_utility.py
RENAMED
|
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
|
{boto3_assist-0.6.1 → boto3_assist-0.7.0}/tests/dynamodb/dbmodels/user_required_fields_model.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{boto3_assist-0.6.1 → boto3_assist-0.7.0}/tests/dynamodb/dynamodb_model_serializtion_test.py
RENAMED
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|