async-lambda-unstable 0.3.8__tar.gz → 0.3.10__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 (32) hide show
  1. {async-lambda-unstable-0.3.8 → async-lambda-unstable-0.3.10}/PKG-INFO +1 -1
  2. {async-lambda-unstable-0.3.8 → async-lambda-unstable-0.3.10}/async_lambda/__init__.py +3 -1
  3. {async-lambda-unstable-0.3.8 → async-lambda-unstable-0.3.10}/async_lambda/controller.py +32 -0
  4. async-lambda-unstable-0.3.10/async_lambda/models/case_insensitive_dict.py +82 -0
  5. {async-lambda-unstable-0.3.8 → async-lambda-unstable-0.3.10}/async_lambda/models/events/api_event.py +5 -4
  6. async-lambda-unstable-0.3.10/async_lambda/models/events/dynamodb_event.py +19 -0
  7. {async-lambda-unstable-0.3.8 → async-lambda-unstable-0.3.10}/async_lambda/models/task.py +16 -1
  8. {async-lambda-unstable-0.3.8 → async-lambda-unstable-0.3.10}/async_lambda_unstable.egg-info/PKG-INFO +1 -1
  9. {async-lambda-unstable-0.3.8 → async-lambda-unstable-0.3.10}/async_lambda_unstable.egg-info/SOURCES.txt +2 -0
  10. {async-lambda-unstable-0.3.8 → async-lambda-unstable-0.3.10}/README.md +0 -0
  11. {async-lambda-unstable-0.3.8 → async-lambda-unstable-0.3.10}/async_lambda/build_config.py +0 -0
  12. {async-lambda-unstable-0.3.8 → async-lambda-unstable-0.3.10}/async_lambda/cli.py +0 -0
  13. {async-lambda-unstable-0.3.8 → async-lambda-unstable-0.3.10}/async_lambda/client.py +0 -0
  14. {async-lambda-unstable-0.3.8 → async-lambda-unstable-0.3.10}/async_lambda/config.py +0 -0
  15. {async-lambda-unstable-0.3.8 → async-lambda-unstable-0.3.10}/async_lambda/defer.py +0 -0
  16. {async-lambda-unstable-0.3.8 → async-lambda-unstable-0.3.10}/async_lambda/env.py +0 -0
  17. {async-lambda-unstable-0.3.8 → async-lambda-unstable-0.3.10}/async_lambda/models/__init__.py +0 -0
  18. {async-lambda-unstable-0.3.8 → async-lambda-unstable-0.3.10}/async_lambda/models/events/__init__.py +0 -0
  19. {async-lambda-unstable-0.3.8 → async-lambda-unstable-0.3.10}/async_lambda/models/events/base_event.py +0 -0
  20. {async-lambda-unstable-0.3.8 → async-lambda-unstable-0.3.10}/async_lambda/models/events/managed_sqs_event.py +0 -0
  21. {async-lambda-unstable-0.3.8 → async-lambda-unstable-0.3.10}/async_lambda/models/events/scheduled_event.py +0 -0
  22. {async-lambda-unstable-0.3.8 → async-lambda-unstable-0.3.10}/async_lambda/models/events/unmanaged_sqs_event.py +0 -0
  23. {async-lambda-unstable-0.3.8 → async-lambda-unstable-0.3.10}/async_lambda/models/mock/mock_context.py +0 -0
  24. {async-lambda-unstable-0.3.8 → async-lambda-unstable-0.3.10}/async_lambda/models/mock/mock_event.py +0 -0
  25. {async-lambda-unstable-0.3.8 → async-lambda-unstable-0.3.10}/async_lambda/py.typed +0 -0
  26. {async-lambda-unstable-0.3.8 → async-lambda-unstable-0.3.10}/async_lambda/util.py +0 -0
  27. {async-lambda-unstable-0.3.8 → async-lambda-unstable-0.3.10}/async_lambda_unstable.egg-info/dependency_links.txt +0 -0
  28. {async-lambda-unstable-0.3.8 → async-lambda-unstable-0.3.10}/async_lambda_unstable.egg-info/entry_points.txt +0 -0
  29. {async-lambda-unstable-0.3.8 → async-lambda-unstable-0.3.10}/async_lambda_unstable.egg-info/requires.txt +0 -0
  30. {async-lambda-unstable-0.3.8 → async-lambda-unstable-0.3.10}/async_lambda_unstable.egg-info/top_level.txt +0 -0
  31. {async-lambda-unstable-0.3.8 → async-lambda-unstable-0.3.10}/pyproject.toml +0 -0
  32. {async-lambda-unstable-0.3.8 → async-lambda-unstable-0.3.10}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: async-lambda-unstable
