clear-skies-aws 1.10.2__py3-none-any.whl → 2.0.2__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 (86) hide show
  1. {clear_skies_aws-1.10.2.dist-info → clear_skies_aws-2.0.2.dist-info}/METADATA +36 -35
  2. clear_skies_aws-2.0.2.dist-info/RECORD +63 -0
  3. {clear_skies_aws-1.10.2.dist-info → clear_skies_aws-2.0.2.dist-info}/WHEEL +1 -1
  4. clear_skies_aws-2.0.2.dist-info/licenses/LICENSE +21 -0
  5. clearskies_aws/__init__.py +15 -2
  6. clearskies_aws/actions/__init__.py +13 -106
  7. clearskies_aws/actions/action_aws.py +74 -57
  8. clearskies_aws/actions/assume_role.py +43 -30
  9. clearskies_aws/actions/ses.py +82 -73
  10. clearskies_aws/actions/sns.py +27 -30
  11. clearskies_aws/actions/sqs.py +32 -33
  12. clearskies_aws/actions/step_function.py +38 -31
  13. clearskies_aws/backends/__init__.py +11 -4
  14. clearskies_aws/backends/backend.py +106 -0
  15. clearskies_aws/backends/dynamo_db_backend.py +150 -155
  16. clearskies_aws/backends/dynamo_db_condition_parser.py +40 -80
  17. clearskies_aws/backends/dynamo_db_parti_ql_backend.py +179 -337
  18. clearskies_aws/backends/sqs_backend.py +32 -51
  19. clearskies_aws/configs/__init__.py +0 -0
  20. clearskies_aws/contexts/__init__.py +23 -10
  21. clearskies_aws/contexts/cli_web_socket_mock.py +19 -0
  22. clearskies_aws/contexts/lambda_alb.py +76 -0
  23. clearskies_aws/contexts/lambda_api_gateway.py +75 -28
  24. clearskies_aws/contexts/lambda_api_gateway_web_socket.py +56 -29
  25. clearskies_aws/contexts/lambda_invocation.py +15 -44
  26. clearskies_aws/contexts/lambda_sns.py +8 -33
  27. clearskies_aws/contexts/lambda_sqs_standard_partial_batch.py +14 -36
  28. clearskies_aws/di/__init__.py +6 -1
  29. clearskies_aws/di/aws_additional_config_auto_import.py +37 -0
  30. clearskies_aws/di/inject/__init__.py +6 -0
  31. clearskies_aws/di/inject/boto3.py +15 -0
  32. clearskies_aws/di/inject/boto3_session.py +13 -0
  33. clearskies_aws/di/inject/parameter_store.py +15 -0
  34. clearskies_aws/{handlers → endpoints}/secrets_manager_rotation.py +76 -55
  35. clearskies_aws/endpoints/simple_body_routing.py +41 -0
  36. clearskies_aws/input_outputs/__init__.py +21 -8
  37. clearskies_aws/input_outputs/{cli_websocket_mock.py → cli_web_socket_mock.py} +9 -3
  38. clearskies_aws/input_outputs/lambda_alb.py +53 -0
  39. clearskies_aws/input_outputs/lambda_api_gateway.py +106 -88
  40. clearskies_aws/input_outputs/lambda_api_gateway_web_socket.py +69 -6
  41. clearskies_aws/input_outputs/lambda_input_output.py +87 -0
  42. clearskies_aws/input_outputs/lambda_invocation.py +77 -26
  43. clearskies_aws/input_outputs/lambda_sns.py +66 -39
  44. clearskies_aws/input_outputs/lambda_sqs_standard.py +70 -40
  45. clearskies_aws/mocks/actions/ses.py +25 -19
  46. clearskies_aws/mocks/actions/sns.py +18 -12
  47. clearskies_aws/mocks/actions/sqs.py +18 -12
  48. clearskies_aws/mocks/actions/step_function.py +19 -13
  49. clearskies_aws/models/__init__.py +0 -0
  50. clearskies_aws/models/web_socket_connection_model.py +182 -0
  51. clearskies_aws/secrets/__init__.py +13 -7
  52. clearskies_aws/secrets/additional_configs/__init__.py +10 -2
  53. clearskies_aws/secrets/additional_configs/iam_db_auth.py +26 -16
  54. clearskies_aws/secrets/additional_configs/iam_db_auth_with_ssm.py +43 -39
  55. clearskies_aws/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py +30 -31
  56. clearskies_aws/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssm_bastion.py +70 -49
  57. clearskies_aws/secrets/akeyless_with_ssm_cache.py +32 -18
  58. clearskies_aws/secrets/parameter_store.py +34 -32
  59. clearskies_aws/secrets/secrets.py +16 -0
  60. clearskies_aws/secrets/secrets_manager.py +78 -57
  61. clear_skies_aws-1.10.2.dist-info/LICENSE +0 -7
  62. clear_skies_aws-1.10.2.dist-info/RECORD +0 -71
  63. clearskies_aws/actions/assume_role_test.py +0 -72
  64. clearskies_aws/actions/ses_test.py +0 -89
  65. clearskies_aws/actions/sns_test.py +0 -77
  66. clearskies_aws/actions/sqs_test.py +0 -127
  67. clearskies_aws/actions/step_function_test.py +0 -103
  68. clearskies_aws/backends/dynamo_db_backend_test.py +0 -300
  69. clearskies_aws/backends/dynamo_db_condition_parser_test.py +0 -266
  70. clearskies_aws/backends/dynamo_db_parti_ql_backend_test.py +0 -544
  71. clearskies_aws/backends/sqs_backend_test.py +0 -31
  72. clearskies_aws/contexts/cli.py +0 -19
  73. clearskies_aws/contexts/cli_websocket_mock.py +0 -33
  74. clearskies_aws/contexts/lambda_elb.py +0 -30
  75. clearskies_aws/contexts/lambda_http_gateway.py +0 -30
  76. clearskies_aws/contexts/lambda_sqs_standard_partial_batch_test.py +0 -66
  77. clearskies_aws/contexts/wsgi.py +0 -19
  78. clearskies_aws/di/standard_dependencies.py +0 -60
  79. clearskies_aws/handlers/simple_body_routing.py +0 -39
  80. clearskies_aws/input_outputs/lambda_api_gateway_test.py +0 -87
  81. clearskies_aws/input_outputs/lambda_elb.py +0 -21
  82. clearskies_aws/input_outputs/lambda_http_gateway.py +0 -12
  83. clearskies_aws/secrets/parameter_store_test.py +0 -18
  84. clearskies_aws/secrets/secrets_manager_test.py +0 -18
  85. clearskies_aws/web_socket_connection_model.py +0 -43
  86. clearskies_aws/{handlers → endpoints}/__init__.py +1 -1
