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,6 +1,8 @@
1
1
  from __future__ import annotations
2
+
2
3
  from types import ModuleType
3
- from typing import Optional
4
+
5
+
4
6
  class AssumeRole:
5
7
  """
6
8
  Used by the various actions if you need to assume a role before making an AWS call.
@@ -16,54 +18,65 @@ class AssumeRole:
16
18
  class User(clearskies.Model):
17
19
  def __init__(self, memory_backend, columns):
18
20
  super().__init__(memory_backend, columns)
21
+
19
22
  def columns_configuration(self):
20
- return OrderedDict([
21
- clearskies.column_types.string(
22
- 'name',
23
- on_change=[
24
- clearskies_aws.actions.sqs(
25
- queue_url='https://queue.url.example.aws.com',
26
- assume_role=clearskies_aws.actions.assume_role(
27
- role_arn='arn:aws:iam:role/name',
28
- external_id='12345',
23
+ return OrderedDict(
24
+ [
25
+ clearskies.column_types.string(
26
+ "name",
27
+ on_change=[
28
+ clearskies_aws.actions.sqs(
29
+ queue_url="https://queue.url.example.aws.com",
30
+ assume_role=clearskies_aws.actions.assume_role(
31
+ role_arn="arn:aws:iam:role/name",
32
+ external_id="12345",
33
+ ),
29
34
  )
30
- )
31
- ],
32
- ),
33
- ])
35
+ ],
36
+ ),
37
+ ]
38
+ )
34
39
 
35
40
  Example:
36
41
  Here's a more complicated example with a double-assumme-role to show how to combine them::
37
42
 
38
43
  first_assume_role = clearskies_aws.actions.assume_role(
39
- role_arn='arn:aws:123456789012:iam:role/name',
40
- external_id='12345',
44
+ role_arn="arn:aws:123456789012:iam:role/name",
45
+ external_id="12345",
41
46
  )
42
47
  final_assume_role = clearskies_aws.actions.assume_role(
43
- role_arn='arn:aws:210987654321:iam:role/name-2',
44
- external_id='54321',
48
+ role_arn="arn:aws:210987654321:iam:role/name-2",
49
+ external_id="54321",
45
50
  source=first_assume_role,
46
51
  )
52
+
53
+
47
54
  class User(clearskies.Model):
48
55
  def __init__(self, memory_backend, columns):
49
56
  super().__init__(memory_backend, columns)
57
+
50
58
  def columns_configuration(self):
51
- return OrderedDict([
52
- clearskies.column_types.string(
53
- 'name',
54
- on_change=[clearskies_aws.actions.sqs(
55
- queue_url='https://queue.url.example.aws.com',
56
- assume_role=final_assume_role,
57
- )],
58
- ),
59
- ])
59
+ return OrderedDict(
60
+ [
61
+ clearskies.column_types.string(
62
+ "name",
63
+ on_change=[
64
+ clearskies_aws.actions.sqs(
65
+ queue_url="https://queue.url.example.aws.com",
66
+ assume_role=final_assume_role,
67
+ )
68
+ ],
69
+ ),
70
+ ]
71
+ )
60
72
 
61
73
  """
74
+
62
75
  role_arn = ""
63
76
  external_id = ""
64
77
  role_session_name = ""
65
78
  duration = 3600
66
- source: Optional[AssumeRole] = None
79
+ source: AssumeRole | None = None
67
80
 
68
81
  def __init__(
69
82
  self,
@@ -71,7 +84,7 @@ class AssumeRole:
71
84
  external_id: str = "",
72
85
  role_session_name: str = "",
73
86
  duration: int = 3600,
74
- source: Optional[AssumeRole] = None
87
+ source: AssumeRole | None = None,
75
88
  ):
76
89
  """Assume a role."""
77
90
  self.role_arn = role_arn
@@ -91,7 +104,7 @@ class AssumeRole:
91
104
  "DurationSeconds": self.duration,
92
105
  }
93
106
  if self.external_id:
94
- calling_params['ExternalId'] = self.external_id
107
+ calling_params["ExternalId"] = self.external_id
95
108
  credentials = boto3.client("sts").assume_role(**calling_params)["Credentials"]
96
109
 
97
110
  # now let's make a new session using those
@@ -1,72 +1,84 @@
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
1
+ from __future__ import annotations
11
2
 
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)
3
+ import datetime
4
+ from typing import TYPE_CHECKING, Any, Callable
21
5
 
