boto3-assist 0.10.0__tar.gz → 0.12.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/.vscode/settings.json +3 -3
  2. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/PKG-INFO +9 -2
  3. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/README.md +4 -0
  4. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/pyproject.toml +9 -2
  5. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/requirements-dev.txt +2 -1
  6. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/boto3session.py +23 -13
  7. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/cloudwatch/cloudwatch_connection_tracker.py +1 -1
  8. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/cognito/cognito_utility.py +13 -13
  9. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/connection.py +3 -1
  10. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/dynamodb/dynamodb.py +2 -4
  11. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/dynamodb/dynamodb_connection.py +2 -0
  12. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/dynamodb/dynamodb_model_base.py +1 -0
  13. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/s3/s3_event_data.py +7 -7
  14. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/s3/s3_object.py +2 -2
  15. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/securityhub/securityhub.py +2 -2
  16. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/utilities/serialization_utility.py +9 -4
  17. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/utilities/string_utility.py +1 -0
  18. boto3_assist-0.12.0/src/boto3_assist/version.py +1 -0
  19. boto3_assist-0.12.0/tests/integration/cross_account_connection_test.py +78 -0
  20. boto3_assist-0.12.0/tests/integration/tenant.py +185 -0
  21. boto3_assist-0.12.0/tests/integration/tenant_services.py +48 -0
  22. {boto3_assist-0.10.0/tests/dynamodb → boto3_assist-0.12.0/tests/unit/dynamodb_tests}/dbmodels/cms/content_block.py +1 -1
  23. {boto3_assist-0.10.0/tests/dynamodb → boto3_assist-0.12.0/tests/unit/dynamodb_tests}/dbmodels/cms/page.py +1 -1
  24. {boto3_assist-0.10.0/tests/dynamodb → boto3_assist-0.12.0/tests/unit/dynamodb_tests}/dbmodels/cms/template.py +1 -1
  25. {boto3_assist-0.10.0/tests/dynamodb → boto3_assist-0.12.0/tests/unit/dynamodb_tests}/dynamodb_model_base_test.py +1 -1
  26. {boto3_assist-0.10.0/tests/dynamodb → boto3_assist-0.12.0/tests/unit/dynamodb_tests}/dynamodb_model_projections_test.py +2 -2
  27. {boto3_assist-0.10.0/tests/dynamodb → boto3_assist-0.12.0/tests/unit/dynamodb_tests}/dynamodb_model_serializtion_test.py +2 -2
  28. {boto3_assist-0.10.0/tests/dynamodb → boto3_assist-0.12.0/tests/unit/dynamodb_tests}/dynamodb_moto_sorting_test.py +2 -4
  29. boto3_assist-0.12.0/tests/unit/models_tests/models/person.py +26 -0
  30. boto3_assist-0.12.0/tests/unit/models_tests/models/user.py +66 -0
  31. boto3_assist-0.10.0/tests/models/serializable_model_test.py → boto3_assist-0.12.0/tests/unit/models_tests/serializable_model_person_test.py +11 -28
  32. boto3_assist-0.12.0/tests/unit/models_tests/serializable_model_user_test.py +54 -0
  33. boto3_assist-0.10.0/src/boto3_assist/version.py +0 -1
  34. boto3_assist-0.10.0/tests/__top/__init__.py +0 -25
  35. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/.env.docker +0 -0
  36. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/.env.docker.001 +0 -0
  37. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/.env.docker.nosql.workbench +0 -0
  38. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/.env.unittest +0 -0
  39. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/.gitignore +0 -0
  40. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/.vscode/launch.json +0 -0
  41. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/.vscode/tasks.json +0 -0
  42. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/LICENSE-EXPLAINED.txt +0 -0
  43. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/LICENSE.txt +0 -0
  44. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/aws_regions_with_status.csv +0 -0
  45. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/aws_regions_with_status.json +0 -0
  46. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/devops/build.py +0 -0
  47. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/devops/readme.md +0 -0
  48. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/examples/__init__.py +0 -0
  49. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/examples/cloudwatch/log_report.py +0 -0
  50. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/examples/dynamodb/models/order_item_model.py +0 -0
  51. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/examples/dynamodb/models/order_model.py +0 -0
  52. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/examples/dynamodb/models/product_model.py +0 -0
  53. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/examples/dynamodb/models/user_model.py +0 -0
  54. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/examples/dynamodb/models/user_post_model.py +0 -0
  55. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/examples/dynamodb/order_example/main.py +0 -0
  56. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/examples/dynamodb/order_example/products.json +0 -0
  57. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/examples/dynamodb/services/order_item_service.py +0 -0
  58. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/examples/dynamodb/services/order_service.py +0 -0
  59. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/examples/dynamodb/services/product_service.py +0 -0
  60. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/examples/dynamodb/services/table_service.py +0 -0
  61. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/examples/dynamodb/services/user_post_service.py +0 -0
  62. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/examples/dynamodb/services/user_service.py +0 -0
  63. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/examples/dynamodb/services/user_service_client_example.py +0 -0
  64. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/examples/dynamodb/services/user_service_resource_example.py +0 -0
  65. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/examples/dynamodb/user_post_example/main.py +0 -0
  66. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/examples/ec2/regions_report.py +0 -0
  67. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/module-headers.txt +0 -0
  68. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/mypy.ini +0 -0
  69. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/requirements.txt +0 -0
  70. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/run-checks.sh +0 -0
  71. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/run_unit_tests.sh +0 -0
  72. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/__init__.py +0 -0
  73. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/aws_lambda/event_info.py +0 -0
  74. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/aws_lambda/mock_context.py +0 -0
  75. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/cloudwatch/cloudwatch_connection.py +0 -0
  76. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/cloudwatch/cloudwatch_log_connection.py +0 -0
  77. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/cloudwatch/cloudwatch_logs.py +0 -0
  78. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/cloudwatch/cloudwatch_query.py +0 -0
  79. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/cognito/cognito_authorizer.py +0 -0
  80. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/cognito/cognito_connection.py +0 -0
  81. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/cognito/jwks_cache.py +0 -0
  82. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/cognito/user.py +0 -0
  83. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/connection_tracker.py +0 -0
  84. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/dynamodb/dynamodb_helpers.py +0 -0
  85. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/dynamodb/dynamodb_importer.py +0 -0
  86. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/dynamodb/dynamodb_index.py +0 -0
  87. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/dynamodb/dynamodb_iservice.py +0 -0
  88. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/dynamodb/dynamodb_key.py +0 -0
  89. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/dynamodb/dynamodb_model_base_interfaces.py +0 -0
  90. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/dynamodb/dynamodb_reindexer.py +0 -0
  91. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/dynamodb/dynamodb_reserved_words.py +0 -0
  92. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/dynamodb/dynamodb_reserved_words.txt +0 -0
  93. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/dynamodb/readme.md +0 -0
  94. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/dynamodb/troubleshooting.md +0 -0
  95. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/ec2/ec2_connection.py +0 -0
  96. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/environment_services/__init__.py +0 -0
  97. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/environment_services/environment_loader.py +0 -0
  98. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/environment_services/environment_variables.py +0 -0
  99. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/errors/custom_exceptions.py +0 -0
  100. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/http_status_codes.py +0 -0
  101. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/models/serializable_model.py +0 -0
  102. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/s3/s3.py +0 -0
  103. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/s3/s3_bucket.py +0 -0
  104. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/s3/s3_connection.py +0 -0
  105. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/securityhub/securityhub_connection.py +0 -0
  106. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/ssm/connection.py +0 -0
  107. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/ssm/parameter_store/parameter_store.py +0 -0
  108. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/utilities/datetime_utility.py +0 -0
  109. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/utilities/dictionary_utility.py +0 -0
  110. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/utilities/file_operations.py +0 -0
  111. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/utilities/http_utility.py +0 -0
  112. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/utilities/logging_utility.py +0 -0
  113. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/src/boto3_assist/utilities/numbers_utility.py +0 -0
  114. {boto3_assist-0.10.0 → boto3_assist-0.12.0}/tests/__init__.py +0 -0
  115. {boto3_assist-0.10.0/tests/dynamodb → boto3_assist-0.12.0/tests/unit/dynamodb_tests}/__init__.py +0 -0
  116. {boto3_assist-0.10.0/tests/dynamodb → boto3_assist-0.12.0/tests/unit/dynamodb_tests}/dbmodels/cms/base.py +0 -0
  117. {boto3_assist-0.10.0/tests/dynamodb → boto3_assist-0.12.0/tests/unit/dynamodb_tests}/dbmodels/simple_model.py +0 -0
  118. {boto3_assist-0.10.0/tests/dynamodb → boto3_assist-0.12.0/tests/unit/dynamodb_tests}/dbmodels/user_model.py +0 -0
  119. {boto3_assist-0.10.0/tests/dynamodb → boto3_assist-0.12.0/tests/unit/dynamodb_tests}/dbmodels/user_required_fields_model.py +0 -0
  120. {boto3_assist-0.10.0/tests/dynamodb → boto3_assist-0.12.0/tests/unit/dynamodb_tests}/dynamodb_reindex_test.py +0 -0
  121. {boto3_assist-0.10.0/tests → boto3_assist-0.12.0/tests/unit}/examples_test/__init__.py +0 -0
  122. {boto3_assist-0.10.0/tests → boto3_assist-0.12.0/tests/unit}/examples_test/user_service_test.py +0 -0
  123. {boto3_assist-0.10.0/tests/lambda → boto3_assist-0.12.0/tests/unit/lambda_tests}/__init__.py +0 -0
  124. {boto3_assist-0.10.0/tests/lambda → boto3_assist-0.12.0/tests/unit/lambda_tests}/event_info_test.py +0 -0
  125. {boto3_assist-0.10.0/tests/models → boto3_assist-0.12.0/tests/unit/models_tests}/__init__.py +0 -0
  126. {boto3_assist-0.10.0/tests/models → boto3_assist-0.12.0/tests/unit/models_tests}/serializable_model_wide_test.py +0 -0
  127. {boto3_assist-0.10.0/tests → boto3_assist-0.12.0/tests/unit}/parameter_store/__init__.py +0 -0
  128. {boto3_assist-0.10.0/tests → boto3_assist-0.12.0/tests/unit}/parameter_store/parameter_store_test.py +0 -0
  129. {boto3_assist-0.10.0/tests → boto3_assist-0.12.0/tests/unit}/s3/__init__.py +0 -0
  130. {boto3_assist-0.10.0/tests → boto3_assist-0.12.0/tests/unit}/s3/files/test.txt +0 -0
  131. {boto3_assist-0.10.0/tests → boto3_assist-0.12.0/tests/unit}/s3/s3_event_data_test.py +0 -0
  132. {boto3_assist-0.10.0/tests → boto3_assist-0.12.0/tests/unit}/s3/s3_file_delete_test.py +0 -0
  133. {boto3_assist-0.10.0/tests → boto3_assist-0.12.0/tests/unit}/s3/s3_file_upload_test.py +0 -0
  134. {boto3_assist-0.10.0/tests → boto3_assist-0.12.0/tests/unit}/utilities/__init__.py +0 -0
  135. {boto3_assist-0.10.0/tests → boto3_assist-0.12.0/tests/unit}/utilities/serialization_utility_test.py +0 -0
  136. {boto3_assist-0.10.0/tests → boto3_assist-0.12.0/tests/unit}/utilities/string_utility_test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
