clear-skies-aws 1.10.1__py3-none-any.whl → 2.0.1__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 (73) hide show
  1. {clear_skies_aws-1.10.1.dist-info → clear_skies_aws-2.0.1.dist-info}/METADATA +36 -35
  2. clear_skies_aws-2.0.1.dist-info/RECORD +4 -0
  3. {clear_skies_aws-1.10.1.dist-info → clear_skies_aws-2.0.1.dist-info}/WHEEL +1 -1
  4. clear_skies_aws-2.0.1.dist-info/licenses/LICENSE +21 -0
  5. clear_skies_aws-1.10.1.dist-info/LICENSE +0 -7
  6. clear_skies_aws-1.10.1.dist-info/RECORD +0 -71
  7. clearskies_aws/__init__.py +0 -2
  8. clearskies_aws/actions/__init__.py +0 -108
  9. clearskies_aws/actions/action_aws.py +0 -118
  10. clearskies_aws/actions/assume_role.py +0 -102
  11. clearskies_aws/actions/assume_role_test.py +0 -72
  12. clearskies_aws/actions/ses.py +0 -194
  13. clearskies_aws/actions/ses_test.py +0 -89
  14. clearskies_aws/actions/sns.py +0 -64
  15. clearskies_aws/actions/sns_test.py +0 -77
  16. clearskies_aws/actions/sqs.py +0 -82
  17. clearskies_aws/actions/sqs_test.py +0 -127
  18. clearskies_aws/actions/step_function.py +0 -66
  19. clearskies_aws/actions/step_function_test.py +0 -103
  20. clearskies_aws/backends/__init__.py +0 -12
  21. clearskies_aws/backends/dynamo_db_backend.py +0 -614
  22. clearskies_aws/backends/dynamo_db_backend_test.py +0 -300
  23. clearskies_aws/backends/dynamo_db_condition_parser.py +0 -365
  24. clearskies_aws/backends/dynamo_db_condition_parser_test.py +0 -266
  25. clearskies_aws/backends/dynamo_db_parti_ql_backend.py +0 -1123
  26. clearskies_aws/backends/dynamo_db_parti_ql_backend_test.py +0 -544
  27. clearskies_aws/backends/sqs_backend.py +0 -80
  28. clearskies_aws/backends/sqs_backend_test.py +0 -31
  29. clearskies_aws/contexts/__init__.py +0 -10
  30. clearskies_aws/contexts/cli.py +0 -19
  31. clearskies_aws/contexts/cli_websocket_mock.py +0 -33
  32. clearskies_aws/contexts/lambda_api_gateway.py +0 -30
  33. clearskies_aws/contexts/lambda_api_gateway_web_socket.py +0 -30
  34. clearskies_aws/contexts/lambda_elb.py +0 -30
  35. clearskies_aws/contexts/lambda_http_gateway.py +0 -30
  36. clearskies_aws/contexts/lambda_invocation.py +0 -48
  37. clearskies_aws/contexts/lambda_sns.py +0 -43
  38. clearskies_aws/contexts/lambda_sqs_standard_partial_batch.py +0 -51
  39. clearskies_aws/contexts/lambda_sqs_standard_partial_batch_test.py +0 -66
  40. clearskies_aws/contexts/wsgi.py +0 -19
  41. clearskies_aws/di/__init__.py +0 -1
  42. clearskies_aws/di/standard_dependencies.py +0 -60
  43. clearskies_aws/handlers/__init__.py +0 -2
  44. clearskies_aws/handlers/secrets_manager_rotation.py +0 -174
  45. clearskies_aws/handlers/simple_body_routing.py +0 -39
  46. clearskies_aws/input_outputs/__init__.py +0 -8
  47. clearskies_aws/input_outputs/cli_websocket_mock.py +0 -12
  48. clearskies_aws/input_outputs/lambda_api_gateway.py +0 -105
  49. clearskies_aws/input_outputs/lambda_api_gateway_test.py +0 -87
  50. clearskies_aws/input_outputs/lambda_api_gateway_web_socket.py +0 -8
  51. clearskies_aws/input_outputs/lambda_elb.py +0 -21
  52. clearskies_aws/input_outputs/lambda_http_gateway.py +0 -12
  53. clearskies_aws/input_outputs/lambda_invocation.py +0 -34
  54. clearskies_aws/input_outputs/lambda_sns.py +0 -52
  55. clearskies_aws/input_outputs/lambda_sqs_standard.py +0 -54
  56. clearskies_aws/mocks/__init__.py +0 -1
  57. clearskies_aws/mocks/actions/__init__.py +0 -6
  58. clearskies_aws/mocks/actions/ses.py +0 -28
  59. clearskies_aws/mocks/actions/sns.py +0 -23
  60. clearskies_aws/mocks/actions/sqs.py +0 -23
  61. clearskies_aws/mocks/actions/step_function.py +0 -26
  62. clearskies_aws/secrets/__init__.py +0 -7
  63. clearskies_aws/secrets/additional_configs/__init__.py +0 -54
  64. clearskies_aws/secrets/additional_configs/iam_db_auth.py +0 -29
  65. clearskies_aws/secrets/additional_configs/iam_db_auth_with_ssm.py +0 -92
  66. clearskies_aws/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py +0 -81
  67. clearskies_aws/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssm_bastion.py +0 -141
  68. clearskies_aws/secrets/akeyless_with_ssm_cache.py +0 -38
  69. clearskies_aws/secrets/parameter_store.py +0 -50
  70. clearskies_aws/secrets/parameter_store_test.py +0 -18
  71. clearskies_aws/secrets/secrets_manager.py +0 -75
  72. clearskies_aws/secrets/secrets_manager_test.py +0 -18
  73. clearskies_aws/web_socket_connection_model.py +0 -43