22
- def configure(
6
+ import clearskies
7
+ import jinja2
8
+ from clearskies import Model
9
+ from clearskies.configs import Any as AnyConfig
10
+ from clearskies.configs import Email, EmailOrEmailListOrCallable, String
11
+ from clearskies.decorators import parameters_to_properties
12
+ from types_boto3_ses import SESClient
13
+
14
+ from clearskies_aws.actions import action_aws
15
+
16
+ if TYPE_CHECKING:
17
+ from clearskies_aws.actions import AssumeRole
18
+
19
+
20
+ class SES(action_aws.ActionAws[SESClient]):
21
+ sender = Email(required=True)
22
+ to = EmailOrEmailListOrCallable(required=False)
23
+ cc = EmailOrEmailListOrCallable(required=False)
24
+ bcc = EmailOrEmailListOrCallable(required=False)
25
+ subject = String(required=False)
26
+ message = String(required=False)
27
+ subject_template = AnyConfig(required=False)
28
+ message_template = AnyConfig(required=False)
29
+ subject_template_file = String(required=False)
30
+ message_template_file = String(required=False)
31
+ dependencies_for_template: list[Any] = []
32
+
33
+ destinations: dict[str, list[str | Callable]] = {
34
+ "to": [],
35
+ "cc": [],
36
+ "bcc": [],
37
+ }
38
+
39
+ @parameters_to_properties
40
+ def __init__(
23
41
  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,
42
+ sender: str,
43
+ to: list | str | Callable | None = None,
44
+ cc: list | str | Callable | None = None,
45
+ bcc: list | str | Callable | None = None,
46
+ subject: str | None = None,
47
+ message: str | None = None,
48
+ subject_template: jinja2.Template | None = None,
49
+ message_template: jinja2.Template | None = None,
50
+ subject_template_file: str | None = None,
51
+ message_template_file: str | None = None,
52
+ assume_role: AssumeRole | None = None,
53
+ dependencies_for_template: list[Any] = [],
54
+ when: Callable | None = None,
37
55
  ) -> None:
38
56
  """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
- }
57
+ super().__init__(service_name="ses", assume_role=assume_role, when=when)
58
+
59
+ def configure(self):
60
+ self.finalize_and_validate_configuration()
61
+ # First finalize and validate configuration to set up defaults
62
+
45
63
  # this just moves the data from the various "to" inputs (to, cc, bcc) into the self.destinations
46
64
  # dictionary, after normalizing it so that it is always a list.
65
+ if not self.to and not self.cc and not self.bcc:
66
+ raise ValueError("You must configure at least one 'to' address or one 'cc' address or one 'bcc' address")
67
+
47
68
  for key in self.destinations.keys():
48
- destination_values = locals()[key]
69
+ destination_values = getattr(self, key, None)
49
70
  if not destination_values:
50
71
  continue
51
72
  if type(destination_values) == str or callable(destination_values):
52
73
  self.destinations[key] = [destination_values]
53
74
  else:
54
75
  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
76
  num_subjects = 0
65
77
  num_messages = 0
66
- for source in [subject, subject_template, subject_template_file]:
78
+ for source in [self.subject, self.subject_template, self.subject_template_file]:
67
79
  if source:
68
80
  num_subjects += 1
69
- for source in [message, message_template, message_template_file]:
81
+ for source in [self.message, self.message_template, self.message_template_file]:
70
82
  if source:
71
83
  num_messages += 1
72
84
  if num_subjects > 1:
@@ -78,25 +90,21 @@ class SES(ActionAws):
78
90
  "More than one of 'message', 'message_template', or 'message_template_file' was set, but only one of these may be set."
79
91
  )
80
92
 
81
- if subject_template_file:
82
- import jinja2
83
- with open(subject_template_file, "r", encoding="utf-8") as template:
93
+ if self.subject_template_file:
94
+ with open(self.subject_template_file, "r", encoding="utf-8") as template:
84
95
  self.subject_template = jinja2.Template(template.read())
85
- elif subject_template:
86
- import jinja2
87
- self.subject_template = jinja2.Template(subject_template)
96
+ elif self.subject_template and not isinstance(self.subject_template, jinja2.Template):
97
+ self.subject_template = jinja2.Template(self.subject_template)
88
98
 
89
- if message_template_file:
90
- import jinja2
91
- with open(message_template_file, "r", encoding="utf-8") as template:
99
+ if self.message_template_file:
100
+ with open(self.message_template_file, "r", encoding="utf-8") as template:
92
101
  self.message_template = jinja2.Template(template.read())
93
- elif message_template:
94
- import jinja2
95
- self.message_template = jinja2.Template(message_template)
102
+ elif self.message_template and not isinstance(self.message_template, jinja2.Template):
103
+ self.message_template = jinja2.Template(self.message_template)
96
104
 
97
- def _execute_action(self, client: ModuleType, model: Models) -> None:
105
+ def _execute_action(self, client: SESClient, model: Model) -> None:
98
106
  """Send a notification as configured."""
99
- utcnow = self.di.build('utcnow')
107
+ utcnow = self.di.build("utcnow")
100
108
 
101
109
  tos = self._resolve_destination("to", model)
102
110
  if not tos:
@@ -118,15 +126,12 @@ class SES(ActionAws):
118
126
  "Data": self._resolve_message_as_text(model, utcnow),
119
127
  },
120
128
  },
121
- "Subject": {
122
- "Charset": "utf-8",
123
- "Data": self._resolve_subject(model, utcnow)
124
- },
129
+ "Subject": {"Charset": "utf-8", "Data": self._resolve_subject(model, utcnow)},
125
130
  },
126
131
  Source=self.sender,
127
132
  )
128
133
 
129
- def _resolve_destination(self, name: str, model: clearskies.Model) -> List[str]:
134
+ def _resolve_destination(self, name: str, model: clearskies.Model) -> list[str]:
130
135
  """
131
136
  Return a list of to/cc/bcc addresses.
132
137
 
@@ -144,15 +149,19 @@ class SES(ActionAws):
144
149
  more = [more]
145
150
  for entry in more:
146
151
  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.")
152
+ raise ValueError(
153
+ 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."
154
+ )
148
155
  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.")
156
+ raise ValueError(
157
+ 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."
158
+ )
150
159
  resolved.extend(more)
151
160
  continue
152
161
  if "@" in destination:
153
162
  resolved.append(destination)
154
163
  continue
155
- resolved.append(model.get(destination))
164
+ resolved.append(getattr(model, destination))
156
165
  return resolved
157
166
 
158
167
  def _resolve_message_as_html(self, model: clearskies.Model, now: datetime.datetime) -> str:
@@ -1,42 +1,39 @@
1
- import boto3
2
- import clearskies
3
- import datetime
4
- import json
1
+ from __future__ import annotations
5
2
 
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
3
+ from typing import Callable
4
+
5
+ from clearskies import Model
6
+ from clearskies.configs import Callable as CallableConfig
7
+ from clearskies.configs import String
8
+ from clearskies.decorators import parameters_to_properties
9
+ from types_boto3_sns import SNSClient
12
10
 
13
- from ..di import StandardDependencies
14
- from .assume_role import AssumeRole
15
11
  from .action_aws import ActionAws
16
- class SNS(ActionAws):
17
- _name = "sns"
12
+ from .assume_role import AssumeRole
18
13
 
19
- def __init__(self, environment: Environment, boto3: boto3, di: StandardDependencies) -> None:
20
- super().__init__(environment, boto3, di)
21
14
 
22
- def configure(
15
+ class SNS(ActionAws[SNSClient]):
16
+ topic = String(required=False)
17
+ topic_environment_key = String(required=False)
18
+ topic_callable = CallableConfig(required=False)
19
+
20
+ @parameters_to_properties
21
+ def __init__(
23
22
  self,
24
23
  topic=None,
25
24
  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,
25
+ topic_callable: Callable | None = None,
26
+ message_callable: Callable | None = None,
27
+ when: Callable | None = None,
28
+ assume_role: AssumeRole | None = None,
30
29
  ) -> 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
30
+ """Configure the SNS action."""
31
+ super().__init__(service_name="sns", message_callable=message_callable, when=when, assume_role=assume_role)
37
32
 
33
+ def configure(self):
34
+ self.finalize_and_validate_configuration()
38
35
  topics = 0
39
- for value in [topic, topic_environment_key, topic_callable]:
36
+ for value in [self.topic, self.topic_environment_key, self.topic_callable]:
40
37
  if value:
41
38
  topics += 1
42
39
  if topics > 1:
@@ -46,7 +43,7 @@ class SNS(ActionAws):
46
43
  if not topics:
47
44
  raise ValueError("You must provide at least one of 'topic', 'topic_environment_key', or 'topic_callable'.")
48
45
 
49
- def _execute_action(self, client: ModuleType, model: Models) -> None:
46
+ def _execute_action(self, client: SNSClient, model: Model) -> None:
50
47
  """Send a notification as configured."""
51
48
  topic_arn = self.get_topic_arn(model)
52
49
  if not topic_arn:
@@ -56,7 +53,7 @@ class SNS(ActionAws):
56
53
  Message=self.get_message_body(model),
57
54
  )
58
55
 
59
- def get_topic_arn(self, model: Models) -> str:
56
+ def get_topic_arn(self, model: Model) -> str:
60
57
  if self.topic:
61
58
  return self.topic
62
59
  if self.topic_environment_key:
@@ -1,44 +1,41 @@
1
- import boto3
2
- import json
3
- import datetime
1
+ from __future__ import annotations
4
2
 
5
- from botocore.exceptions import ClientError
6
- from clearskies.environment import Environment
3
+ from typing import Callable
4
+
5
+ from clearskies.configs import Callable as CallableConfig
6
+ from clearskies.configs import String
7
+ from clearskies.decorators import parameters_to_properties
7
8
  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
9
+ from types_boto3_sqs import SQSClient
12
10
 
13
- from ..di import StandardDependencies
14
11
  from . import assume_role
15
12
  from .action_aws import ActionAws
16
- class SQS(ActionAws):
17
- _name = "sqs"
18
13
 
19
- def __init__(self, environment: Environment, boto3: boto3, di: StandardDependencies) -> None:
20
- """Setup action."""
21
- super().__init__(environment, boto3, di)
22
14
 
23
- def configure(
15
+ class SQS(ActionAws[SQSClient]):
16
+ queue_url = String(required=False)
17
+ queue_url_environment_key = String(required=False)
18
+ queue_url_callable = CallableConfig(required=False)
19
+ message_group_id = CallableConfig(required=False)
20
+
21
+ @parameters_to_properties
22
+ def __init__(
24
23
  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,
24
+ queue_url: str = "",
25
+ queue_url_environment_key: str = "",
26
+ queue_url_callable: Callable | None = None,
27
+ message_callable: Callable | None = None,
28
+ when: Callable | None = None,
29
+ assume_role: assume_role.AssumeRole | None = None,
30
+ message_group_id: str | Callable | None = None,
32
31
  ) -> 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
32
+ """Set up the SQS action."""
33
+ super().__init__(service_name="sqs", message_callable=message_callable, when=when, assume_role=assume_role)
39
34
 
35
+ def configure(self):
36
+ self.finalize_and_validate_configuration()
40
37
  queue_urls = 0
41
- for value in [queue_url, queue_url_environment_key, queue_url_callable]:
38
+ for value in [self.queue_url, self.queue_url_environment_key, self.queue_url_callable]:
42
39
  if value:
43
40
  queue_urls += 1
44
41
  if queue_urls > 1:
@@ -49,12 +46,12 @@ class SQS(ActionAws):
49
46
  raise ValueError(
50
47
  "You must provide at least one of 'queue_url', 'queue_url_environment_key', or 'queue_url_callable'."
51
48
  )
52
- if message_group_id and not callable(message_group_id) and not isinstance(message_group_id, str):
49
+ if self.message_group_id and not callable(self.message_group_id) and not isinstance(self.message_group_id, str):
53
50
  raise ValueError(
54
51
  "If provided, 'message_group_id' must be a string or callable, but the provided value was neither."
55
52
  )
56
53
 
57
- def _execute_action(self, client: ModuleType, model: Model) -> None:
54
+ def _execute_action(self, client: SQSClient, model: Model) -> None:
58
55
  """Send a notification as configured."""
59
56
  params = {
60
57
  "QueueUrl": self.get_queue_url(model),
@@ -67,7 +64,9 @@ class SQS(ActionAws):
67
64
  if callable(self.message_group_id):
68
65
  message_group_id = self.di.call_function(self.message_group_id, model=model)
69
66
  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.")
67
+ raise ValueError(
68
+ 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."
69
+ )
71
70
  else:
72
71
  message_group_id = self.message_group_id
73
72
  params["MessageGroupId"] = message_group_id
@@ -1,39 +1,45 @@
1
- import boto3
1
+ from __future__ import annotations
2
2
 
3
- from clearskies.environment import Environment
4
- from clearskies.models import Models
5
- from types import ModuleType
6
- from typing import List, Optional, Callable, cast
3
+ from typing import Callable
4
+
5
+ import boto3
6
+ from clearskies import Model
7
+ from clearskies.configs import Callable as CallableConfig
8
+ from clearskies.configs import String
9
+ from clearskies.decorators import parameters_to_properties
10
+ from types_boto3_stepfunctions import SFNClient
7
11
 
8
- from ..di import StandardDependencies
9
- from .assume_role import AssumeRole
10
12
  from .action_aws import ActionAws
11
- class StepFunction(ActionAws):
12
- _name = "stepfunctions"
13
+ from .assume_role import AssumeRole
14
+
13
15
 
14
- def __init__(self, environment: Environment, boto3: boto3, di: StandardDependencies) -> None:
15
- super().__init__(environment, boto3, di)
16
+ class StepFunction(ActionAws[SFNClient]):
17
+ arn = String(required=False)
18
+ arn_environment_key = String(required=False)
19
+ arn_callable = CallableConfig(required=False)
20
+ column_to_store_execution_arn = String(required=False)
16
21
 
17
- def configure(
22
+ @parameters_to_properties
23
+ def __init__(
18
24
  self,
19
- arn: Optional[str]=None,
20
- arn_environment_key: Optional[str] = None,
21
- arn_callable: Optional[Callable] = None,
22
- column_to_store_execution_arn: Optional[str] = None,
23
- message_callable: Optional[Callable] = None,
24
- when: Optional[Callable] = None,
25
- assume_role: Optional[AssumeRole] = None,
25
+ arn: str | None = None,
26
+ arn_environment_key: str | None = None,
27
+ arn_callable: Callable | None = None,
28
+ column_to_store_execution_arn: str | None = None,
29
+ message_callable: Callable | None = None,
30
+ when: Callable | None = None,
31
+ assume_role: AssumeRole | None = None,
26
32
  ) -> None:
27
- """Configures the action for the step function."""
28
- super().configure(message_callable=message_callable, when=when, assume_role=assume_role)
33
+ """Configure the Step Function action."""
34
+ super().__init__(
35
+ service_name="stepfunctions", message_callable=message_callable, when=when, assume_role=assume_role
36
+ )
29
37
 
30
- self.arn = arn
31
- self.arn_environment_key = arn_environment_key
32
- self.arn_callable = arn_callable
33
- self.column_to_store_execution_arn = column_to_store_execution_arn
38
+ def configure(self):
39
+ self.finalize_and_validate_configuration()
34
40
 
35
41
  arns = 0
36
- for value in [arn, arn_environment_key, arn_callable]:
42
+ for value in [self.arn, self.arn_environment_key, self.arn_callable]:
37
43
  if value:
38
44
  arns += 1
39
45
  if arns > 1:
@@ -43,22 +49,23 @@ class StepFunction(ActionAws):
43
49
  if not arns:
44
50
  raise ValueError("You must provide at least one of 'arn', 'arn_environment_key', or 'arn_callable'.")
45
51
 
46
- def _execute_action(self, client: ModuleType, model: Models) -> None:
52
+ def _execute_action(self, client: SFNClient, model: Model) -> None:
47
53
  """Send a notification as configured."""
48
54
  arn = self.get_arn(model)
49
55
  default_region = self.default_region()
50
- arn_region = arn.split(':')[3]
56
+ arn_region = arn.split(":")[3]
51
57
  if default_region and default_region != arn_region:
52
- client = self._getClient(region=arn_region)
58
+ self.region = arn_region
59
+ client = self._get_client()
53
60
  response = client.start_execution(
54
61
  stateMachineArn=self.get_arn(model),
55
62
  input=self.get_message_body(model),
56
63
  )
57
64
 
58
65
  if self.column_to_store_execution_arn:
59
- model.save({self.column_to_store_execution_arn: response['executionArn']})
66
+ model.save({self.column_to_store_execution_arn: response["executionArn"]})
60
67
 
61
- def get_arn(self, model: Models) -> str:
68
+ def get_arn(self, model: Model) -> str:
62
69
  if self.arn:
63
70
  return self.arn
64
71
  if self.arn_environment_key:
@@ -1,9 +1,16 @@
1
- from .dynamo_db_backend import DynamoDBBackend
2
- from .dynamo_db_parti_ql_backend import DynamoDBPartiQLBackend, DynamoDBPartiQLCursor
3
- from .dynamo_db_condition_parser import DynamoDBConditionParser
4
- from .sqs_backend import SqsBackend
1
+ from __future__ import annotations
2
+
3
+ from clearskies_aws.backends.backend import Backend
4
+ from clearskies_aws.backends.dynamo_db_backend import DynamoDBBackend
5
+ from clearskies_aws.backends.dynamo_db_condition_parser import DynamoDBConditionParser
6
+ from clearskies_aws.backends.dynamo_db_parti_ql_backend import (
7
+ DynamoDBPartiQLBackend,
8
+ DynamoDBPartiQLCursor,
9
+ )
10
+ from clearskies_aws.backends.sqs_backend import SqsBackend
5
11
 
6
12
  __all__ = [
13
+ "Backend",
7
14
  "DynamoDBBackend",
8
15
  "SqsBackend",
9
16
  "DynamoDBPartiQLBackend",