- "python.testing.pytestEnabled": false,
3
- "python.testing.unittestEnabled": true,
2
+ "python.testing.pytestEnabled": true,
3
+ "python.testing.unittestEnabled": false,
4
4
  "python.testing.unittestArgs": [
5
5
  "-v",
6
6
  "-s",
@@ -24,7 +24,7 @@
24
24
  "[python]": {
25
25
 
26
26
  "editor.formatOnSave": true,
27
- "editor.defaultFormatter": "charliermarsh.ruff",
27
+ "editor.defaultFormatter": "ms-python.black-formatter",
28
28
  },
29
29
 
30
30
  }
@@ -1,13 +1,16 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boto3_assist
3
- Version: 0.10.0
3
+ Version: 0.12.0
4
4
  Summary: Additional boto3 wrappers to make your life a little easier
5
5
  Author-email: Eric Wilson <boto3-assist@geekcafe.com>
6
6
  License-File: LICENSE-EXPLAINED.txt
7
7
  License-File: LICENSE.txt
8
- Classifier: License :: Other/Proprietary License
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: License :: OSI Approved :: MIT License
9
10
  Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python
10
12
  Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Software Development
11
14
  Requires-Python: >=3.10
12
15
  Requires-Dist: aws-lambda-powertools
13
16
  Requires-Dist: aws-xray-sdk