3
- Version: 0.3.8
3
+ Version: 0.3.10
4
4
  Summary: A framework for creating AWS Lambda Async Workflows. - Unstable Branch
5
5
  Author-email: "Nuclei, Inc" <engineering@nuclei.ai>
6
6
  Description-Content-Type: text/markdown
@@ -5,9 +5,11 @@ from .defer import Defer as Defer
5
5
  from .env import disable_force_sync_mode as disable_force_sync_mode
6
6
  from .env import enable_force_sync_mode as enable_force_sync_mode
7
7
  from .env import is_build_mode as is_build_mode
8
+ from .models.case_insensitive_dict import CaseInsensitiveDict as CaseInsensitiveDict
8
9
  from .models.events.api_event import APIEvent as APIEvent
10
+ from .models.events.dynamodb_event import DynamoDBEvent as DynamoDBEvent
9
11
  from .models.events.managed_sqs_event import ManagedSQSEvent as ManagedSQSEvent
10
12
  from .models.events.scheduled_event import ScheduledEvent as ScheduledEvent
11
13
  from .models.events.unmanaged_sqs_event import UnmanagedSQSEvent as UnmanagedSQSEvent
12
14
 
13
- __version__ = "0.3.8"
15
+ __version__ = "0.3.10"
@@ -11,6 +11,7 @@ from . import env
11
11
  from .build_config import get_build_config_for_stage
12
12
  from .client import get_s3_client, get_sqs_client
13
13
  from .models.events.api_event import APIEvent
14
+ from .models.events.dynamodb_event import DynamoDBEvent
14
15
  from .models.events.managed_sqs_event import ManagedSQSEvent
15
16
  from .models.events.scheduled_event import ScheduledEvent
16
17
  from .models.events.unmanaged_sqs_event import UnmanagedSQSEvent
@@ -238,6 +239,8 @@ class AsyncLambdaController:
238
239
  _event = ScheduledEvent(*args)
239
240
  elif task.trigger_type == TaskTriggerType.API_EVENT:
240
241
  _event = APIEvent(*args)
242
+ elif task.trigger_type == TaskTriggerType.DYNAMODB_EVENT:
243
+ _event = DynamoDBEvent(*args)
241
244
  else:
242
245
  raise NotImplementedError(
243
246
  f"Trigger type of {task.trigger_type} is not supported."
@@ -525,3 +528,32 @@ class AsyncLambdaController:
525
528
  return inner
526
529
 
527
530
  return _task
531
+
532
+ def dynamodb_task(self, task_id: str, *, stream_arn: str, batch_size: int):
533
+ """
534
+ Decorate a function to register it as a DynamoDB task.
535
+ These tasks will be triggered by the given DynamoDB stream.
536
+ """
537
+ logger.debug(
538
+ f"Registered dynamodb task '{task_id}' with stream_arn '{stream_arn}' and batch_size '{batch_size}' with the controller."
539
+ )
540
+
541
+ def _task(func: Callable[[DynamoDBEvent], Any]):
542
+ @functools.wraps(func)
543
+ def inner(*args, **kwargs):
544
+ self.set_current_task_id(task_id)
545
+ return func(*args, **kwargs)
546
+
547
+ self.add_task(
548
+ AsyncLambdaTask(
549
+ controller=self,
550
+ executable=inner,
551
+ task_id=task_id,
552
+ trigger_type=TaskTriggerType.DYNAMODB_EVENT,
553
+ trigger_config={"stream_arn": stream_arn, "batch_size": batch_size},
554
+ )
555
+ )
556
+
557
+ return inner
558
+
559
+ return _task
@@ -0,0 +1,82 @@
1
+ import collections.abc
2
+ from collections import OrderedDict
3
+ from typing import Tuple, TypeVar
4
+
5
+ T = TypeVar("T", bound=str)
6
+ V = TypeVar("V")
7
+
8
+
9
+ class CaseInsensitiveDict(collections.abc.MutableMapping[T, V]):
10
+ """
11
+ A case-insensitive ``dict``-like object.
12
+
13
+ Implements all methods and operations of
14
+ ``MutableMapping`` as well as dict's ``copy``. Also
15
+ provides ``lower_items``.
16
+
17
+ All keys are expected to be strings. The structure remembers the
18
+ case of the last key to be set, and ``iter(instance)``,
19
+ ``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()``
20
+ will contain case-sensitive keys. However, querying and contains
21
+ testing is case insensitive::
22
+
23
+ cid = CaseInsensitiveDict()
24
+ cid['Accept'] = 'application/json'
25
+ cid['aCCEPT'] == 'application/json' # True
26
+ list(cid) == ['Accept'] # True
27
+
28
+ For example, ``headers['content-encoding']`` will return the
29
+ value of a ``'Content-Encoding'`` response header, regardless
30
+ of how the header name was originally stored.
31
+
32
+ If the constructor, ``.update``, or equality comparison
33
+ operations are given keys that have equal ``.lower()``s, the
34
+ behavior is undefined.
35
+
36
+ This was vendored from requests
37
+ https://github.com/kennethreitz/requests/blob/v2.25.1/requests/structures.py
38
+
39
+ Generic typing added by me.
40
+ """
41
+
42
+ def __init__(self, data=None, **kwargs):
43
+ self._store: OrderedDict[T, Tuple[T, V]] = OrderedDict()
44
+ if data is None:
45
+ data = {}
46
+ self.update(data, **kwargs)
47
+
48
+ def __setitem__(self, key, value):
49
+ # Use the lowercased key for lookups, but store the actual
50
+ # key alongside the value.
51
+ self._store[key.lower()] = (key, value)
52
+
53
+ def __getitem__(self, key):
54
+ return self._store[key.lower()][1]
55
+
56
+ def __delitem__(self, key):
57
+ del self._store[key.lower()]
58
+
59
+ def __iter__(self):
60
+ return (casedkey for casedkey, mappedvalue in self._store.values())
61
+
62
+ def __len__(self):
63
+ return len(self._store)
64
+
65
+ def lower_items(self):
66
+ """Like iteritems(), but with all lowercase keys."""
67
+ return ((lowerkey, keyval[1]) for (lowerkey, keyval) in self._store.items())
68
+
69
+ def __eq__(self, other):
70
+ if isinstance(other, collections.abc.Mapping):
71
+ other = CaseInsensitiveDict(other)
72
+ else:
73
+ return NotImplemented
74
+ # Compare insensitively
75
+ return dict(self.lower_items()) == dict(other.lower_items())
76
+
77
+ # Copy is required
78
+ def copy(self):
79
+ return CaseInsensitiveDict(self._store.values())
80
+
81
+ def __repr__(self):
82
+ return str(dict(self.items()))
@@ -1,13 +1,14 @@
1
1
  from typing import Any, Dict, List, Optional
2
2
 
3
+ from ..case_insensitive_dict import CaseInsensitiveDict
3
4
  from .base_event import BaseEvent
4
5
 
5
6
 
6
7
  class APIEvent(BaseEvent):
7
8
  path: str
8
9
  method: str
9
- headers: Dict[str, str]
10
- multi_value_headers: Dict[str, List[str]]
10
+ headers: CaseInsensitiveDict[str, str]
11
+ multi_value_headers: CaseInsensitiveDict[str, List[str]]
11
12
  querystring_params: Dict[str, str]
12
13
  multi_value_querystring_params: Dict[str, List[str]]
13
14
  path_parameters: Dict[str, str]
@@ -18,8 +19,8 @@ class APIEvent(BaseEvent):
18
19
  def _hydrate_event(self):
19
20
  self.path = self._event["path"]
20
21
  self.method = self._event["httpMethod"]
21
- self.headers = self._event["headers"]
22
- self.multi_value_headers = self._event["multiValueHeaders"]
22
+ self.headers = CaseInsensitiveDict(self._event["headers"])
23
+ self.multi_value_headers = CaseInsensitiveDict(self._event["multiValueHeaders"])
23
24
  self.querystring_params = self._event.get("queryStringParameters", dict())
24
25
  self.multi_value_querystring_params = self._event.get(
25
26
  "multiValueQueryStringParameters", dict()
@@ -0,0 +1,19 @@
1
+ from typing import Optional
2
+
3
+ from .base_event import BaseEvent
4
+
5
+
6
+ class DynamoDBRecord(BaseEvent):
7
+ new_image: Optional[dict]
8
+ old_image: Optional[dict]
9
+
10
+ def _hydrate_event(self):
11
+ dynamodb_dict = self._event["dynamodb"]
12
+ self.new_image = dynamodb_dict.get("NewImage")
13
+ self.old_image = dynamodb_dict.get("OldImage")
14
+
15
+
16
+ class DynamoDBEvent(BaseEvent):
17
+ def __iter__(self):
18
+ for record in self._event["Records"]:
19
+ yield DynamoDBRecord(record, self._context, self._task)
@@ -13,6 +13,7 @@ from ..config import config
13
13
  if TYPE_CHECKING:
14
14
  from ..controller import AsyncLambdaController # pragma: not covered
15
15
 
16
+ from .events.dynamodb_event import DynamoDBEvent
16
17
  from .events.managed_sqs_event import ManagedSQSEvent
17
18
  from .events.scheduled_event import ScheduledEvent
18
19
  from .events.unmanaged_sqs_event import UnmanagedSQSEvent
@@ -23,10 +24,12 @@ class TaskTriggerType(Enum):
23
24
  UNMANAGED_SQS = 2
24
25
  SCHEDULED_EVENT = 3
25
26
  API_EVENT = 4
27
+ DYNAMODB_EVENT = 5
26
28
 
27
29
 
28
30
  EventType = TypeVar(
29
- "EventType", bound=Union[ManagedSQSEvent, ScheduledEvent, UnmanagedSQSEvent]
31
+ "EventType",
32
+ bound=Union[ManagedSQSEvent, ScheduledEvent, UnmanagedSQSEvent, DynamoDBEvent],
30
33
  )
31
34
 
32
35
 
@@ -262,6 +265,18 @@ class AsyncLambdaTask(Generic[EventType]):
262
265
  },
263
266
  }