@@ -1,30 +0,0 @@
1
- from ..input_outputs import LambdaHTTPGateway as LambdaHTTPGatewayInputOutput
2
- from ..di import StandardDependencies
3
- from clearskies.contexts.build_context import build_context
4
- from clearskies.contexts.context import Context
5
- class LambdaHTTPGateway(Context):
6
- def __init__(self, di):
7
- super().__init__(di)
8
-
9
- def __call__(self, event, context):
10
- if self.handler is None:
11
- raise ValueError("Cannot execute LambdaHTTPGateway context without first configuring it")
12
-
13
- return self.handler(LambdaHTTPGatewayInputOutput(event, context))
14
- def lambda_http_gateway(
15
- application,
16
- di_class=StandardDependencies,
17
- bindings=None,
18
- binding_classes=None,
19
- binding_modules=None,
20
- additional_configs=None,
21
- ):
22
- return build_context(
23
- LambdaHTTPGateway,
24
- application,
25
- di_class=di_class,
26
- bindings=bindings,
27
- binding_classes=binding_classes,
28
- binding_modules=binding_modules,
29
- additional_configs=additional_configs,
30
- )
@@ -1,66 +0,0 @@
1
- import json
2
- import unittest
3
- from types import SimpleNamespace
4
- from unittest.mock import MagicMock, call
5
-
6
- from .lambda_sqs_standard_partial_batch import lambda_sqs_standard_partial_batch
7
-
8
-
9
- class LambdaSqsStandardPartialBatchTest(unittest.TestCase):
10
- def setUp(self):
11
- self.calls = []
12
-
13
- def my_callable(self, request_data):
14
- if 'boom' in request_data:
15
- raise ValueError('oops')
16
- self.calls.append(request_data)
17
-
18
- def test_simple_execution(self):
19
- sqs_handler = lambda_sqs_standard_partial_batch(self.my_callable)
20
- sqs_handler(
21
- {
22
- 'Records': [
23
- {
24
- 'messageId': '1-2-3-4',
25
- 'body': json.dumps({'hey': 'sup'}),
26
- },
27
- {
28
- 'messageId': '2-3-4-5',
29
- 'body': json.dumps({'cool': 'yo'}),
30
- },
31
- ]
32
- },
33
- {},
34
- )
35
- self.assertEqual(
36
- [
37
- {"hey": "sup"},
38
- {"cool": "yo"},
39
- ],
40
- self.calls,
41
- )
42
-
43
- def test_with_failure(self):
44
- sqs_handler = lambda_sqs_standard_partial_batch(self.my_callable)
45
- results = sqs_handler({
46
- 'Records': [
47
- {
48
- 'messageId': '1-2-3-4',
49
- 'body': json.dumps({'hey': 'sup'}),
50
- },
51
- {
52
- 'messageId': '2-3-4-5',
53
- 'body': json.dumps({'boom': 'yo'}),
54
- },
55
- ]
56
- }, {})
57
- self.assertEqual(
58
- [
59
- {"hey": "sup"},
60
- ],
61
- self.calls,
62
- )
63
- self.assertEqual(
64
- {"batchItemFailures": [{"itemIdentifier": "2-3-4-5"}]},
65
- results,
66
- )
@@ -1,19 +0,0 @@
1
- from ..di import StandardDependencies
2
- from clearskies.contexts.wsgi import WSGI, build_context
3
- def wsgi(
4
- application,
5
- di_class=StandardDependencies,
6
- bindings=None,
7
- binding_classes=None,
8
- binding_modules=None,
9
- additional_configs=None,
10
- ):
11
- return build_context(
12
- WSGI,
13
- application,
14
- di_class=di_class,
15
- bindings=bindings,
16
- binding_classes=binding_classes,
17
- binding_modules=binding_modules,
18
- additional_configs=additional_configs,
19
- )
@@ -1,60 +0,0 @@
1
- from types import ModuleType
2
-
3
- import boto3 as boto3_module
4
- from boto3.session import Session as Boto3Session
5
- from clearskies import Environment
6
- from clearskies.di import StandardDependencies as DefaultStandardDependencies
7
-
8
- from ..backends import (
9
- DynamoDBBackend,
10
- DynamoDBPartiQLBackend,
11
- DynamoDBPartiQLCursor,
12
- SqsBackend,
13
- )
14
- from ..secrets import ParameterStore
15
-
16
-
17
- class StandardDependencies(DefaultStandardDependencies):
18
-
19
- def provide_dynamo_db_parti_ql_cursor(
20
- self, boto3_session: Boto3Session
21
- ) -> DynamoDBPartiQLCursor:
22
- return DynamoDBPartiQLCursor(boto3_session)
23
-
24
- def provide_dynamo_db_backend(
25
- self, boto3: ModuleType, environment: Environment
26
- ) -> DynamoDBBackend:
27
- return DynamoDBBackend(boto3, environment)
28
-
29
- def provide_dynamo_db_parti_ql_backend(
30
- self, dynamo_db_parti_ql_cursor: DynamoDBPartiQLCursor, environment: Environment
31
- ) -> DynamoDBPartiQLBackend:
32
- return DynamoDBPartiQLBackend(dynamo_db_parti_ql_cursor)
33
-
34
- def provide_sqs_backend(
35
- self, boto3: ModuleType, environment: Environment
36
- ) -> SqsBackend:
37
- return SqsBackend(boto3, environment)
38
-
39
- def provide_boto3(self) -> ModuleType:
40
- import boto3
41
- return boto3
42
-
43
- def provide_secrets(
44
- self, boto3: ModuleType, environment: Environment
45
- ) -> ParameterStore:
46
- # This is just here so that we can auto-inject the secrets into the environment without having
47
- # to force the developer to define a secrets manager
48
- return ParameterStore(boto3, environment)
49
-
50
- def provide_boto3_session(
51
- self, boto3: ModuleType, environment: Environment
52
- ) -> boto3_module.session.Session:
53
-
54
- if not environment.get("AWS_REGION", True):
55
- raise ValueError(
56
- "To use AWS Session you must use set AWS_REGION in the .env file or an environment variable"
57
- )
58
-
59
- session = boto3.session.Session(region_name=environment.get("AWS_REGION", True))
60
- return session
@@ -1,39 +0,0 @@
1
- import clearskies
2
-
3
-
4
- class SimpleBodyRouting(clearskies.handlers.Routing):
5
- def __init__(self, di):
6
- super().__init__(di)
7
-
8
- _configuration_defaults = {
9
- "routes": {},
10
- "route_key": "route",
11
- }
12
-
13
- def handler_classes(self, configuration):
14
- # not actually used but required by base
15
- return []
16
-
17
- def _check_configuration(self, configuration):
18
- for config_name in ["route_key", "routes"]:
19
- if not configuration.get(config_name):
20
- raise KeyError(f"Missing required configuration for SimpleBodyRouting handler: '{config_name}'")
21
- if not isinstance(configuration["routes"], dict):
22
- raise ValueError(f"COnfiguration 'routes' for handler SimpleBodyRouting must be a dictionary, but instead I got something else.")
23
-
24
- def handle(self, input_output):
25
- body = input_output.json_body(required=True)
26
- if not body or not body.get(self.configuration("route_key")):
27
- return self.error(input_output, "Not Found", 404)
28
-
29
- route = body[self.configuration("route_key")]
30
- if route not in self.configuration("routes"):
31
- return self.error(input_output, "Not Found", 404)
32
- return input_output.respond(self._di.call_function(
33
- self.configuration("routes")[route],
34
- request_data=body,
35
- **input_output.context_specifics(),
36
- ), 200)
37
-
38
- def documentation(self):
39
- return []
@@ -1,87 +0,0 @@
1
- import unittest
2
- from collections import OrderedDict
3
-
4
- from .lambda_api_gateway import LambdaAPIGateway
5
-
6
-
7
- class LambdaAPIGatewayTest(unittest.TestCase):
8
- dummy_event = {
9
- 'httpMethod': 'GET',
10
- 'path': '/test',
11
- 'resource': 'bob',
12
- 'queryStringParameters': {
13
- 'q': 'hey',
14
- 'bob': 'sup'
15
- },
16
- 'pathParameters': None,
17
- 'headers': {
18
- 'Content-Type': 'application/json'
19
- },
20
- }
21
-
22
- def test_respond(self):
23
- aws_lambda = LambdaAPIGateway(self.dummy_event, {})
24
- aws_lambda.set_headers({'bob': 'hey', 'jane': 'kay'})
25
- aws_lambda.set_header('hey', 'sup')
26
- aws_lambda.clear_header('bob')
27
- response = aws_lambda.respond({'some': 'data'}, 200)
28
- self.assertEqual(
29
- {
30
- "isBase64Encoded": False,
31
- "statusCode": 200,
32
- "headers": OrderedDict(
33
- [
34
- ("JANE", "kay"),
35
- ("HEY", "sup"),
36
- ("CONTENT-TYPE", "application/json; charset=UTF-8"),
37
- ]
38
- ),
39
- "body": '{"some": "data"}',
40
- },
41
- response,
42
- )
43
-
44
- def test_headers(self):
45
- aws_lambda = LambdaAPIGateway({
46
- **self.dummy_event,
47
- **{
48
- 'headers': {
49
- 'Content-Type': 'application/json',
50
- 'AUTHORIZATION': 'hey',
51
- 'X-Auth': 'asdf',
52
- }
53
- }
54
- }, {})
55
- self.assertEqual("hey", aws_lambda.get_request_header("authorizatiON"))
56
- self.assertEqual("asdf", aws_lambda.get_request_header("x-auth"))
57
- self.assertTrue(aws_lambda.has_request_header('authorization'))
58
- self.assertTrue(aws_lambda.has_request_header('x-auth'))
59
- self.assertFalse(aws_lambda.has_request_header('bearer'))
60
-
61
- def test_body_plain(self):
62
- aws_lambda = LambdaAPIGateway({**self.dummy_event, **{'body': '{"hey": "sup"}', 'isBase64Encoded': False}}, {})
63
-
64
- self.assertEqual({"hey": "sup"}, aws_lambda.json_body())
65
- self.assertEqual('{"hey": "sup"}', aws_lambda.get_body())
66
- self.assertTrue(aws_lambda.has_body())
67
-
68
- def test_body_base64(self):
69
- aws_lambda = LambdaAPIGateway({
70
- **self.dummy_event,
71
- **{
72
- 'body': 'eyJoZXkiOiAic3VwIn0=',
73
- 'isBase64Encoded': True
74
- }
75
- }, {})
76
-
77
- self.assertEqual({"hey": "sup"}, aws_lambda.json_body())
78
- self.assertEqual('{"hey": "sup"}', aws_lambda.get_body())
79
- self.assertTrue(aws_lambda.has_body())
80
-
81
- def test_path(self):
82
- aws_lambda = LambdaAPIGateway(self.dummy_event, {})
83
- self.assertEqual("/test", aws_lambda.get_path_info())
84
-
85
- def test_query_string(self):
86
- aws_lambda = LambdaAPIGateway(self.dummy_event, {})
87
- self.assertEqual("q=hey&bob=sup", aws_lambda.get_query_string())
@@ -1,21 +0,0 @@
1
- from .lambda_api_gateway import LambdaAPIGateway
2
- class LambdaELB(LambdaAPIGateway):
3
- _event = None
4
- _context = None
5
- _request_headers = None
6
- _request_method = None
7
- _path = None
8
- _query_parameters = None
9
-
10
- def __init__(self, event, context):
11
- self._event = event
12
- self._context = context
13
- self._request_method = event.get('httpMethod', 'GET').upper()
14
- self._path = event.get('path', '/')
15
- self._query_parameters = event.get('queryStringParameters', {})
16
- self._request_headers = {}
17
- for (key, value) in event.get('headers', {}).items():
18
- self._request_headers[key.lower()] = value
19
-
20
- def get_client_ip(self):
21
- return self.get_request_header('x-forwarded-for')
@@ -1,12 +0,0 @@
1
- from .lambda_api_gateway import LambdaAPIGateway
2
- class LambdaHTTPGateway(LambdaAPIGateway):
3
- def __init__(self, event, context):
4
- self._event = event
5
- self._context = context
6
- self._path = event.get('requestContext', {}).get('http', {}).get('path')
7
- self._request_method = event.get('requestContext', {}).get('http', {}).get('method').upper()
8
- self._query_parameters = event['queryStringParameters'] if event['queryStringParameters'] is not None else {}
9
- self._path_parameters = event['pathParameters']
10
- self._request_headers = {}
11
- for (key, value) in event['headers'].items():
12
- self._request_headers[key.lower()] = value
@@ -1,18 +0,0 @@
1
- import unittest
2
- from types import SimpleNamespace
3
- from unittest.mock import MagicMock
4
-
5
- from .parameter_store import ParameterStore
6
-
7
-
8
- class ParameterStoreTest(unittest.TestCase):
9
- def setUp(self):
10
- self.environment = SimpleNamespace(get=MagicMock(return_value='us-east-1'))
11
-
12
- def test_get(self):
13
- ssm = SimpleNamespace(get_parameter=MagicMock(return_value={'Parameter': {'Value': 'sup'}}))
14
- boto3 = SimpleNamespace(client=MagicMock(return_value=ssm))
15
- parameter_store = ParameterStore(boto3, self.environment)
16
- self.assertEqual("sup", parameter_store.get("/my/item"))
17
- ssm.get_parameter.assert_called_with(Name='/my/item', WithDecryption=True)
18
- boto3.client.assert_called_with('ssm', region_name='us-east-1')
@@ -1,18 +0,0 @@
1
- import unittest
2
- from types import SimpleNamespace
3
- from unittest.mock import MagicMock
4
-
5
- from .secrets_manager import SecretsManager
6
-
7
-
8
- class SecretsManagerTest(unittest.TestCase):
9
- def setUp(self):
10
- self.environment = SimpleNamespace(get=MagicMock(return_value='us-east-1'))
11
-
12
- def test_get(self):
13
- secretsmanager = SimpleNamespace(get_secret_value=MagicMock(return_value={'SecretString': 'sup'}))
14
- boto3 = SimpleNamespace(client=MagicMock(return_value=secretsmanager))
15
- secrets_manager = SecretsManager(boto3, self.environment)
16
- self.assertEqual("sup", secrets_manager.get("/my/item"))
17
- secretsmanager.get_secret_value.assert_called_with(SecretId='/my/item')
18
- boto3.client.assert_called_with('secretsmanager', region_name='us-east-1')
@@ -1,43 +0,0 @@
1
- from abc import abstractmethod
2
- import json
3
-
4
- import clearskies
5
-
6
-
7
- class WebSocketConnectionModel(clearskies.Model):
8
-
9
- id_column_name = "connection_id"
10
-
11
- def __init__(self, backend, columns, boto3, input_output):
12
- super().__init__(backend, columns)
13
- self._boto3 = boto3
14
- self._input_output = input_output
15
-
16
- def _build_model(self):
17
- model_class = self.model_class()
18
- return model_class(self._backend, self._columns, self._boto3, self._input_output)
19
-
20
- def send(self, message):
21
- if not self.exists:
22
- raise ValueError("Cannot send message to non-existent connection.")
23
- if not self.data.get("connection_id"):
24
- raise ValueError(
25
- f"Hmmm... I couldn't find the connection id for the {self.__class__.__name__}. I'm picky about id column names. Can you please make sure I have a column called connection_id and that it contains the connection id?"
26
- )
27
-
28
- event = self._input_output.context_specifics()["event"]
29
- domain = event.get("requestContext", {}).get("domainName")
30
- stage = event.get("requestContext", {}).get("stage")
31
- # only include the stage if we're using the default AWS domain - not with a custom domain
32
- if ".amazonaws.com" in domain:
33
- endpoint_url = f"https://{domain}/{stage}"
34
- else:
35
- endpoint_url = f"https://{domain}"
36
- api_gateway = self._boto3.client("apigatewaymanagementapi", endpoint_url=endpoint_url)
37
-
38
- bytes_message = json.dumps(message).encode("utf-8")
39
- try:
40
- response = api_gateway.post_to_connection(Data=bytes_message, ConnectionId=self.connection_id)
41
- except api_gateway.exceptions.GoneException:
42
- self.delete()
43
- return response
@@ -1,2 +1,2 @@
1
- from .simple_body_routing import SimpleBodyRouting
2
1
  from .secrets_manager_rotation import SecretsManagerRotation
2
+ from .simple_body_routing import SimpleBodyRouting