boto3-assist 0.32.0__py3-none-any.whl

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 (67) hide show
  1. boto3_assist/__init__.py +0 -0
  2. boto3_assist/aws_config.py +199 -0
  3. boto3_assist/aws_lambda/event_info.py +414 -0
  4. boto3_assist/aws_lambda/mock_context.py +5 -0
  5. boto3_assist/boto3session.py +87 -0
  6. boto3_assist/cloudwatch/cloudwatch_connection.py +84 -0
  7. boto3_assist/cloudwatch/cloudwatch_connection_tracker.py +17 -0
  8. boto3_assist/cloudwatch/cloudwatch_log_connection.py +62 -0
  9. boto3_assist/cloudwatch/cloudwatch_logs.py +39 -0
  10. boto3_assist/cloudwatch/cloudwatch_query.py +191 -0
  11. boto3_assist/cognito/cognito_authorizer.py +169 -0
  12. boto3_assist/cognito/cognito_connection.py +59 -0
  13. boto3_assist/cognito/cognito_utility.py +514 -0
  14. boto3_assist/cognito/jwks_cache.py +21 -0
  15. boto3_assist/cognito/user.py +27 -0
  16. boto3_assist/connection.py +146 -0
  17. boto3_assist/connection_tracker.py +120 -0
  18. boto3_assist/dynamodb/dynamodb.py +1206 -0
  19. boto3_assist/dynamodb/dynamodb_connection.py +113 -0
  20. boto3_assist/dynamodb/dynamodb_helpers.py +333 -0
  21. boto3_assist/dynamodb/dynamodb_importer.py +102 -0
  22. boto3_assist/dynamodb/dynamodb_index.py +507 -0
  23. boto3_assist/dynamodb/dynamodb_iservice.py +29 -0
  24. boto3_assist/dynamodb/dynamodb_key.py +130 -0
  25. boto3_assist/dynamodb/dynamodb_model_base.py +382 -0
  26. boto3_assist/dynamodb/dynamodb_model_base_interfaces.py +34 -0
  27. boto3_assist/dynamodb/dynamodb_re_indexer.py +165 -0
  28. boto3_assist/dynamodb/dynamodb_reindexer.py +165 -0
  29. boto3_assist/dynamodb/dynamodb_reserved_words.py +52 -0
  30. boto3_assist/dynamodb/dynamodb_reserved_words.txt +573 -0
  31. boto3_assist/dynamodb/readme.md +68 -0
  32. boto3_assist/dynamodb/troubleshooting.md +7 -0
  33. boto3_assist/ec2/ec2_connection.py +57 -0
  34. boto3_assist/environment_services/__init__.py +0 -0
  35. boto3_assist/environment_services/environment_loader.py +128 -0
  36. boto3_assist/environment_services/environment_variables.py +219 -0
  37. boto3_assist/erc/__init__.py +64 -0
  38. boto3_assist/erc/ecr_connection.py +57 -0
  39. boto3_assist/errors/custom_exceptions.py +46 -0
  40. boto3_assist/http_status_codes.py +80 -0
  41. boto3_assist/models/serializable_model.py +9 -0
  42. boto3_assist/role_assumption_mixin.py +38 -0
  43. boto3_assist/s3/s3.py +64 -0
  44. boto3_assist/s3/s3_bucket.py +67 -0
  45. boto3_assist/s3/s3_connection.py +76 -0
  46. boto3_assist/s3/s3_event_data.py +168 -0
  47. boto3_assist/s3/s3_object.py +695 -0
  48. boto3_assist/securityhub/securityhub.py +150 -0
  49. boto3_assist/securityhub/securityhub_connection.py +57 -0
  50. boto3_assist/session_setup_mixin.py +70 -0
  51. boto3_assist/ssm/connection.py +57 -0
  52. boto3_assist/ssm/parameter_store/parameter_store.py +116 -0
  53. boto3_assist/utilities/datetime_utility.py +349 -0
  54. boto3_assist/utilities/decimal_conversion_utility.py +140 -0
  55. boto3_assist/utilities/dictionary_utility.py +32 -0
  56. boto3_assist/utilities/file_operations.py +135 -0
  57. boto3_assist/utilities/http_utility.py +48 -0
  58. boto3_assist/utilities/logging_utility.py +0 -0
  59. boto3_assist/utilities/numbers_utility.py +329 -0
  60. boto3_assist/utilities/serialization_utility.py +664 -0
  61. boto3_assist/utilities/string_utility.py +337 -0
  62. boto3_assist/version.py +1 -0
  63. boto3_assist-0.32.0.dist-info/METADATA +76 -0
  64. boto3_assist-0.32.0.dist-info/RECORD +67 -0
  65. boto3_assist-0.32.0.dist-info/WHEEL +4 -0
  66. boto3_assist-0.32.0.dist-info/licenses/LICENSE-EXPLAINED.txt +11 -0
  67. boto3_assist-0.32.0.dist-info/licenses/LICENSE.txt +21 -0