@@ -23,6 +26,10 @@ Description-Content-Type: text/markdown
23
26
 
24
27
  # boto3 assist
25
28
 
29
+ [![PyPI version](https://img.shields.io/pypi/v/boto3-assist.svg)](https://pypi.org/project/boto3-assist/)
30
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
31
+ [![Downloads](https://static.pepy.tech/badge/boto3-assist)](https://pepy.tech/project/boto3-assist)
32
+
26
33
  This is in beta and subject to changes before it's initial 1.0.0 release
27
34
 
28
35
  This libary was created to make life a little easier when using boto3.
@@ -1,5 +1,9 @@
1
1
  # boto3 assist
2
2
 
3
+ [![PyPI version](https://img.shields.io/pypi/v/boto3-assist.svg)](https://pypi.org/project/boto3-assist/)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![Downloads](https://static.pepy.tech/badge/boto3-assist)](https://pepy.tech/project/boto3-assist)
6
+
3
7
  This is in beta and subject to changes before it's initial 1.0.0 release
4
8
 
5
9
  This libary was created to make life a little easier when using boto3.
@@ -8,10 +8,14 @@ packages = ["src/boto3_assist"]
8
8
  [tool.pytest.ini_options]
9
9
  pythonpath = ["src"]
10
10
  testpaths = ["tests"]
11
+ markers = [
12
+ "integration: marks tests as integration (deselect with '-m \"not integration\"')"
13
+ ]
14
+ addopts = "-m 'not integration'"
11
15
 
12
16
  [project]
13
17
  name = "boto3_assist"
14
- version = "0.10.0"
18
+ version = "0.12.0"
15
19
 
16
20
  authors = [
17
21
  { name="Eric Wilson", email="boto3-assist@geekcafe.com" }
@@ -21,8 +25,11 @@ readme = "README.md"
21
25
  requires-python = ">=3.10"
22
26
  classifiers = [
23
27
  "Programming Language :: Python :: 3",
24
- "License :: Other/Proprietary License",
28
+ "License :: OSI Approved :: MIT License",
29
+ "Development Status :: 4 - Beta",
25
30
  "Operating System :: OS Independent",
31
+ "Programming Language :: Python",
32
+ "Topic :: Software Development"
26
33
  ]
27
34
 
28
35
  dependencies = [
@@ -19,4 +19,5 @@ pkginfo # occasionally we need to upgrade this or we can get upload errors when
19
19
  # the error may look like the following
20
20
  # ERROR InvalidDistribution: Metadata is missing required fields: Name, Version.
21
21
  # Make sure the distribution includes the files where those fields are specified, and is using a supported
22
- # Metadata-Version: 1.0, 1.1, 1.2, 2.0, 2.1, 2.2, 2.3.
22
+ # Metadata-Version: 1.0, 1.1, 1.2, 2.0, 2.1, 2.2, 2.3.
23
+ pytest
@@ -27,7 +27,7 @@ class Boto3SessionManager:
27
27
  aws_region: Optional[str] = None,
28
28
  assume_role_arn: Optional[str] = None,
29
29
  assume_role_session_name: Optional[str] = None,
30
- cross_account_role_arn: Optional[str] = None,
30
+ # cross_account_role_arn: Optional[str] = None,
31
31
  config: Optional[Config] = None,
32
32
  aws_endpoint_url: Optional[str] = None,
33
33
  aws_access_key_id: Optional[str] = None,
@@ -40,7 +40,7 @@ class Boto3SessionManager:
40
40
  self.assume_role_arn = assume_role_arn
41
41
  self.assume_role_session_name = assume_role_session_name
42
42
  self.config = config
43
- self.cross_account_role_arn = cross_account_role_arn
43
+ # # self.cross_account_role_arn = cross_account_role_arn
44
44
  self.endpoint_url = aws_endpoint_url
45
45
  self.aws_access_key_id = aws_access_key_id
46
46
  self.aws_secret_access_key = aws_secret_access_key
@@ -57,36 +57,46 @@ class Boto3SessionManager:
57
57
 
58
58
  profile = self.aws_profile or EnvironmentVariables.AWS.profile()
59
59
  region = self.aws_region or EnvironmentVariables.AWS.region()
60
- if self.assume_role_arn:
61
- self.__assume_role()
62
- else:
63
- logger.debug("Connecting without assuming a role.")
64
- self.__session = self.__get_aws_session(profile, region)
60
+
61
+ logger.debug("Connecting without assuming a role.")
62
+ self.__session = self.__get_aws_session(profile, region)
65
63
 
66
64
  if profile:
67
65
  print(f"Connecting with a profile: {profile}")
68
66
 
67
+ if self.assume_role_arn:
68
+ self.__assume_role()
69
+
69
70
  def __assume_role(self):
70
71
  """Assume an AWS IAM role."""
71
72
  try:
73
+ if not self.__session:
74
+ raise RuntimeError(
75
+ "Session must be established before assuming a role."
76
+ )
77
+
72
78
  logger.debug(f"Assuming role {self.assume_role_arn}")
73
- sts_client = boto3.client("sts")
79
+
80
+ sts_client = self.__session.client("sts")
74
81
  session_name = (
75
82
  self.assume_role_session_name
76
83
  or f"AssumeRoleSessionFor{self.service_name}"
77
84
  )
78
- if not self.assume_role_arn:
79
- raise ValueError("assume_role_arn is required")
85
+
80
86
  assumed_role_response = sts_client.assume_role(
81
87
  RoleArn=self.assume_role_arn,
82
88
  RoleSessionName=session_name,
83
89
  )
84
90
  credentials = assumed_role_response["Credentials"]
91
+
92
+ # Now override the session with assumed credentials
85
93
  self.__session = boto3.Session(
86
94
  aws_access_key_id=credentials["AccessKeyId"],
87
95
  aws_secret_access_key=credentials["SecretAccessKey"],
88
96
  aws_session_token=credentials["SessionToken"],
97
+ region_name=self.aws_region,
89
98
  )
99
+ logger.debug("Successfully assumed role and created new session.")
90
100
 
91
101
  except Exception as e:
92
102
  logger.error(f"Error assuming role: {e}")
@@ -117,9 +127,9 @@ class Boto3SessionManager:
117
127
  "region": self.aws_region,
118
128
  "aws_access_key_id": tmp_access_key_id,
119
129
  "aws_secret_access_key": tmp_secret_access_key,
120
- "aws_session_token": "*******"
121
- if self.aws_session_token is not None
122
- else "",
130
+ "aws_session_token": (
131
+ "*******" if self.aws_session_token is not None else ""
132
+ ),
123
133
  }
124
134
  )
125
135
  logger.debug("Creating boto3 session")
@@ -14,4 +14,4 @@ class CloudWatchConnectionTracker(ConnectionTracker):
14
14
  """
15
15
 
16
16
  def __init__(self) -> None:
17
- super().__init__("CloudWatch")
17
+ super().__init__()
@@ -5,7 +5,7 @@ MIT License. See Project Root for the license information.
5
5
  """
6
6
 
7
7
  import time
8
- from typing import List, Dict, Any, Optional
8
+ from typing import List, Dict, Any, Optional, Literal
9
9
 
10
10
 
11
11
  from aws_lambda_powertools import Logger
@@ -130,7 +130,7 @@ class CognitoUtility(CognitoConnection):
130
130
  is_permanent=True,
131
131
  )
132
132
 
133
- return response
133
+ return dict(response)
134
134
 
135
135
  except self.client.exceptions.UsernameExistsException as e:
136
136
  logger.error(f"Error: {e.response['Error']['Message']}")
@@ -185,7 +185,7 @@ class CognitoUtility(CognitoConnection):
185
185
  UserPoolId=user_pool_id, Username=user_name
186
186
  )
187
187
 
188
- return response
188
+ return dict(response)
189
189
 
190
190
  def admin_enable_user(
191
191
  self, user_name: str, user_pool_id: str, reset_password: bool = True
@@ -271,7 +271,7 @@ class CognitoUtility(CognitoConnection):
271
271
  logger.debug(
272
272
  f"User {email} created successfully. Confirmation code sent to {email}."
273
273
  )
274
- return response
274
+ return dict(response)
275
275
 
276
276
  except self.client.exceptions.UsernameExistsException as e:
277
277
  logger.error(f"Error: {e.response['Error']['Message']}")
@@ -324,11 +324,11 @@ class CognitoUtility(CognitoConnection):
324
324
  user_pool_id,
325
325
  client_name,
326
326
  id_token_time_out=60,
327
- id_token_units="minutes",
327
+ id_token_units: Literal["days", "hours", "minutes", "seconds"] = "minutes",
328
328
  access_token_time_out=60,
329
- access_token_units="minutes",
329
+ access_token_units: Literal["days", "hours", "minutes", "seconds"] = "minutes",
330
330
  refresh_token_time_out=60,
331
- refresh_token_units="minutes",
331
+ refresh_token_units: Literal["days", "hours", "minutes", "seconds"] = "minutes",
332
332
  ) -> dict:
333
333
  # valid units: 'seconds'|'minutes'|'hours'|'days'
334
334
 
@@ -340,9 +340,9 @@ class CognitoUtility(CognitoConnection):
340
340
  AccessTokenValidity=access_token_time_out,
341
341
  IdTokenValidity=id_token_time_out,
342
342
  TokenValidityUnits={
343
- "AccessToken": f"{access_token_units}",
344
- "IdToken": f"{id_token_units}",
345
- "RefreshToken": f"{refresh_token_units}",
343
+ "AccessToken": access_token_units,
344
+ "IdToken": id_token_units,
345
+ "RefreshToken": refresh_token_units,
346
346
  },
347
347
  # ReadAttributes=[
348
348
  # 'string',
@@ -381,18 +381,18 @@ class CognitoUtility(CognitoConnection):
381
381
  # AuthSessionValidity=123
382
382
  )
383
383
 
384
- return response
384
+ return dict(response)
385
385
 
386
386
  def search_cognito(self, email: str, user_pool_id: str) -> dict:
387
387
  """Search cognito for an existing user"""
388
388
 
389
- email = self.__format_email(email=email)
389
+ email = self.__format_email(email=email) or ""
390
390
  filter_string = f'email = "{email}"'
391
391
 
392
392
  # Call the admin_list_users method with the filter
393
393
  response = self.client.list_users(UserPoolId=user_pool_id, Filter=filter_string)
394
394
 
395
- return response
395
+ return dict(response)
396
396
 
397
397
  def __set_user_attributes(self, *, user: CognitoUser) -> List[dict]:
398
398
  """
@@ -30,6 +30,7 @@ class Connection:
30
30
  aws_access_key_id: Optional[str] = None,
31
31
  aws_secret_access_key: Optional[str] = None,
32
32
  aws_end_point_url: Optional[str] = None,
33
+ assume_role_arn: Optional[str] = None,
33
34
  ) -> None:
34
35
  self.__aws_profile = aws_profile
35
36
  self.__aws_region = aws_region
@@ -37,7 +38,7 @@ class Connection:
37
38
  self.__aws_secret_access_key = aws_secret_access_key
38
39
  self.end_point_url = aws_end_point_url
39
40
  self.__session: Boto3SessionManager | None = None
40
-
41
+ self.__assume_role_arn: Optional[str] = assume_role_arn
41
42
  self.__service_name: str | None = service_name
42
43
 
43
44
  if self.__service_name is None:
@@ -73,6 +74,7 @@ class Connection:
73
74
  aws_access_key_id=self.aws_access_key_id,
74
75
  aws_secret_access_key=self.aws_secret_access_key,
75
76
  aws_endpoint_url=self.end_point_url,
77
+ assume_role_arn=self.__assume_role_arn,
76
78
  )
77
79
 
78
80
  tracker.add(service_name=self.service_name)
@@ -24,7 +24,6 @@ from boto3_assist.utilities.string_utility import StringUtility
24
24
  logger = Logger()
25
25
 
26
26
 
27
-
28
27
  class DynamoDB(DynamoDBConnection):
29
28
  """
30
29
  DynamoDB. Wrapper for basic DynamoDB Connection and Actions
@@ -41,6 +40,7 @@ class DynamoDB(DynamoDBConnection):
41
40
  aws_end_point_url: Optional[str] = None,
42
41
  aws_access_key_id: Optional[str] = None,
43
42
  aws_secret_access_key: Optional[str] = None,
43
+ assume_role_arn: Optional[str] = None,
44
44
  ) -> None:
45
45
  super().__init__(
46
46
  aws_profile=aws_profile,
@@ -48,6 +48,7 @@ class DynamoDB(DynamoDBConnection):
48
48
  aws_end_point_url=aws_end_point_url,
49
49
  aws_access_key_id=aws_access_key_id,
50
50
  aws_secret_access_key=aws_secret_access_key,
51
+ assume_role_arn=assume_role_arn,
51
52
  )
52
53
  self.helpers: DynamoDBHelpers = DynamoDBHelpers()
53
54
  self.log_dynamodb_item_size = (
@@ -55,7 +56,6 @@ class DynamoDB(DynamoDBConnection):
55
56
  )
56
57
  logger.setLevel(os.getenv("LOG_LEVEL", "INFO"))
57
58
 
58
-
59
59
  def save(
60
60
  self,
61
61
  item: dict | DynamoDBModelBase,
@@ -169,7 +169,6 @@ class DynamoDB(DynamoDBConnection):
169
169
  call_type: str = "resource",
170
170
  ) -> Dict[str, Any]: ...
171
171
 
172
-
173
172
  def get(
174
173
  self,
175
174
  key: Optional[dict] = None,
@@ -344,7 +343,6 @@ class DynamoDB(DynamoDBConnection):
344
343
  ) -> dict:
345
344
  pass
346
345
 
347
-
348
346
  def delete(
349
347
  self,
350
348
  *,
@@ -32,6 +32,7 @@ class DynamoDBConnection(Connection):
32
32
  aws_end_point_url: Optional[str] = None,
33
33
  aws_access_key_id: Optional[str] = None,
34
34
  aws_secret_access_key: Optional[str] = None,
35
+ assume_role_arn: Optional[str] = None,
35
36
  ) -> None:
36
37
  super().__init__(
37
38
  service_name="dynamodb",
@@ -40,6 +41,7 @@ class DynamoDBConnection(Connection):
40
41
  aws_access_key_id=aws_access_key_id,
41
42
  aws_secret_access_key=aws_secret_access_key,
42
43
  aws_end_point_url=aws_end_point_url,
44
+ assume_role_arn=assume_role_arn,
43
45
  )
44
46
 
45
47
  self.__dynamodb_client: DynamoDBClient | None = None
@@ -249,6 +249,7 @@ class DynamoDBModelBase(SerializableModel):
249
249
  """
250
250
 
251
251
  value = DatetimeUtility.to_datetime_utc(value)
252
+ return value
252
253
 
253
254
 
254
255
  class DynamoDBSerializer:
@@ -15,37 +15,37 @@ class EventData:
15
15
  self.__event = event
16
16
 
17
17
  @property
18
- def version(self) -> str:
18
+ def version(self) -> str | None:
19
19
  """Event Version"""
20
20
  return self.__event.get("version")
21
21
 
22
22
  @property
23
- def id(self) -> str:
23
+ def id(self) -> str | None:
24
24
  """Event Id"""
25
25
  return self.__event.get("id")
26
26
 
27
27
  @property
28
- def detail_type(self) -> str:
28
+ def detail_type(self) -> str | None:
29
29
  """Event Detail Type"""
30
30
  return self.__event.get("detail-type")
31
31
 
32
32
  @property
33
- def source(self) -> str:
33
+ def source(self) -> str | None:
34
34
  """Event Source"""
35
35
  return self.__event.get("source")
36
36
 
37
37
  @property
38
- def account(self) -> str:
38
+ def account(self) -> str | None:
39
39
  """Event Account"""
40
40
  return self.__event.get("account")
41
41
 
42
42
  @property
43
- def time(self) -> str:
43
+ def time(self) -> str | None:
44
44
  """Event Time"""
45
45
  return self.__event.get("time")
46
46
 
47
47
  @property
48
- def region(self) -> str:
48
+ def region(self) -> str | None:
49
49
  """Event Region"""
50
50
  return self.__event.get("region")
51
51
 
@@ -217,7 +217,7 @@ class S3Object:
217
217
  )
218
218
  try:
219
219
  # convert if necessary
220
- file_obj: bytes = (
220
+ file_obj = (
221
221
  file_obj.encode("utf-8") if isinstance(file_obj, str) else file_obj
222
222
  )
223
223
  self.connection.client.upload_fileobj(
@@ -658,7 +658,7 @@ class S3Object:
658
658
  Key=destination_key,
659
659
  )
660
660
 
661
- return response
661
+ return dict(response)
662
662
 
663
663
  def move(
664
664
  self,
@@ -5,7 +5,7 @@ MIT License. See Project Root for the license information.
5
5
  """
6
6
 
7
7
  import os
8
- from typing import Optional
8
+ from typing import Optional, Literal
9
9
 
10
10
  from aws_lambda_powertools import Logger
11
11
 
@@ -20,7 +20,7 @@ class SecurityHub(SecurityHubConnection):
20
20
  def update_findings_status(
21
21
  self,
22
22
  region_name: str,
23
- workflow_status: str,
23
+ workflow_status: Literal["NEW", "NOTIFIED", "RESOLVED", "SUPPRESSED"],
24
24
  note_text: Optional[str] = None,
25
25
  updated_by: Optional[str] = None,
26
26
  ):
@@ -148,9 +148,11 @@ class JsonConversions:
148
148
  elif isinstance(data, list):
149
149
  # For lists, if deep conversion is enabled, process each element.
150
150
  return [
151
- JsonConversions._convert_keys(item, convert_func, deep)
152
- if deep
153
- else item
151
+ (
152
+ JsonConversions._convert_keys(item, convert_func, deep)
153
+ if deep
154
+ else item
155
+ )
154
156
  for item in data
155
157
  ]
156
158
  else:
@@ -377,7 +379,10 @@ class Serialization:
377
379
  setattr(target, "__actively_serializing_data__", True)
378
380
 
379
381
  for key, value in source.items():
380
- if Serialization.has_attribute(target, key):
382
+ if isinstance(target, dict):
383
+ # our target is a dictionary, so we need to handle this differently
384
+ target[key] = value
385
+ elif Serialization.has_attribute(target, key):
381
386
  attr = getattr(target, key)
382
387
  expected_type = type(attr)
383
388
 
@@ -313,6 +313,7 @@ class StringUtility:
313
313
  return True
314
314
  if value in ("false", "0", "f", "n", "no"):
315
315
  return False
316
+ raise ValueError(f"Invalid boolean value: {value}")
316
317
  elif isinstance(value, int):
317
318
  return bool(value)
318
319
  elif value is None:
@@ -0,0 +1 @@
1
+ __version__ = '0.12.0'
@@ -0,0 +1,78 @@
1
+ import pytest
2
+ import os
3
+ from typing import List
4
+ from boto3_assist.dynamodb.dynamodb import DynamoDB
5
+ from tests.integration.tenant_services import TenantServices
6
+
7
+
8
+ @pytest.mark.integration
9
+ def test_cross_account_role_assumption_with_profile():
10
+ responses = []
11
+
12
+ profile_name = os.getenv("AWS_PROFILE")
13
+
14
+ connections: List[dict] = [
15
+ {
16
+ "profile_name": profile_name,
17
+ "aws_account": "959096737760",
18
+ "aws_region": "us-east-1",
19
+ "role_name": "CrossAccountAccessRole",
20
+ "table_name": "db-us-east-1",
21
+ "enabled": False,
22
+ },
23
+ {
24
+ "profile_name": profile_name,
25
+ "aws_account": "959096737760",
26
+ "aws_region": "eu-west-2",
27
+ "role_name": "CrossAccountAccessRole",
28
+ "table_name": "db-eu-west-2",
29
+ "enabled": False,
30
+ },
31
+ {
32
+ "profile_name": profile_name,
33
+ "aws_account": "257932641017",
34
+ "aws_region": "us-east-1",
35
+ "role_name": "CrossAccountAccessRole",
36
+ "table_name": "aplos-nca-saas-production-demo-001-database",
37
+ "enabled": True,
38
+ },
39
+ {
40
+ "profile_name": profile_name,
41
+ "aws_region": "us-east-1",
42
+ "aws_account": "211125601483",
43
+ "role_name": "CrossAccountAccessRole",
44
+ "table_name": "aplos-nca-saas-production-app-database",
45
+ "enabled": True,
46
+ },
47
+ ]
48
+
49
+ for connection in connections:
50
+ role_arn = (
51
+ f"arn:aws:iam::{connection['aws_account']}:role/{connection['role_name']}"
52
+ )
53
+ if connection["enabled"]:
54
+ db = DynamoDB(
55
+ aws_profile=connection["profile_name"],
56
+ aws_region=connection["aws_region"],
57
+ assume_role_arn=role_arn,
58
+ )
59
+ ts: TenantServices = TenantServices(
60
+ db=db, table_name=connection["table_name"]
61
+ )
62
+ response = ts.list()
63
+ responses.append(response)
64
+ # print(response)
65
+ else:
66
+ responses.append(None)
67
+
68
+ print(len(responses))
69
+ assert len(responses) == 3
70
+
71
+
72
+ def main():
73
+ test_cross_account_role_assumption_with_profile()
74
+ pass
75
+
76
+
77
+ if __name__ == "__main__":
78
+ main()