264
267
  }
268
+ elif self.trigger_type == TaskTriggerType.DYNAMODB_EVENT:
269
+ return {
270
+ "DynamoDBEvent": {
271
+ "Type": "DynamoDB",
272
+ "Properties": {
273
+ "Stream": self.trigger_config["stream_arn"],
274
+ "StartingPosition": "TRIM_HORIZON",
275
+ "BatchSize": self.trigger_config["batch_size"],
276
+ "Enabled": True,
277
+ },
278
+ }
279
+ }
265
280
  raise NotImplementedError()
266
281
 
267
282
  def get_policy_sqs_resources(self) -> List[dict]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: async-lambda-unstable
3
- Version: 0.3.8
3
+ Version: 0.3.10
4
4
  Summary: A framework for creating AWS Lambda Async Workflows. - Unstable Branch
5
5
  Author-email: "Nuclei, Inc" <engineering@nuclei.ai>
6
6
  Description-Content-Type: text/markdown
@@ -11,10 +11,12 @@ async_lambda/env.py
11
11
  async_lambda/py.typed
12
12
  async_lambda/util.py
13
13
  async_lambda/models/__init__.py
14
+ async_lambda/models/case_insensitive_dict.py
14
15
  async_lambda/models/task.py
15
16
  async_lambda/models/events/__init__.py
16
17
  async_lambda/models/events/api_event.py
17
18
  async_lambda/models/events/base_event.py
19
+ async_lambda/models/events/dynamodb_event.py
18
20
  async_lambda/models/events/managed_sqs_event.py
19
21
  async_lambda/models/events/scheduled_event.py
20
22
  async_lambda/models/events/unmanaged_sqs_event.py