@@ -1,72 +0,0 @@
1
- import unittest
2
- from unittest.mock import MagicMock, call
3
-
4
- from .assume_role import AssumeRole
5
-
6
-
7
- class AssumeRoleTest(unittest.TestCase):
8
- def test_with_external_id(self):
9
- sts = MagicMock()
10
- sts.assume_role = MagicMock(
11
- return_value={
12
- "Credentials": {
13
- "AccessKeyId": "access-key",
14
- "SecretAccessKey": "secret-key",
15
- "SessionToken": "session-token",
16
- }
17
- }
18
- )
19
- boto3 = MagicMock()
20
- boto3.client = MagicMock(return_value=sts)
21
- boto3.Session = MagicMock(return_value='MOAR BOTO')
22
-
23
- assume_role = AssumeRole(role_arn='aws:arn:role/name', external_id='12345')
24
- self.assertEqual("MOAR BOTO", assume_role(boto3))
25
- boto3.client.assert_called_with("sts")
26
- boto3.Session.assert_called_with(
27
- aws_access_key_id="access-key",
28
- aws_secret_access_key="secret-key",
29
- aws_session_token="session-token",
30
- )
31
- sts.assume_role.assert_called_with(
32
- RoleArn='aws:arn:role/name',
33
- RoleSessionName="clearkies-aws",
34
- DurationSeconds=3600,
35
- ExternalId="12345",
36
- )
37
-
38
- def test_with_source(self):
39
- sts = MagicMock()
40
- sts.assume_role = MagicMock(
41
- return_value={
42
- "Credentials": {
43
- "AccessKeyId": "access-key",
44
- "SecretAccessKey": "secret-key",
45
- "SessionToken": "session-token",
46
- }
47
- }
48
- )
49
- boto3 = MagicMock()
50
- boto3.client = MagicMock(return_value=sts)
51
- boto3.Session = MagicMock(return_value='MOAR BOTO')
52
- source = MagicMock(return_value=boto3)
53
-
54
- assume_role = AssumeRole(
55
- role_arn='aws:arn:role/name',
56
- source=source,
57
- role_session_name="sup",
58
- duration=7200,
59
- )
60
- self.assertEqual("MOAR BOTO", assume_role("not-boto3"))
61
- boto3.client.assert_called_with("sts")
62
- boto3.Session.assert_called_with(
63
- aws_access_key_id="access-key",
64
- aws_secret_access_key="secret-key",
65
- aws_session_token="session-token",
66
- )
67
- sts.assume_role.assert_called_with(
68
- RoleArn='aws:arn:role/name',
69
- RoleSessionName="sup",
70
- DurationSeconds=7200,
71
- )
72
- source.assert_called_with("not-boto3")
@@ -1,194 +0,0 @@
1
- import boto3
2
- import clearskies
3
- import datetime
4
-
5
- from botocore.exceptions import ClientError
6
- from clearskies.environment import Environment
7
- from clearskies.models import Models
8
- from collections.abc import Sequence
9
- from typing import Any, Callable, List, Optional, Union
10
- from types import ModuleType
11
-
12
- from ..di import StandardDependencies
13
- from .assume_role import AssumeRole
14
- from .action_aws import ActionAws
15
- class SES(ActionAws):
16
- _name = "ses"
17
-
18
- def __init__(self, environment: Environment, boto3: boto3, di: StandardDependencies) -> None:
19
- """Setup action."""
20
- super().__init__(environment, boto3, di)
21
-
22
- def configure(
23
- self,
24
- sender,
25
- to: Optional[Union[list, str, Callable]] = None,
26
- cc: Optional[Union[list, str, Callable]] = None,
27
- bcc: Optional[Union[list, str, Callable]] = None,
28
- subject: Optional[str] = None,
29
- message: Optional[str] = None,
30
- subject_template: Optional[str] = None,
31
- message_template: Optional[str] = None,
32
- subject_template_file: Optional[str] = None,
33
- message_template_file: Optional[str] = None,
34
- assume_role: Optional[AssumeRole] = None,
35
- dependencies_for_template: Optional[list[Any]] = None,
36
- when: Optional[Callable] = None,
37
- ) -> None:
38
- """Configure the rules for this email notification."""
39
- super().configure(message_callable=None, when=when, assume_role=assume_role)
40
- self.destinations = {
41
- "to": [],
42
- "cc": [],
43
- "bcc": [],
44
- }
45
- # this just moves the data from the various "to" inputs (to, cc, bcc) into the self.destinations
46
- # dictionary, after normalizing it so that it is always a list.
47
- for key in self.destinations.keys():
48
- destination_values = locals()[key]
49
- if not destination_values:
50
- continue
51
- if type(destination_values) == str or callable(destination_values):
52
- self.destinations[key] = [destination_values]
53
- else:
54
- self.destinations[key] = destination_values
55
- self.subject = subject
56
- self.message = message
57
- self.sender = sender
58
- self.subject_template = None
59
- self.message_template = None
60
- self.dependencies_for_template = dependencies_for_template if dependencies_for_template else []
61
-
62
- if not to and not cc:
63
- raise ValueError("You must configure at least one 'to' address or one 'cc' address")
64
- num_subjects = 0
65
- num_messages = 0
66
- for source in [subject, subject_template, subject_template_file]:
67
- if source:
68
- num_subjects += 1
69
- for source in [message, message_template, message_template_file]:
70
- if source:
71
- num_messages += 1
72
- if num_subjects > 1:
73
- raise ValueError(
74
- "More than one of 'subject', 'subject_template', or 'subject_template_file' was set, but only one of these may be set."
75
- )
76
- if num_messages > 1:
77
- raise ValueError(
78
- "More than one of 'message', 'message_template', or 'message_template_file' was set, but only one of these may be set."
79
- )
80
-
81
- if subject_template_file:
82
- import jinja2
83
- with open(subject_template_file, "r", encoding="utf-8") as template:
84
- self.subject_template = jinja2.Template(template.read())
85
- elif subject_template:
86
- import jinja2
87
- self.subject_template = jinja2.Template(subject_template)
88
-
89
- if message_template_file:
90
- import jinja2
91
- with open(message_template_file, "r", encoding="utf-8") as template:
92
- self.message_template = jinja2.Template(template.read())
93
- elif message_template:
94
- import jinja2
95
- self.message_template = jinja2.Template(message_template)
96
-
97
- def _execute_action(self, client: ModuleType, model: Models) -> None:
98
- """Send a notification as configured."""
99
- utcnow = self.di.build('utcnow')
100
-
101
- tos = self._resolve_destination("to", model)
102
- if not tos:
103
- return
104
- response = client.send_email(
105
- Destination={
106
- "ToAddresses": tos,
107
- "CcAddresses": self._resolve_destination("cc", model),
108
- "BccAddresses": self._resolve_destination("bcc", model),
109
- },
110
- Message={
111
- "Body": {
112
- "Html": {
113
- "Charset": "utf-8",
114
- "Data": self._resolve_message_as_html(model, utcnow),
115
- },
116
- "Text": {
117
- "Charset": "utf-8",
118
- "Data": self._resolve_message_as_text(model, utcnow),
119
- },
120
- },
121
- "Subject": {
122
- "Charset": "utf-8",
123
- "Data": self._resolve_subject(model, utcnow)
124
- },
125
- },
126
- Source=self.sender,
127
- )
128
-
129
- def _resolve_destination(self, name: str, model: clearskies.Model) -> List[str]:
130
- """
131
- Return a list of to/cc/bcc addresses.
132
-
133
- Each entry can be:
134
-
135
- 1. An email address
136
- 2. The name of a column in the model that contains an email address
137
- """
138
- resolved = []
139
- destinations = self.destinations[name]
140
- for destination in destinations:
141
- if callable(destination):
142
- more = self.di.call_function(destination, model=model)
143
- if not isinstance(more, list):
144
- more = [more]
145
- for entry in more:
146
- if not isinstance(entry, str):
147
- raise ValueError(f"I invoked a callable to fetch the '{name}' addresses for model '{model.__class__.__name__}' but it returned something other than a string. Callables must return a valid email address or a list of email addresses.")
148
- if "@" not in entry:
149
- raise ValueError(f"I invoked a callable to fetch the '{name}' addresses for model '{model.__class__.__name__}' but it returned a non-email address. Callables must return a valid email address or a list of email addresses.")
150
- resolved.extend(more)
151
- continue
152
- if "@" in destination:
153
- resolved.append(destination)
154
- continue
155
- resolved.append(model.get(destination))
156
- return resolved
157
-
158
- def _resolve_message_as_html(self, model: clearskies.Model, now: datetime.datetime) -> str:
159
- """Build the HTML for a message."""
160
- if self.message:
161
- return self.message
162
-
163
- if self.message_template:
164
- return str(
165
- self.message_template.render(model=model, now=now, **self.more_template_variables(), text_in_html=True)
166
- )
167
-
168
- return ""
169
-
170
- def _resolve_message_as_text(self, model: clearskies.Model, now: datetime.datetime) -> str:
171
- """Build the text for a message."""
172
- if self.message:
173
- return self.message
174
-
175
- if self.message_template:
176
- return str(self.message_template.render(model=model, now=now, **self.more_template_variables()))
177
-
178
- return ""
179
-
180
- def _resolve_subject(self, model: clearskies.Model, now: datetime.datetime) -> str:
181
- """Build the subject for a message."""
182
- if self.subject:
183
- return self.subject
184
-
185
- if self.subject_template:
186
- return str(self.subject_template.render(model=model, now=now, **self.more_template_variables()))
187
-
188
- return ""
189
-
190
- def more_template_variables(self) -> dict[str, Any]:
191
- more_variables = {}
192
- for dependency_name in self.dependencies_for_template:
193
- more_variables[dependency_name] = self.di.build(dependency_name, cache=True)
194
- return more_variables
@@ -1,89 +0,0 @@
1
- import unittest
2
- from unittest.mock import MagicMock, call
3
- from .ses import SES
4
- import clearskies
5
- from ..di import StandardDependencies
6
- class SESTest(unittest.TestCase):
7
- def setUp(self):
8
- self.di = StandardDependencies()
9
- self.di.bind('environment', {'AWS_REGION': 'us-east-2'})
10
- self.ses = MagicMock()
11
- self.ses.send_email = MagicMock()
12
- self.boto3 = MagicMock()
13
- self.boto3.client = MagicMock(return_value=self.ses)
14
- self.environment = MagicMock()
15
- self.environment.get = MagicMock(return_value='us-east-1')
16
-
17
- def test_send(self):
18
- ses = SES(self.environment, self.boto3, self.di)
19
- ses.configure(
20
- 'test@example.com', to='jane@example.com', subject='welcome!', message_template='hi {{ model.id }}!'
21
- )
22
- model = MagicMock()
23
- model.id = 'asdf'
24
- ses(model)
25
- self.ses.send_email.assert_has_calls([
26
- call(
27
- Destination={
28
- 'ToAddresses': ['jane@example.com'],
29
- 'CcAddresses': [],
30
- 'BccAddresses': []
31
- },
32
- Message={
33
- 'Body': {
34
- 'Html': {
35
- 'Charset': 'utf-8',
36
- 'Data': 'hi asdf!'
37
- },
38
- 'Text': {
39
- 'Charset': 'utf-8',
40
- 'Data': 'hi asdf!'
41
- }
42
- },
43
- 'Subject': {
44
- 'Charset': 'utf-8',
45
- 'Data': 'welcome!'
46
- }
47
- },
48
- Source='test@example.com'
49
- ),
50
- ])
51
-
52
- def test_send_callable(self):
53
- ses = SES(self.environment, self.boto3, self.di)
54
- ses.configure(
55
- 'test@example.com',
56
- to=lambda model: 'jane@example.com',
57
- bcc=lambda model: ['bob@example.com', 'greg@example.com'],
58
- subject='welcome!',
59
- message_template='hi {{ model.id }}!'
60
- )
61
- model = MagicMock()
62
- model.id = 'asdf'
63
- ses(model)
64
- self.ses.send_email.assert_has_calls([
65
- call(
66
- Destination={
67
- 'ToAddresses': ['jane@example.com'],
68
- 'CcAddresses': [],
69
- 'BccAddresses': ['bob@example.com', 'greg@example.com']
70
- },
71
- Message={
72
- 'Body': {
73
- 'Html': {
74
- 'Charset': 'utf-8',
75
- 'Data': 'hi asdf!'
76
- },
77
- 'Text': {
78
- 'Charset': 'utf-8',
79
- 'Data': 'hi asdf!'
80
- }
81
- },
82
- 'Subject': {
83
- 'Charset': 'utf-8',
84
- 'Data': 'welcome!'
85
- }
86
- },
87
- Source='test@example.com'
88
- ),
89
- ])
@@ -1,64 +0,0 @@
1
- import boto3
2
- import clearskies
3
- import datetime
4
- import json
5
-
6
- from botocore.exceptions import ClientError
7
- from collections.abc import Sequence
8
- from clearskies.environment import Environment
9
- from clearskies.models import Models
10
- from types import ModuleType
11
- from typing import List, Optional, Callable, cast
12
-
13
- from ..di import StandardDependencies
14
- from .assume_role import AssumeRole
15
- from .action_aws import ActionAws
16
- class SNS(ActionAws):
17
- _name = "sns"
18
-
19
- def __init__(self, environment: Environment, boto3: boto3, di: StandardDependencies) -> None:
20
- super().__init__(environment, boto3, di)
21
-
22
- def configure(
23
- self,
24
- topic=None,
25
- topic_environment_key=None,
26
- topic_callable: Optional[Callable] = None,
27
- message_callable: Optional[Callable] = None,
28
- when: Optional[Callable] = None,
29
- assume_role: Optional[AssumeRole] = None,
30
- ) -> None:
31
- """Configures the action for SNS."""
32
- super().configure(message_callable=message_callable, when=when, assume_role=assume_role)
33
-
34
- self.topic = topic
35
- self.topic_environment_key = topic_environment_key
36
- self.topic_callable = topic_callable
37
-
38
- topics = 0
39
- for value in [topic, topic_environment_key, topic_callable]:
40
- if value:
41
- topics += 1
42
- if topics > 1:
43
- raise ValueError(
44
- "You can only provide one of 'topic', 'topic_environment_key', or 'topic_callable', but more than one were provided."
45
- )
46
- if not topics:
47
- raise ValueError("You must provide at least one of 'topic', 'topic_environment_key', or 'topic_callable'.")
48
-
49
- def _execute_action(self, client: ModuleType, model: Models) -> None:
50
- """Send a notification as configured."""
51
- topic_arn = self.get_topic_arn(model)
52
- if not topic_arn:
53
- return
54
- client.publish(
55
- TopicArn=self.get_topic_arn(model),
56
- Message=self.get_message_body(model),
57
- )
58
-
59
- def get_topic_arn(self, model: Models) -> str:
60
- if self.topic:
61
- return self.topic
62
- if self.topic_environment_key:
63
- return self.environment.get(self.topic_environment_key)
64
- return self.di.call_function(self.topic_callable, model=model)
@@ -1,77 +0,0 @@
1
- import unittest
2
- import boto3
3
-
4
- from unittest.mock import MagicMock, call
5
- from .sns import SNS
6
- import clearskies
7
- from ..di import StandardDependencies
8
- import json
9
- from collections import OrderedDict
10
- class User(clearskies.Model):
11
- def __init__(self, memory_backend, columns):
12
- super().__init__(memory_backend, columns)
13
-
14
- def columns_configuration(self):
15
- return OrderedDict([
16
- clearskies.column_types.string('name'),
17
- clearskies.column_types.email('email'),
18
- ])
19
- class SNSTest(unittest.TestCase):
20
- def setUp(self):
21
- self.di = StandardDependencies()
22
- self.di.bind('environment', {'AWS_REGION': 'us-east-2'})
23
- self.users = self.di.build(User)
24
- self.sns = MagicMock()
25
- self.sns.publish = MagicMock()
26
- self.boto3 = MagicMock()
27
- self.boto3.client = MagicMock(return_value=self.sns)
28
- self.when = None
29
- self.environment = MagicMock()
30
- self.environment.get = MagicMock(return_value='us-east-1')
31
-
32
- def always(self, model):
33
- self.when = model
34
- return True
35
-
36
- def never(self, model):
37
- self.when = model
38
- return False
39
-
40
- def test_send(self):
41
- sns = SNS(self.environment, self.boto3, self.di)
42
- sns.configure(
43
- topic='arn:aws:my-topic',
44
- when=self.always,
45
- )
46
- user = self.users.model({
47
- "id": "1-2-3-4",
48
- "name": "Jane",
49
- "email": "jane@example.com",
50
- })
51
- sns(user)
52
- self.sns.publish.assert_has_calls([
53
- call(
54
- TopicArn='arn:aws:my-topic',
55
- Message=json.dumps({
56
- "id": "1-2-3-4",
57
- "name": "Jane",
58
- "email": "jane@example.com",
59
- }),
60
- ),
61
- ])
62
- self.assertEqual(id(user), id(self.when))
63
-
64
- def test_not_now(self):
65
- sns = SNS(self.environment, self.boto3, self.di)
66
- sns.configure(
67
- topic='arn:aws:my-topic',
68
- when=self.never,
69
- )
70
- user = self.users.model({
71
- "id": "1-2-3-4",
72
- "name": "Jane",
73
- "email": "jane@example.com",
74
- })
75
- sns(user)
76
- self.sns.publish.assert_not_called()
77
- self.assertEqual(id(user), id(self.when))
@@ -1,82 +0,0 @@
1
- import boto3
2
- import json
3
- import datetime
4
-
5
- from botocore.exceptions import ClientError
6
- from clearskies.environment import Environment
7
- from clearskies.model import Model
8
- from collections.abc import Sequence
9
- from collections import OrderedDict
10
- from types import ModuleType
11
- from typing import List, Optional, Callable, Union
12
-
13
- from ..di import StandardDependencies
14
- from . import assume_role
15
- from .action_aws import ActionAws
16
- class SQS(ActionAws):
17
- _name = "sqs"
18
-
19
- def __init__(self, environment: Environment, boto3: boto3, di: StandardDependencies) -> None:
20
- """Setup action."""
21
- super().__init__(environment, boto3, di)
22
-
23
- def configure(
24
- self,
25
- queue_url: str = '',
26
- queue_url_environment_key: str = '',
27
- queue_url_callable: Optional[Callable] = None,
28
- message_callable: Optional[Callable] = None,
29
- when: Optional[Callable] = None,
30
- assume_role: Optional[assume_role.AssumeRole] = None,
31
- message_group_id: Optional[Union[str, Callable]] = None,
32
- ) -> None:
33
- super().configure(message_callable=message_callable, when=when, assume_role=assume_role)
34
-
35
- self.queue_url = queue_url
36
- self.queue_url_environment_key = queue_url_environment_key
37
- self.queue_url_callable = queue_url_callable
38
- self.message_group_id = message_group_id
39
-
40
- queue_urls = 0
41
- for value in [queue_url, queue_url_environment_key, queue_url_callable]:
42
- if value:
43
- queue_urls += 1
44
- if queue_urls > 1:
45
- raise ValueError(
46
- "You can only provide one of 'queue_url', 'queue_url_environment_key', or 'queue_url_callable', but more than one were provided."
47
- )
48
- if not queue_urls:
49
- raise ValueError(
50
- "You must provide at least one of 'queue_url', 'queue_url_environment_key', or 'queue_url_callable'."
51
- )
52
- if message_group_id and not callable(message_group_id) and not isinstance(message_group_id, str):
53
- raise ValueError(
54
- "If provided, 'message_group_id' must be a string or callable, but the provided value was neither."
55
- )
56
-
57
- def _execute_action(self, client: ModuleType, model: Model) -> None:
58
- """Send a notification as configured."""
59
- params = {
60
- "QueueUrl": self.get_queue_url(model),
61
- "MessageBody": self.get_message_body(model),
62
- }
63
- if not params["QueueUrl"]:
64
- return
65
-
66
- if self.message_group_id:
67
- if callable(self.message_group_id):
68
- message_group_id = self.di.call_function(self.message_group_id, model=model)
69
- if not isinstance(message_group_id, str):
70
- raise ValueError(f"I called the message_group_id function for SQS for model '{model.__class__.__name__}' but the value it returned was not a string. The message group id must be a string.")
71
- else:
72
- message_group_id = self.message_group_id
73
- params["MessageGroupId"] = message_group_id
74
-
75
- client.send_message(**params)
76
-
77
- def get_queue_url(self, model: Model):
78
- if self.queue_url:
79
- return self.queue_url
80
- if self.queue_url_environment_key:
81
- return self.environment.get(self.queue_url_environment_key)
82
- return self.di.call_function(self.queue_url_callable, model=model)