@@ -0,0 +1,165 @@
1
+ """
2
+ Geek Cafe, LLC
3
+ Maintainers: Eric Wilson
4
+ MIT License. See Project Root for the license information.
5
+ """
6
+
7
+ import json
8
+ from typing import Any, Dict, Optional, List, Type
9
+ from aws_lambda_powertools import Logger
10
+ from boto3_assist.dynamodb.dynamodb import DynamoDB
11
+ from boto3_assist.dynamodb.dynamodb_model_base import DynamoDBModelBase
12
+ from boto3_assist.utilities.serialization_utility import Serialization
13
+ from boto3_assist.dynamodb.dynamodb_index import DynamoDBIndex
14
+ from boto3_assist.dynamodb.dynamodb_iservice import IDynamoDBService
15
+
16
+ logger = Logger()
17
+
18
+
19
+ class DynamoDBReindexer:
20
+ """Reindexing your database"""
21
+
22
+ def __init__(
23
+ self,
24
+ table_name: str,
25
+ *,
26
+ db: Optional[DynamoDB] = None,
27
+ aws_profile: Optional[str] = None,
28
+ aws_region: Optional[str] = None,
29
+ aws_end_point_url: Optional[str] = None,
30
+ aws_access_key_id: Optional[str] = None,
31
+ aws_secret_access_key: Optional[str] = None,
32
+ ):
33
+ self.table_name = table_name
34
+ self.db: DynamoDB = db or DynamoDB(
35
+ aws_profile=aws_profile,
36
+ aws_region=aws_region,
37
+ aws_end_point_url=aws_end_point_url,
38
+ aws_access_key_id=aws_access_key_id,
39
+ aws_secret_access_key=aws_secret_access_key,
40
+ )
41
+
42
+ def reindex_item(
43
+ self,
44
+ original_primary_key: dict,
45
+ model: DynamoDBModelBase,
46
+ *,
47
+ dry_run: bool = False,
48
+ inplace: bool = True,
49
+ leave_original_record: bool = False,
50
+ service_cls: Type[IDynamoDBService] | None,
51
+ ):
52
+ """
53
+ Reindex the record
54
+
55
+ Args:
56
+ original_primary_key (dict): The original primary key of the record to be reindexed.
57
+ This is either the partition_key or a composite key (partition_key, sort_key)
58
+ model (DynamoDBModelBase): A model instance that will be used to serialize the new keys
59
+ into a dictionary. It must inherit from DynamoDBModelBase
60
+
61
+ dry_run (bool, optional): Ability to log the actions without executing them. Defaults to False.
62
+ inplace (bool, optional): Ability to just update the indexes only.
63
+ No other fields will be updated, however you can't update the primary_key (partition/sort key)
64
+ with this action since they are immutable.
65
+ Defaults to True.
66
+ leave_original_record (bool, optional): _description_. Defaults to False.
67
+ """
68
+
69
+ if inplace:
70
+ keys: List[DynamoDBIndex] = model.list_keys()
71
+ # Update the item in DynamoDB with new keys
72
+ self.update_item_in_dynamodb(
73
+ original_primary_key=original_primary_key, keys=keys, dry_run=dry_run
74
+ )
75
+ # todo: add some additional error handling here and throw a more
76
+ # descriptive error if they try to use a different primary
77
+ # pk or sk, which you can't do. If that's the case
78
+ else:
79
+ # add the new one first and optionally delete the older one
80
+ # once we are succesfull
81
+ try:
82
+ # save the new one first
83
+ service_instance: Optional[IDynamoDBService] = (
84
+ service_cls(db=self.db) if callable(service_cls) else None
85
+ )
86
+
87
+ if service_instance:
88
+ service_instance.save(model=model)
89
+ else:
90
+ self.db.save(
91
+ item=model, table_name=self.table_name, source="reindex"
92
+ )
93
+
94
+ # then delete the old on
95
+ if not leave_original_record:
96
+ self.db.delete(
97
+ table_name=self.table_name, primary_key=original_primary_key
98
+ )
99
+ except Exception as e: # pylint: disable=broad-except
100
+ logger.error(str(e))
101
+ raise RuntimeError(str(e)) from e
102
+ # this gets a little more trick as we need to delete the item
103
+
104
+ def load_model(
105
+ self, db_item: dict, db_model: DynamoDBModelBase
106
+ ) -> DynamoDBModelBase | None:
107
+ """load the model which will serialze the dynamodb dictionary to an instance of an object"""
108
+
109
+ base_model = Serialization.map(db_item, db_model)
110
+ return base_model
111
+
112
+ def update_item_in_dynamodb(
113
+ self,
114
+ original_primary_key: dict,
115
+ keys: List[DynamoDBIndex],
116
+ dry_run: bool = False,
117
+ ):
118
+ """Update the dynamodb item"""
119
+ dictionary = self.db.helpers.keys_to_dictionary(keys=keys)
120
+
121
+ update_expression = self.build_update_expression(dictionary)
122
+ expression_attribute_values = self.build_expression_attribute_values(dictionary)
123
+
124
+ if not dry_run:
125
+ self.db.update_item(
126
+ table_name=self.table_name,
127
+ key=original_primary_key,
128
+ update_expression=update_expression,
129
+ expression_attribute_values=expression_attribute_values,
130
+ )
131
+ else:
132
+ print("Dry run: Skipping Update item")
133
+ print(f"{json.dumps(original_primary_key, indent=4)}")
134
+ print(f"{update_expression}")
135
+ print(f"{json.dumps(expression_attribute_values, indent=4)}")
136
+
137
+ def build_update_expression(self, updated_keys: Dict[str, Any]) -> str:
138
+ """
139
+ Build the expression for updating the item
140
+
141
+ Args:
142
+ updated_keys (Dict[str, Any]): _description_
143
+
144
+ Returns:
145
+ str: _description_
146
+ """
147
+ update_expression = "SET " + ", ".join(
148
+ f"{k} = :{k}" for k in updated_keys.keys()
149
+ )
150
+ return update_expression
151
+
152
+ def build_expression_attribute_values(
153
+ self, updated_keys: Dict[str, Any]
154
+ ) -> Dict[str, Any]:
155
+ """
156
+ Build the expression attribute values for the update expression
157
+
158
+ Args:
159
+ updated_keys (Dict[str, Any]): _description_
160
+
161
+ Returns:
162
+ Dict[str, Any]: _description_
163
+ """
164
+ expression_attribute_values = {f":{k}": v for k, v in updated_keys.items()}
165
+ return expression_attribute_values
@@ -0,0 +1,52 @@
1
+ import os
2
+ from typing import List
3
+
4
+
5
+ class DynamoDBReservedWords:
6
+ """
7
+ Reserved Words used by DynamoDB that can cause issues,
8
+ so they will need to be transformed under certain conditions, such as when doing projections
9
+ """
10
+
11
+ def __init__(self) -> None:
12
+ self.__list: List[str] = self.__read_list()
13
+
14
+ def words(self) -> List[str]:
15
+ """Gets a list of dynamodb reserved words"""
16
+ return self.__list
17
+
18
+ def __read_list(self) -> List[str]:
19
+ path = os.path.dirname(__file__)
20
+ path = os.path.join(path, "dynamodb_reserved_words.txt")
21
+ with open(path, "r", encoding="utf-8") as f:
22
+ words = f.read().splitlines()
23
+ # make sure they are all in uppercase
24
+ for i, word in enumerate(words):
25
+ words[i] = word.upper()
26
+ return words
27
+
28
+ def is_reserved_word(self, word: str) -> bool:
29
+ """Checks if a word is a dynamodb reserved word"""
30
+ return word.upper() in self.__list
31
+
32
+ def tranform_projections(self, projections: List[str] | str) -> List[str]:
33
+ """Transforms a list of projections to remove reserved words"""
34
+ if isinstance(projections, str):
35
+ projections = projections.split(",")
36
+
37
+ # any projection that exists add a # infront of it
38
+ projections = ["#" + p if self.is_reserved_word(p) else p for p in projections]
39
+ return projections
40
+
41
+ def transform_attributes(self, projections: List[str] | str) -> dict | None:
42
+ """Transforms a dict of attributes to remove reserved words"""
43
+ transformed_attributes: dict = {}
44
+ if isinstance(projections, str):
45
+ projections = projections.split(",")
46
+ for item in projections:
47
+ if self.is_reserved_word(item):
48
+ transformed_attributes["#" + item] = item
49
+
50
+ if len(transformed_attributes) > 0:
51
+ return transformed_attributes
52
+ return None