async-lambda-unstable 0.4.1__tar.gz → 0.4.3__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 (33) hide show
  1. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/PKG-INFO +44 -11
  2. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/README.md +43 -10
  3. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/async_lambda/__init__.py +1 -1
  4. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/async_lambda/controller.py +17 -19
  5. async-lambda-unstable-0.4.3/async_lambda/middleware.py +30 -0
  6. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/async_lambda/models/task.py +10 -6
  7. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/async_lambda_unstable.egg-info/PKG-INFO +44 -11
  8. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/async_lambda_unstable.egg-info/SOURCES.txt +1 -0
  9. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/async_lambda/build_config.py +0 -0
  10. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/async_lambda/cli.py +0 -0
  11. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/async_lambda/client.py +0 -0
  12. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/async_lambda/config.py +0 -0
  13. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/async_lambda/defer.py +0 -0
  14. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/async_lambda/env.py +0 -0
  15. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/async_lambda/models/__init__.py +0 -0
  16. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/async_lambda/models/case_insensitive_dict.py +0 -0
  17. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/async_lambda/models/events/__init__.py +0 -0
  18. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/async_lambda/models/events/api_event.py +0 -0
  19. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/async_lambda/models/events/base_event.py +0 -0
  20. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/async_lambda/models/events/dynamodb_event.py +0 -0
  21. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/async_lambda/models/events/managed_sqs_event.py +0 -0
  22. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/async_lambda/models/events/scheduled_event.py +0 -0
  23. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/async_lambda/models/events/unmanaged_sqs_event.py +0 -0
  24. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/async_lambda/models/mock/mock_context.py +0 -0
  25. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/async_lambda/models/mock/mock_event.py +0 -0
  26. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/async_lambda/py.typed +0 -0
  27. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/async_lambda/util.py +0 -0
  28. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/async_lambda_unstable.egg-info/dependency_links.txt +0 -0
  29. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/async_lambda_unstable.egg-info/entry_points.txt +0 -0
  30. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/async_lambda_unstable.egg-info/requires.txt +0 -0
  31. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/async_lambda_unstable.egg-info/top_level.txt +0 -0
  32. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/pyproject.toml +0 -0
  33. {async-lambda-unstable-0.4.1 → async-lambda-unstable-0.4.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: async-lambda-unstable
3
- Version: 0.4.1
3
+ Version: 0.4.3
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
@@ -191,24 +191,57 @@ def api_task(event: APIEvent):
191
191
 
192
192
  # Middleware
193
193
 
194
- Middleware functions can be registered with controllers which will execute before the task code is run.
195
- These functions can be configured to trigger on specific types of tasks and can modify the `event`.
194
+ Middleware functions can be registered with controllers which will wrap the execution of tasks.
195
+ These functions can be configured to trigger on specific types of tasks and can trigger
196
+ side effects and modify the `event` or `response` objects.
196
197
 
197
- Middleware functions must have the signature `Callable[[BaseEvent], BaseEvent]`.
198
- Middleware functions are run in the order which they were registered and parent controller middleware will be run first.
199
- Middleware functions which are registered more than once will only be run once.
198
+ Middleware functions must have the signature `Callable[[BaseEvent, Callable[[BaseEvent], T]], T]`.
199
+ The first argument is the `event`, and the second argument (`call_next`) is a function which will propagate the
200
+ calls down the middleware/task stack. The `call_next` function must be called, and its result in most cases be returned.
201
+ If this is not done then tasks will not run as expected.
202
+
203
+ **Extreme care should be taken with middleware as a simple mistake can have catastrophic effects.**
204
+
205
+ - Middleware functions are run in the order which they were registered and parent controller middleware will be run first.
206
+
207
+ - Middleware functions which are registered more than once will only be run once.
200
208
 
201
209
  Registration can be done when the `AsyncLambdaController` is initialized with the parameter `middleware` or by using the `add_middleware` method.
202
210
 
211
+ Middleware functions have three sections:
212
+
213
+ 1. Pre task
214
+ 2. Task execution
215
+ 3. Post task
216
+
217
+ ```python
218
+ def async_lambda_middleware(event: BaseEvent, call_next):
219
+ # pre task
220
+ result = call_next(event) # task execution
221
+ # post task
222
+ return result
223
+ ```
224
+
225
+ If there are multiple middleware functions then `call_next` will actually be calling the next middleware function in the stack.
226
+
227
+ For example if there is middleware functions `A` and `B` registered in that order.
228
+ Then the execution order would go:
229
+
230
+ `A(Pre)` -> `B(Pre)` -> `Task` -> `B(Post)` -> `A(Post)`
231
+
203
232
  EX:
204
233
 
205
234
  ```python
206
- def async_task_only_middleware(event: ManagedSQSEvent):
235
+ def async_task_only_middleware(event: ManagedSQSEvent, call_next):
207
236
  print(f"Invocation Payload: {event}")
208
- return event
209
-
210
- def all_task_types_middleware(event: BaseEvent):
211
- print(f"This task is of the type {type(event)}")
237
+ result = call_next(event)
238
+ print(f"Invocation Result: {result}")
239
+ return result
240
+
241
+ def all_task_types_middleware(event: BaseEvent, call_next):
242
+ print(f"This event is of the type {type(event)}")
243
+ result = call_next(event)
244
+ print(f"The result is of the type {type(result)}")
212
245
  return event
213
246
 
214
247
  controller = AsyncLambdaController(middleware=[([BaseEvent], all_task_types_middleware)])
@@ -183,24 +183,57 @@ def api_task(event: APIEvent):
183
183
 
184
184
  # Middleware
185
185
 
186
- Middleware functions can be registered with controllers which will execute before the task code is run.
187
- These functions can be configured to trigger on specific types of tasks and can modify the `event`.
186
+ Middleware functions can be registered with controllers which will wrap the execution of tasks.
187
+ These functions can be configured to trigger on specific types of tasks and can trigger
188
+ side effects and modify the `event` or `response` objects.
188
189
 
189
- Middleware functions must have the signature `Callable[[BaseEvent], BaseEvent]`.
190
- Middleware functions are run in the order which they were registered and parent controller middleware will be run first.
191
- Middleware functions which are registered more than once will only be run once.
190
+ Middleware functions must have the signature `Callable[[BaseEvent, Callable[[BaseEvent], T]], T]`.
191
+ The first argument is the `event`, and the second argument (`call_next`) is a function which will propagate the
192
+ calls down the middleware/task stack. The `call_next` function must be called, and its result in most cases be returned.
193
+ If this is not done then tasks will not run as expected.
194
+
195
+ **Extreme care should be taken with middleware as a simple mistake can have catastrophic effects.**
196
+
197
+ - Middleware functions are run in the order which they were registered and parent controller middleware will be run first.
198
+
199
+ - Middleware functions which are registered more than once will only be run once.
192
200
 
193
201
  Registration can be done when the `AsyncLambdaController` is initialized with the parameter `middleware` or by using the `add_middleware` method.
194
202
 
203
+ Middleware functions have three sections:
204
+
205
+ 1. Pre task
206
+ 2. Task execution
207
+ 3. Post task
208
+
209
+ ```python
210
+ def async_lambda_middleware(event: BaseEvent, call_next):
211
+ # pre task
212
+ result = call_next(event) # task execution
213
+ # post task
214
+ return result
215
+ ```
216
+
217
+ If there are multiple middleware functions then `call_next` will actually be calling the next middleware function in the stack.
218
+
219
+ For example if there is middleware functions `A` and `B` registered in that order.
220
+ Then the execution order would go:
221
+
222
+ `A(Pre)` -> `B(Pre)` -> `Task` -> `B(Post)` -> `A(Post)`
223
+
195
224
  EX:
196
225
 
197
226
  ```python
198
- def async_task_only_middleware(event: ManagedSQSEvent):
227
+ def async_task_only_middleware(event: ManagedSQSEvent, call_next):
199
228
  print(f"Invocation Payload: {event}")
200
- return event
201
-
202
- def all_task_types_middleware(event: BaseEvent):
203
- print(f"This task is of the type {type(event)}")
229
+ result = call_next(event)
230
+ print(f"Invocation Result: {result}")
231
+ return result
232
+
233
+ def all_task_types_middleware(event: BaseEvent, call_next):
234
+ print(f"This event is of the type {type(event)}")
235
+ result = call_next(event)
236
+ print(f"The result is of the type {type(result)}")
204
237
  return event
205
238
 
206
239
  controller = AsyncLambdaController(middleware=[([BaseEvent], all_task_types_middleware)])
@@ -13,4 +13,4 @@ from .models.events.managed_sqs_event import ManagedSQSEvent as ManagedSQSEvent
13
13
  from .models.events.scheduled_event import ScheduledEvent as ScheduledEvent
14
14
  from .models.events.unmanaged_sqs_event import UnmanagedSQSEvent as UnmanagedSQSEvent
15
15
 
16
- __version__ = "0.4.1"
16
+ __version__ = "0.4.3"
@@ -4,12 +4,13 @@ import logging
4
4
  import re
5
5
  import time
6
6
  from datetime import datetime, timezone
7
- from typing import Any, Callable, Dict, List, Optional, Tuple, Type, TypeVar
7
+ from typing import Any, Callable, Dict, List, Optional, Tuple, Type
8
8
  from uuid import uuid4
9
9
 
10
10
  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
+ from .middleware import MET, RT, MiddlewareFunction, MiddlewareRegistration
13
14
  from .models.events.api_event import APIEvent
14
15
  from .models.events.base_event import BaseEvent
15
16
  from .models.events.dynamodb_event import DynamoDBEvent
@@ -23,11 +24,6 @@ from .util import make_cf_tags
23
24
 
24
25
  logger = logging.getLogger(__name__)
25
26
 
26
- MET = TypeVar("MET", bound=BaseEvent)
27
-
28
- MiddlewareFunction = Callable[[MET], MET]
29
- MiddlewareRegistration = Tuple[List[Type[MET]], MiddlewareFunction[MET]]
30
-
31
27
 
32
28
  class AsyncLambdaController:
33
29
  is_sub: bool
@@ -39,6 +35,7 @@ class AsyncLambdaController:
39
35
  current_invocation_id: Optional[str] = None
40
36
  parent_controller: Optional["AsyncLambdaController"] = None
41
37
  middleware: List[MiddlewareRegistration]
38
+
42
39
  dlq_task_id: Optional[str] = None
43
40
 
44
41
  def __init__(
@@ -47,32 +44,33 @@ class AsyncLambdaController:
47
44
  lane_count: Optional[int] = None,
48
45
  propagate_lane_assignment: Optional[bool] = None,
49
46
  middleware: Optional[List[MiddlewareRegistration]] = None,
47
+ exception_handler: Optional[Callable[[Exception], None]] = None,
50
48
  ):
51
49
  self.tasks = dict()
52
50
  self.is_sub = is_sub
53
51
  self.lane_count = lane_count
54
52
  self.propagate_lane_assignment = propagate_lane_assignment
55
53
  self.middleware = middleware or list()
54
+ self.exception_handler = exception_handler
56
55
 
57
56
  def add_middleware(
58
- self, event_types: List[Type[BaseEvent]], func: MiddlewareFunction[MET]
57
+ self, event_types: List[Type[BaseEvent]], func: MiddlewareFunction[MET, RT]
59
58
  ):
60
59
  self.middleware.append((event_types, func))
61
60
 
62
- def execute_middleware(
63
- self, event: MET, _ran_fns: Optional[List[Callable]] = None
64
- ) -> Tuple[MET, List[Callable]]:
65
- if _ran_fns is None:
66
- _ran_fns = list()
61
+ def get_middleware_for_event(self, event: MET) -> List[MiddlewareFunction[MET, RT]]:
67
62
  if self.parent_controller is not None:
68
- event, _ran_fns = self.parent_controller.execute_middleware(event, _ran_fns)
63
+ _middleware_functions = self.parent_controller.get_middleware_for_event(
64
+ event
65
+ )
66
+ else:
67
+ _middleware_functions = list()
68
+
69
69
  for event_types, func in self.middleware:
70
- if func in _ran_fns:
71
- continue
72
70
  if any(isinstance(event, event_type) for event_type in event_types):
73
- event = func(event)
74
- _ran_fns.append(func)
75
- return event, _ran_fns
71
+ _middleware_functions.append(func)
72
+
73
+ return _middleware_functions
76
74
 
77
75
  def add_task(self, task: AsyncLambdaTask):
78
76
  """
@@ -411,7 +409,7 @@ class AsyncLambdaController:
411
409
  """
412
410
  Invoke an Async-Lambda task.
413
411
  """
414
- self._async_invoke(
412
+ return self._async_invoke(
415
413
  destination_task_id=task_id,
416
414
  payload=payload,
417
415
  delay=delay,
@@ -0,0 +1,30 @@
1
+ from typing import Callable, Generic, List, Tuple, Type, TypeVar
2
+
3
+ from .models.events.base_event import BaseEvent
4
+
5
+ MET = TypeVar("MET", bound=BaseEvent)
6
+ RT = TypeVar("RT")
7
+
8
+ MiddlewareFunction = Callable[[MET, Callable[[MET], RT]], RT]
9
+ MiddlewareRegistration = Tuple[List[Type[MET]], MiddlewareFunction[MET, RT]]
10
+
11
+
12
+ class MiddlewareStackExecutor(Generic[MET, RT]):
13
+ def __init__(
14
+ self,
15
+ middleware: List[MiddlewareFunction],
16
+ final: Callable[[MET], RT],
17
+ ):
18
+ self.middleware = middleware.copy()
19
+ self.final = final
20
+ self._ran_fns = list()
21
+
22
+ def call_next(self, event: MET) -> RT:
23
+ while True:
24
+ if len(self.middleware) == 0:
25
+ return self.final(event)
26
+ next_fn = self.middleware.pop(0)
27
+ if next_fn in self._ran_fns:
28
+ continue
29
+ self._ran_fns.append(next_fn)
30
+ return next_fn(event, self.call_next)
@@ -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 ..middleware import RT, MiddlewareStackExecutor
16
17
  from .events.dynamodb_event import DynamoDBEvent
17
18
  from .events.managed_sqs_event import ManagedSQSEvent
18
19
  from .events.scheduled_event import ScheduledEvent
@@ -33,7 +34,7 @@ EventType = TypeVar(
33
34
  )
34
35
 
35
36
 
36
- class AsyncLambdaTask(Generic[EventType]):
37
+ class AsyncLambdaTask(Generic[EventType, RT]):
37
38
  controller: "AsyncLambdaController"
38
39
  task_id: str
39
40
  trigger_type: TaskTriggerType
@@ -46,12 +47,12 @@ class AsyncLambdaTask(Generic[EventType]):
46
47
  init_tasks: List[Union[Callable[[str], Any], Callable[[], Any]]]
47
48
  _has_run_init_tasks: bool
48
49
 
49
- executable: Callable[[EventType], Any]
50
+ executable: Callable[[EventType], RT]
50
51
 
51
52
  def __init__(
52
53
  self,
53
54
  controller: "AsyncLambdaController",
54
- executable: Callable[[EventType], Any],
55
+ executable: Callable[[EventType], RT],
55
56
  task_id: str,
56
57
  trigger_type: TaskTriggerType,
57
58
  trigger_config: Optional[dict] = None,
@@ -481,10 +482,13 @@ class AsyncLambdaTask(Generic[EventType]):
481
482
  return self.controller.get_task(self.trigger_config["dlq_task_id"])
482
483
  return self.controller.get_dlq_task()
483
484
 
484
- def execute(self, event: EventType) -> Any:
485
+ def execute(self, event: EventType) -> RT:
485
486
  """
486
487
  Executes the tasks function
487
488
  """
488
489
  self._run_init_tasks()
489
- self.controller.execute_middleware(event)
490
- return self.executable(event)
490
+ middleware = self.controller.get_middleware_for_event(event)
491
+ middleware_stack_executor = MiddlewareStackExecutor[EventType, RT](
492
+ middleware=middleware, final=self.executable
493
+ )
494
+ return middleware_stack_executor.call_next(event)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: async-lambda-unstable
3
- Version: 0.4.1
3
+ Version: 0.4.3
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
@@ -191,24 +191,57 @@ def api_task(event: APIEvent):
191
191
 
192
192
  # Middleware
193
193
 
194
- Middleware functions can be registered with controllers which will execute before the task code is run.
195
- These functions can be configured to trigger on specific types of tasks and can modify the `event`.
194
+ Middleware functions can be registered with controllers which will wrap the execution of tasks.
195
+ These functions can be configured to trigger on specific types of tasks and can trigger
196
+ side effects and modify the `event` or `response` objects.
196
197
 
197
- Middleware functions must have the signature `Callable[[BaseEvent], BaseEvent]`.
198
- Middleware functions are run in the order which they were registered and parent controller middleware will be run first.
199
- Middleware functions which are registered more than once will only be run once.
198
+ Middleware functions must have the signature `Callable[[BaseEvent, Callable[[BaseEvent], T]], T]`.
199
+ The first argument is the `event`, and the second argument (`call_next`) is a function which will propagate the
200
+ calls down the middleware/task stack. The `call_next` function must be called, and its result in most cases be returned.
201
+ If this is not done then tasks will not run as expected.
202
+
203
+ **Extreme care should be taken with middleware as a simple mistake can have catastrophic effects.**
204
+
205
+ - Middleware functions are run in the order which they were registered and parent controller middleware will be run first.
206
+
207
+ - Middleware functions which are registered more than once will only be run once.
200
208
 
201
209
  Registration can be done when the `AsyncLambdaController` is initialized with the parameter `middleware` or by using the `add_middleware` method.
202
210
 
211
+ Middleware functions have three sections:
212
+
213
+ 1. Pre task
214
+ 2. Task execution
215
+ 3. Post task
216
+
217
+ ```python
218
+ def async_lambda_middleware(event: BaseEvent, call_next):
219
+ # pre task
220
+ result = call_next(event) # task execution
221
+ # post task
222
+ return result
223
+ ```
224
+
225
+ If there are multiple middleware functions then `call_next` will actually be calling the next middleware function in the stack.
226
+
227
+ For example if there is middleware functions `A` and `B` registered in that order.
228
+ Then the execution order would go:
229
+
230
+ `A(Pre)` -> `B(Pre)` -> `Task` -> `B(Post)` -> `A(Post)`
231
+
203
232
  EX:
204
233
 
205
234
  ```python
206
- def async_task_only_middleware(event: ManagedSQSEvent):
235
+ def async_task_only_middleware(event: ManagedSQSEvent, call_next):
207
236
  print(f"Invocation Payload: {event}")
208
- return event
209
-
210
- def all_task_types_middleware(event: BaseEvent):
211
- print(f"This task is of the type {type(event)}")
237
+ result = call_next(event)
238
+ print(f"Invocation Result: {result}")
239
+ return result
240
+
241
+ def all_task_types_middleware(event: BaseEvent, call_next):
242
+ print(f"This event is of the type {type(event)}")
243
+ result = call_next(event)
244
+ print(f"The result is of the type {type(result)}")
212
245
  return event
213
246
 
214
247
  controller = AsyncLambdaController(middleware=[([BaseEvent], all_task_types_middleware)])
@@ -8,6 +8,7 @@ async_lambda/config.py
8
8
  async_lambda/controller.py
9
9
  async_lambda/defer.py
10
10
  async_lambda/env.py
11
+ async_lambda/middleware.py
11
12
  async_lambda/py.typed
12
13
  async_lambda/util.py
13
14
  async_lambda/models/__init__.py