async-lambda-unstable 0.5.7__tar.gz → 0.6.0__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 (37) hide show
  1. async_lambda_unstable-0.6.0/.gitignore +164 -0
  2. {async-lambda-unstable-0.5.7 → async_lambda_unstable-0.6.0}/PKG-INFO +7 -3
  3. {async-lambda-unstable-0.5.7 → async_lambda_unstable-0.6.0}/async_lambda/__init__.py +1 -1
  4. {async-lambda-unstable-0.5.7 → async_lambda_unstable-0.6.0}/async_lambda/build_config.py +5 -0
  5. {async-lambda-unstable-0.5.7 → async_lambda_unstable-0.6.0}/async_lambda/controller.py +59 -2
  6. {async-lambda-unstable-0.5.7 → async_lambda_unstable-0.6.0}/pyproject.toml +24 -5
  7. async-lambda-unstable-0.5.7/async_lambda_unstable.egg-info/PKG-INFO +0 -438
  8. async-lambda-unstable-0.5.7/async_lambda_unstable.egg-info/SOURCES.txt +0 -34
  9. async-lambda-unstable-0.5.7/async_lambda_unstable.egg-info/dependency_links.txt +0 -1
  10. async-lambda-unstable-0.5.7/async_lambda_unstable.egg-info/entry_points.txt +0 -2
  11. async-lambda-unstable-0.5.7/async_lambda_unstable.egg-info/requires.txt +0 -6
  12. async-lambda-unstable-0.5.7/async_lambda_unstable.egg-info/top_level.txt +0 -1
  13. async-lambda-unstable-0.5.7/setup.cfg +0 -4
  14. {async-lambda-unstable-0.5.7 → async_lambda_unstable-0.6.0}/README.md +0 -0
  15. {async-lambda-unstable-0.5.7 → async_lambda_unstable-0.6.0}/async_lambda/cli.py +0 -0
  16. {async-lambda-unstable-0.5.7 → async_lambda_unstable-0.6.0}/async_lambda/client.py +0 -0
  17. {async-lambda-unstable-0.5.7 → async_lambda_unstable-0.6.0}/async_lambda/config.py +0 -0
  18. {async-lambda-unstable-0.5.7 → async_lambda_unstable-0.6.0}/async_lambda/defer.py +0 -0
  19. {async-lambda-unstable-0.5.7 → async_lambda_unstable-0.6.0}/async_lambda/env.py +0 -0
  20. {async-lambda-unstable-0.5.7 → async_lambda_unstable-0.6.0}/async_lambda/middleware.py +0 -0
  21. {async-lambda-unstable-0.5.7 → async_lambda_unstable-0.6.0}/async_lambda/models/__init__.py +0 -0
  22. {async-lambda-unstable-0.5.7 → async_lambda_unstable-0.6.0}/async_lambda/models/api_response.py +0 -0
  23. {async-lambda-unstable-0.5.7 → async_lambda_unstable-0.6.0}/async_lambda/models/case_insensitive_dict.py +0 -0
  24. {async-lambda-unstable-0.5.7 → async_lambda_unstable-0.6.0}/async_lambda/models/events/__init__.py +0 -0
  25. {async-lambda-unstable-0.5.7 → async_lambda_unstable-0.6.0}/async_lambda/models/events/api_event.py +0 -0
  26. {async-lambda-unstable-0.5.7 → async_lambda_unstable-0.6.0}/async_lambda/models/events/base_event.py +0 -0
  27. {async-lambda-unstable-0.5.7 → async_lambda_unstable-0.6.0}/async_lambda/models/events/dynamodb_event.py +0 -0
  28. {async-lambda-unstable-0.5.7 → async_lambda_unstable-0.6.0}/async_lambda/models/events/managed_sqs_batch_event.py +0 -0
  29. {async-lambda-unstable-0.5.7 → async_lambda_unstable-0.6.0}/async_lambda/models/events/managed_sqs_event.py +0 -0
  30. {async-lambda-unstable-0.5.7 → async_lambda_unstable-0.6.0}/async_lambda/models/events/scheduled_event.py +0 -0
  31. {async-lambda-unstable-0.5.7 → async_lambda_unstable-0.6.0}/async_lambda/models/events/unmanaged_sqs_event.py +0 -0
  32. {async-lambda-unstable-0.5.7 → async_lambda_unstable-0.6.0}/async_lambda/models/mock/mock_context.py +0 -0
  33. {async-lambda-unstable-0.5.7 → async_lambda_unstable-0.6.0}/async_lambda/models/mock/mock_event.py +0 -0
  34. {async-lambda-unstable-0.5.7 → async_lambda_unstable-0.6.0}/async_lambda/models/task.py +0 -0
  35. {async-lambda-unstable-0.5.7 → async_lambda_unstable-0.6.0}/async_lambda/payload_encoder.py +0 -0
  36. {async-lambda-unstable-0.5.7 → async_lambda_unstable-0.6.0}/async_lambda/py.typed +0 -0
  37. {async-lambda-unstable-0.5.7 → async_lambda_unstable-0.6.0}/async_lambda/util.py +0 -0
@@ -0,0 +1,164 @@
1
+ *samconfig.toml
2
+
3
+ # Byte-compiled / optimized / DLL files
4
+ __pycache__/
5
+ *.py[cod]
6
+ *$py.class
7
+
8
+ # C extensions
9
+ *.so
10
+
11
+ # Distribution / packaging
12
+ .Python
13
+ build/
14
+ develop-eggs/
15
+ dist/
16
+ downloads/
17
+ eggs/
18
+ .eggs/
19
+ lib/
20
+ lib64/
21
+ parts/
22
+ sdist/
23
+ var/
24
+ wheels/
25
+ share/python-wheels/
26
+ *.egg-info/
27
+ .installed.cfg
28
+ *.egg
29
+ MANIFEST
30
+
31
+ # PyInstaller
32
+ # Usually these files are written by a python script from a template
33
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
34
+ *.manifest
35
+ *.spec
36
+
37
+ # Installer logs
38
+ pip-log.txt
39
+ pip-delete-this-directory.txt
40
+
41
+ # Unit test / coverage reports
42
+ htmlcov/
43
+ .tox/
44
+ .nox/
45
+ .coverage
46
+ .coverage.*
47
+ .cache
48
+ nosetests.xml
49
+ coverage.xml
50
+ *.cover
51
+ *.py,cover
52
+ .hypothesis/
53
+ .pytest_cache/
54
+ cover/
55
+
56
+ # Translations
57
+ *.mo
58
+ *.pot
59
+
60
+ # Django stuff:
61
+ *.log
62
+ local_settings.py
63
+ db.sqlite3
64
+ db.sqlite3-journal
65
+
66
+ # Flask stuff:
67
+ instance/
68
+ .webassets-cache
69
+
70
+ # Scrapy stuff:
71
+ .scrapy
72
+
73
+ # Sphinx documentation
74
+ docs/_build/
75
+
76
+ # PyBuilder
77
+ .pybuilder/
78
+ target/
79
+
80
+ # Jupyter Notebook
81
+ .ipynb_checkpoints
82
+
83
+ # IPython
84
+ profile_default/
85
+ ipython_config.py
86
+
87
+ # pyenv
88
+ # For a library or package, you might want to ignore these files since the code is
89
+ # intended to run in multiple environments; otherwise, check them in:
90
+ # .python-version
91
+
92
+ # pipenv
93
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
94
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
95
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
96
+ # install all needed dependencies.
97
+ #Pipfile.lock
98
+
99
+ # poetry
100
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
101
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
102
+ # commonly ignored for libraries.
103
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
104
+ #poetry.lock
105
+
106
+ # pdm
107
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
108
+ #pdm.lock
109
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
110
+ # in version control.
111
+ # https://pdm.fming.dev/#use-with-ide
112
+ .pdm.toml
113
+
114
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
115
+ __pypackages__/
116
+
117
+ # Celery stuff
118
+ celerybeat-schedule
119
+ celerybeat.pid
120
+
121
+ # SageMath parsed files
122
+ *.sage.py
123
+
124
+ # Environments
125
+ .env
126
+ .venv
127
+ env/
128
+ venv/
129
+ ENV/
130
+ env.bak/
131
+ venv.bak/
132
+
133
+ # Spyder project settings
134
+ .spyderproject
135
+ .spyproject
136
+
137
+ # Rope project settings
138
+ .ropeproject
139
+
140
+ # mkdocs documentation
141
+ /site
142
+
143
+ # mypy
144
+ .mypy_cache/
145
+ .dmypy.json
146
+ dmypy.json
147
+
148
+ # Pyre type checker
149
+ .pyre/
150
+
151
+ # pytype static type analyzer
152
+ .pytype/
153
+
154
+ # Cython debug symbols
155
+ cython_debug/
156
+
157
+ # PyCharm
158
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
159
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
160
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
161
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
162
+ #.idea/
163
+ *.DS_Store
164
+ uv.lock
@@ -1,10 +1,14 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: async-lambda-unstable
3
- Version: 0.5.7
3
+ Version: 0.6.0
4
4
  Summary: A framework for creating AWS Lambda Async Workflows. - Unstable Branch
5
5
  Author-email: "Nuclei, Inc" <engineering@nuclei.ai>
6
- Description-Content-Type: text/markdown
6
+ Requires-Dist: click>=8.0.0
7
+ Requires-Dist: typing-extensions>=4.0.0
7
8
  Provides-Extra: local
9
+ Requires-Dist: boto3; extra == 'local'
10
+ Requires-Dist: flask<3.0.0,>=2.3.0; extra == 'local'
11
+ Description-Content-Type: text/markdown
8
12
 
9
13
  # async-lambda
10
14
 
@@ -21,4 +21,4 @@ from .models.events.managed_sqs_event import ManagedSQSEvent as ManagedSQSEvent
21
21
  from .models.events.scheduled_event import ScheduledEvent as ScheduledEvent
22
22
  from .models.events.unmanaged_sqs_event import UnmanagedSQSEvent as UnmanagedSQSEvent
23
23
 
24
- __version__ = "0.5.7"
24
+ __version__ = "0.6.0"
@@ -33,6 +33,7 @@ class AsyncLambdaBuildConfig:
33
33
  tls_version (Optional[str]): TLS version to use for the API.
34
34
  certificate_arn (Optional[str]): ARN of the SSL certificate for the domain.
35
35
  hosted_zone_id (Optional[str]): Hosted zone name for the Lambda/API.
36
+ auto_create_acm_certificate (bool): Whether or not to auto create a certificate in ACM for the domain name. Must be used in conjunction with hosted_zone_id and domain_name to be used.
36
37
 
37
38
  Methods:
38
39
  new(config: dict) -> AsyncLambdaBuildConfig:
@@ -55,6 +56,7 @@ class AsyncLambdaBuildConfig:
55
56
  tls_version: Optional[str] = None
56
57
  certificate_arn: Optional[str] = None
57
58
  hosted_zone_id: Optional[str] = None
59
+ auto_create_acm_certificate: Optional[bool] = None
58
60
 
59
61
  @classmethod
60
62
  def new(cls, config: dict) -> "AsyncLambdaBuildConfig":
@@ -94,6 +96,7 @@ class AsyncLambdaBuildConfig:
94
96
  tls_version=config.get("tls_version"),
95
97
  certificate_arn=config.get("certificate_arn"),
96
98
  hosted_zone_id=config.get("hosted_zone_id"),
99
+ auto_create_acm_certificate=config.get("auto_create_acm_certificate"),
97
100
  )
98
101
 
99
102
  def merge(self, other: "AsyncLambdaBuildConfig"):
@@ -113,6 +116,8 @@ class AsyncLambdaBuildConfig:
113
116
  self.certificate_arn = other.certificate_arn
114
117
  if other.hosted_zone_id is not None:
115
118
  self.hosted_zone_id = other.hosted_zone_id
119
+ if other.auto_create_acm_certificate is not None:
120
+ self.auto_create_acm_certificate = other.auto_create_acm_certificate
116
121
 
117
122
 
118
123
  def get_build_config_for_stage(
@@ -137,6 +137,7 @@ class AsyncLambdaController:
137
137
  current_task_id: Optional[str] = None
138
138
  current_lane: Optional[int] = None
139
139
  current_invocation_id: Optional[str] = None
140
+ _current_event_context: Optional[Any] = None
140
141
  parent_controller: Optional["AsyncLambdaController"] = None
141
142
  middleware: List[MiddlewareRegistration]
142
143
  delete_s3_payloads: bool = False
@@ -398,9 +399,10 @@ class AsyncLambdaController:
398
399
  if len(build_config.method_settings) > 0:
399
400
  properties["MethodSettings"] = build_config.method_settings
400
401
  if build_config.domain_name is not None:
401
- properties["Domain"] = {
402
+ domain_dict: dict = {
402
403
  "DomainName": build_config.domain_name,
403
404
  }
405
+ properties["Domain"] = domain_dict
404
406
  if build_config.certificate_arn is not None:
405
407
  properties["Domain"][
406
408
  "CertificateArn"
@@ -409,10 +411,35 @@ class AsyncLambdaController:
409
411
  properties["Domain"][
410
412
  "SecurityPolicy"
411
413
  ] = build_config.tls_version
414
+ elif (
415
+ build_config.auto_create_acm_certificate is True
416
+ and build_config.hosted_zone_id is not None
417
+ ):
418
+ template["Resources"]["AsyncLambdaAPICertificate"] = {
419
+ "Type": "AWS::CertificateManager::Certificate",
420
+ "Properties": {
421
+ "DomainName": build_config.domain_name,
422
+ "ValidationMethod": "DNS",
423
+ "DomainValidationOptions": [
424
+ {
425
+ "DomainName": build_config.domain_name,
426
+ "HostedZoneId": build_config.hosted_zone_id,
427
+ }
428
+ ],
429
+ },
430
+ }
431
+ properties["Domain"]["CertificateArn"] = {
432
+ "Ref": "AsyncLambdaAPICertificate"
433
+ }
434
+ if build_config.tls_version is not None:
435
+ properties["Domain"][
436
+ "SecurityPolicy"
437
+ ] = build_config.tls_version
412
438
  if build_config.hosted_zone_id is not None:
413
- properties["Domain"]["Route53"] = { # type: ignore
439
+ properties["Domain"]["Route53"] = {
414
440
  "HostedZoneId": build_config.hosted_zone_id
415
441
  }
442
+
416
443
  template["Resources"]["AsyncLambdaAPIGateway"] = {
417
444
  "Type": "AWS::Serverless::Api",
418
445
  "Properties": properties,
@@ -497,6 +524,23 @@ class AsyncLambdaController:
497
524
  self.parent_controller.set_current_invocation_id(invocation_id)
498
525
  self.current_invocation_id = invocation_id
499
526
 
527
+ def set_current_event_context(self, context: Any):
528
+ """
529
+ Set the current event context on the parent controller.
530
+ """
531
+ if self.parent_controller is not None:
532
+ self.parent_controller.set_current_event_context(context)
533
+ return
534
+ self._current_event_context = context
535
+
536
+ def get_current_event_context(self) -> Any:
537
+ """
538
+ Get the current event context
539
+ """
540
+ if self.parent_controller is not None:
541
+ return self.parent_controller.get_current_event_context()
542
+ return self._current_event_context
543
+
500
544
  def handle_invocation(self, event, context, task_id: Optional[str] = None):
501
545
  """
502
546
  Handles the invocation of a task based on its trigger type.
@@ -525,6 +569,8 @@ class AsyncLambdaController:
525
569
 
526
570
  args = (event, context, task)
527
571
 
572
+ self.set_current_event_context(context)
573
+
528
574
  if task.trigger_type == TaskTriggerType.MANAGED_SQS:
529
575
  _event = ManagedSQSEvent(*args)
530
576
  lane_count = task.get_lane_count()
@@ -591,6 +637,7 @@ class AsyncLambdaController:
591
637
  delay: int = 0,
592
638
  force_sync: bool = False,
593
639
  lane: Optional[int] = None,
640
+ message_group_id: Optional[str] = None,
594
641
  ):
595
642
  """
596
643
  Sends an asynchronous invocation payload to a managed or external task via SQS.
@@ -621,6 +668,7 @@ class AsyncLambdaController:
621
668
  delay=delay,
622
669
  force_sync=force_sync,
623
670
  lane=lane,
671
+ message_group_id=message_group_id,
624
672
  )
625
673
  is_external_task = False
626
674
  if destination_task_id not in self.tasks:
@@ -667,6 +715,7 @@ class AsyncLambdaController:
667
715
  # Sync invocation with mock event/context
668
716
  current_task_id = self.current_task_id
669
717
  current_lane = self.get_current_lane()
718
+ current_context = self.get_current_event_context()
670
719
  queue_arn = destination_task.get_managed_queue_arn(lane=lane)
671
720
  mock_event = MockSQSLambdaEvent(
672
721
  json.dumps(sqs_payload), source_queue_arn=queue_arn
@@ -677,6 +726,7 @@ class AsyncLambdaController:
677
726
  )
678
727
  self.set_current_task_id(current_task_id)
679
728
  self.set_current_lane(current_lane)
729
+ self.set_current_event_context(current_context)
680
730
  return result
681
731
  else:
682
732
  if is_external_task:
@@ -684,10 +734,15 @@ class AsyncLambdaController:
684
734
  else:
685
735
  assert destination_task is not None
686
736
  url = destination_task.get_managed_queue_url(lane=lane)
737
+
738
+ _kwargs = {}
739
+ if message_group_id is not None:
740
+ _kwargs["MessageGroupId"] = message_group_id
687
741
  get_sqs_client().send_message(
688
742
  QueueUrl=url,
689
743
  MessageBody=json.dumps(sqs_payload),
690
744
  DelaySeconds=delay,
745
+ **_kwargs,
691
746
  )
692
747
 
693
748
  def send_async_invoke_payload_batch(
@@ -932,6 +987,7 @@ class AsyncLambdaController:
932
987
  delay: int = 0,
933
988
  force_sync: bool = False,
934
989
  lane: Optional[int] = None,
990
+ message_group_id: Optional[str] = None,
935
991
  ):
936
992
  """
937
993
  Asynchronously invokes a task by sending a payload to the specified destination.
@@ -960,6 +1016,7 @@ class AsyncLambdaController:
960
1016
  delay=delay,
961
1017
  force_sync=force_sync,
962
1018
  lane=lane,
1019
+ message_group_id=message_group_id,
963
1020
  )
964
1021
 
965
1022
  def async_invoke_batch(
@@ -1,5 +1,6 @@
1
1
  [build-system]
2
- requires = ["setuptools<68.0.0", "wheel"]
2
+ requires = ["hatchling>=1.25"]
3
+ build-backend = "hatchling.build"
3
4
 
4
5
  [project]
5
6
  name = "async-lambda-unstable"
@@ -15,11 +16,29 @@ local = ["boto3", "flask>=2.3.0,<3.0.0"]
15
16
  [project.scripts]
16
17
  async-lambda = "async_lambda.cli:cli"
17
18
 
18
- [tool.setuptools.dynamic]
19
- version = { attr = "async_lambda.__version__" }
19
+ [tool.hatch.version]
20
+ path = "async_lambda/__init__.py"
21
+
22
+ [tool.hatch.build.targets.wheel]
23
+ packages = ["async_lambda"]
24
+
25
+ [tool.hatch.build.targets.sdist]
26
+ include = [
27
+ "async_lambda/**",
28
+ "pyproject.toml",
29
+ "README.md",
30
+ "LICENSE",
31
+ ]
32
+ # (optional) extra excludes for safety
33
+ exclude = [
34
+ "tests/**",
35
+ ".github/**",
36
+ "docs/**",
37
+ "examples/**",
38
+ "scripts/**",
39
+ "*.ipynb",
40
+ ]
20
41
 
21
- [tool.setuptools.package-data]
22
- "async_lambda" = ["py.typed"]
23
42
 
24
43
  [tool.pytest.ini_options]
25
44
  minversion = "6.0"
@@ -1,438 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: async-lambda-unstable
3
- Version: 0.5.7
4
- Summary: A framework for creating AWS Lambda Async Workflows. - Unstable Branch
5
- Author-email: "Nuclei, Inc" <engineering@nuclei.ai>
6
- Description-Content-Type: text/markdown
7
- Provides-Extra: local
8
-
9
- # async-lambda
10
-
11
- Async-lambda is a Python framework for building scalable, event-driven AWS Lambda applications with first-class support for asynchronous invocation via SQS queues. It provides a high-level abstraction for orchestrating Lambda functions, managing event triggers, and handling complex workflows with minimal boilerplate.
12
-
13
- `async-lambda` converts your application into a `Serverless Application Model (SAM)`
14
- template which can be deployed with the `SAM` cli tool or via Cloudformation.
15
-
16
- ## Features
17
-
18
- - **Async Task Abstraction**: Define tasks as Lambda functions triggered by SQS, API Gateway, DynamoDB Streams, or scheduled events.
19
- - **Automatic SAM Template Generation**: Converts your application into a Serverless Application Model (SAM) template for deployment.
20
- - **Lane-based Parallelism**: Scale async tasks horizontally using multiple SQS queues (lanes) and control concurrency per lane.
21
- - **Middleware Support**: Register middleware to wrap task execution, enabling cross-cutting concerns (logging, auth, etc.).
22
- - **Large Payload Handling**: Offload large payloads to S3 automatically when exceeding SQS size limits.
23
- - **Dead Letter Queues (DLQ)**: Robust error handling and message redrive for failed async invocations.
24
- - **Configurable via Code and JSON**: Set configuration at the app, stage, and task level using Python or config files.
25
- - **Task Initialization**: Run custom logic during Lambda INIT phase for caching, setup, or resource allocation.
26
-
27
- ## Getting Started
28
-
29
- ### Installation
30
-
31
- Add async-lambda to your project (see requirements in `pyproject.toml`).
32
-
33
- ### Basic Usage
34
-
35
- ```python
36
- from async_lambda import AsyncLambdaController, config_set_name, ScheduledEvent, ManagedSQSEvent
37
-
38
- app = AsyncLambdaController()
39
- config_set_name("project-name")
40
- lambda_handler = app.async_lambda_handler # Required Lambda export
41
-
42
- @app.scheduled_task('ScheduledTask1', schedule_expression="rate(15 minutes)")
43
- def scheduled_task_1(event: ScheduledEvent):
44
- app.async_invoke("AsyncTask1", payload={"foo": "bar"})
45
-
46
- @app.async_task('AsyncTask1')
47
- def async_task_2(event: ManagedSQSEvent):
48
- print(event.payload) # {"foo": "bar"}
49
- ```
50
-
51
- ### Packaging & Deployment
52
-
53
- Use the async-lambda CLI to build and package your app:
54
-
55
- ```bash
56
- async-lambda build app --stage <stage-name>
57
- ```
58
-
59
- This generates a SAM template (`template.json`) and deployment bundle (`deployment.zip`). Deploy using AWS SAM CLI or CloudFormation.
60
-
61
- ## Core Concepts
62
-
63
- ### Controller & Tasks
64
-
65
- - **AsyncLambdaController**: Central orchestrator for registering tasks, managing middleware, and handling invocations.
66
- - **Task**: Each task is a Lambda function with a unique `task_id` and a trigger type (SQS, API, DynamoDB, schedule).
67
-
68
- #### Task Decorators
69
-
70
- All task decorators accept common configuration arguments:
71
- - `memory`: Memory allocation (MB)
72
- - `timeout`: Timeout (seconds)
73
- - `ephemeral_storage`: Ephemeral storage (MB)
74
- - `maximum_concurrency`: Max concurrency for SQS triggers (int or list per lane)
75
- - `lane_count`: Number of parallel lanes (for async tasks)
76
- - `init_tasks`: Functions to run during Lambda INIT phase
77
-
78
- #### Example: Async Task
79
-
80
- ```python
81
- @app.async_task("TaskID")
82
- def async_task(event: ManagedSQSEvent):
83
- print(event.payload)
84
- ```
85
-
86
-
87
- **It is quite easy to get into infinite looping situations when utilizing `async-lambda` and care should be taken.**
88
-
89
- **INFINITE LOOP EXAMPLE**
90
-
91
- ```python
92
- # If task_1 where to ever get invoked, then it would start an infinite loop with
93
- # task 1 invoking task 2, task 2 invoking task 1, and repeat...
94
-
95
- @app.async_task("Task1")
96
- def task_1(event: ManagedSQSEvent):
97
- app.async_invoke("Task2", {})
98
-
99
- @app.async_task("Task2")
100
- def task_1(event: ManagedSQSEvent):
101
- app.async_invoke("Task1", {})
102
- ```
103
-
104
- #### Example: Scheduled Task
105
-
106
- ```python
107
- @app.scheduled_task("TaskID", schedule_expression='rate(15 minutes)')
108
- def scheduled_task(event: ScheduledEvent):
109
- ...
110
- ```
111
-
112
- #### Example: API Task
113
-
114
- ```python
115
- @app.api_task("TaskID", path='/test', method='get')
116
- def api_task(event: APIEvent):
117
- print(event.headers)
118
- print(event.querystring_params)
119
- print(event.body)
120
- ```
121
-
122
- #### Example: Unmanaged SQS Task
123
-
124
- ```python
125
- @app.sqs_task("TaskID", queue_arn='queue-arn')
126
- def sqs_task(event: UnmanagedSQSEvent):
127
- print(event.body)
128
- ```
129
-
130
- ### Lanes & Concurrency
131
-
132
- Async tasks can be scaled horizontally using lanes. Each lane is a separate SQS queue and can have its own concurrency limit. Lane assignment can be controlled at the controller, sub-controller, or task level.
133
-
134
- ```python
135
- app = AsyncLambdaController(lane_count=2)
136
-
137
- @app.async_task("SwitchBoard")
138
- def switch_board(event: ManagedSQSEvent):
139
- lane = 1 if event.payload['value'] > 50000 else 0
140
- app.async_invoke("ProcessingTask", event.payload, lane=lane)
141
-
142
- @app.async_task("ProcessingTask", maximum_concurrency=[10, 2])
143
- def processing_task(event: ManagedSQSEvent):
144
- ...
145
- ```
146
-
147
- ### Middleware
148
-
149
- Middleware functions wrap task execution and can be used for logging, authentication, or modifying events/responses.
150
-
151
- ```python
152
- def async_lambda_middleware(event, call_next):
153
- print(f"Invocation Payload: {event}")
154
- result = call_next(event)
155
- print(f"Invocation Result: {result}")
156
- return result
157
-
158
- controller = AsyncLambdaController(middleware=[([BaseEvent], async_lambda_middleware)])
159
- ```
160
-
161
- If there are multiple middleware functions then `call_next` will actually be calling the next middleware function in the stack.
162
-
163
- For example if there is middleware functions `A` and `B` registered in that order.
164
- Then the execution order would go:
165
-
166
- `A(Pre)` -> `B(Pre)` -> `Task` -> `B(Post)` -> `A(Post)`
167
-
168
- ### Large Payloads
169
-
170
- If a payload exceeds SQS size limits, async-lambda automatically stores it in S3 and passes a reference key to the Lambda function.
171
-
172
- ### Dead Letter Queues (DLQ)
173
-
174
- All async tasks share a DLQ for failed messages. You can configure custom DLQ tasks for advanced error handling.
175
-
176
- ## async-lambda config
177
-
178
- Configuration options can be set with the `.async_lambda/config.json` file.
179
- The configuration options can be set at the app, stage, and task level. A configuration option set
180
- will apply unless overridden at a more specific level (app -> stage -> task -> stage).
181
- The override logic attempts to be non-destructive so if you have a `layers` of `['layer_1']` at the app level,
182
- and `[layer_2]` at the stage level, then the value will be `['layer_1', 'layer_2']`.
183
-
184
- **Config file levels schema**
185
-
186
- ```
187
- {
188
- # APP LEVEL
189
- "stages": {
190
- "stage_name": {
191
- # STAGE LEVEL
192
- }
193
- },
194
- "tasks": {
195
- "task_id": {
196
- # TASK LEVEL
197
- "stages": {
198
- "stage_name": {
199
- # TASK STAGE LEVEL
200
- }
201
- }
202
- }
203
- }
204
- }
205
- ```
206
-
207
- **At any of these `levels` any of the configuration options can be set:**
208
- With the exception of `domain_name`, `tls_version`, and `certificate_arn` which can not be set at the task level.
209
-
210
- ### environment_variables
211
-
212
- ```
213
- {
214
- "ENV_VAR_NAME": "ENV_VAR_VALUE"
215
- }
216
- ```
217
-
218
- This config value will set environment variables for the function execution.
219
- These environment variables will also be available during build time.
220
-
221
- [The value is passed to the `Environment` property on `SAM::Serverless::Function`](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html#sam-function-environment)
222
-
223
- ### policies
224
-
225
- ```
226
- [
227
- 'IAM_POLICY_ARN' | STATEMENT
228
- ]
229
- ```
230
-
231
- Use this config option to attach any arbitrary policies to the lambda functions execution role.
232
-
233
- [The value is passed to the `Policies` property on `SAM::Serverless::Function`](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html#sam-function-policies), in addition to the `async-lambda` created inline policies.
234
-
235
- ### layers
236
-
237
- ```
238
- [
239
- "LAYER_ARN"
240
- ]
241
- ```
242
-
243
- Use this config option to add any arbitrary lambda layers to the lambda functions. Ordering matters,
244
- and merging is done thru concatenation.
245
-
246
- [The value is passed to the `Layers` property on `SAM::Serverless::Function`](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html#sam-function-layers)
247
-
248
- ### subnet_ids
249
-
250
- ```
251
- [
252
- "SUBNET_ID"
253
- ]
254
- ```
255
-
256
- Use this config option to put the lambda function into a vpc/subnet.
257
-
258
- The value is passed into the [`SubnetIds`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-vpcconfig.html) field of the [`VpcConfig` property on `SAM::Serverless::Function`](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html#sam-function-vpcconfig)
259
-
260
- ### security_group_ids
261
-
262
- ```
263
- [
264
- "SECURITY_GROUP_ID"
265
- ]
266
- ```
267
-
268
- Use this config option to attach a security group to the lambda function.
269
-
270
- The value is passed into the [`SecurityGroupIds`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-vpcconfig.html) field of the [`VpcConfig` property on `SAM::Serverless::Function`](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html#sam-function-vpcconfig)
271
-
272
- ### managed_queue_extras
273
-
274
- ```
275
- [
276
- {
277
- # Cloudformation resource
278
- }
279
- ]
280
- ```
281
-
282
- Use this config option to add extra resources for managed SQS queues (`async_task` tasks.)
283
-
284
- For example this might be used to attach alarms to these queues.
285
-
286
- Each item in the list should be a complete cloudformation resource. `async-lambda` provides a few custom substitutions
287
- so that you can reference the extras and the associated managed sqs resource by `LogicalId`.
288
-
289
- - `$QUEUEID` will be replaced with the `LogicalId` of the associated Managed SQS queue.
290
- - `$EXTRA<index>` will be replaced with the `LogicalId` of the extra at the specified index.
291
-
292
- ### method_settings
293
-
294
- **This config value can only be set at the app or stage level.**
295
-
296
- ```
297
- [
298
- {...}
299
- ]
300
- ```
301
-
302
- If your `async-lambda` app contains any `api_task` tasks, then a `AWS::Serverless::Api` resource is created.
303
-
304
- The value is passed into the [`MethodSettings`](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-api.html#sam-api-methodsettings) property of the `AWS::Serverless::Api`. The spec for `MethodSetting` can be found [here](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html).
305
-
306
- ### domain_name
307
-
308
- **This config value can only be set at the app or stage level.**
309
-
310
- ```
311
- "domain_name"
312
- ```
313
-
314
- If your `async-lambda` app contains any `api_task` tasks, then a `AWS::Serverless::Api` resource is created.
315
-
316
- This config value will set the [`DomainName`](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-api-domainconfiguration.html) field of the [`Domain` property](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-api.html#sam-api-domain)
317
-
318
- ### tls_version
319
-
320
- **This config value can only be set at the app or stage level.**
321
-
322
- ```
323
- "tls_version"
324
- ```
325
-
326
- If your `async-lambda` app contains any `api_task` tasks, then a `AWS::Serverless::Api` resource is created.
327
-
328
- This config value will set the [`SecurityPolicy`](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-api-domainconfiguration.html) field of the [`Domain` property](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-api.html#sam-api-domain)
329
-
330
- Possible values are `TLS_1_0` and `TLS_1_2`
331
-
332
- ### certificate_arn
333
-
334
- **This config value can only be set at the app or stage level.**
335
-
336
- ```
337
- "certificate_arn"
338
- ```
339
-
340
- If your `async-lambda` app contains any `api_task` tasks, then a `AWS::Serverless::Api` resource is created.
341
-
342
- This config value will set the [`CertificateArn`](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-api-domainconfiguration.html) field of the [`Domain` property](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-api.html#sam-api-domain)
343
-
344
- ### hosted_zone_id
345
-
346
- **This config value can only be set at the app or stage level.**
347
-
348
- ```
349
- "hosted_zone_id"
350
- ```
351
-
352
- If your `async-lambda` app contains any `api_task` tasks, then a `AWS::Serverless::Api` resource is created.
353
-
354
- This config value will set the [`HostedZoneId`](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-api-domainconfiguration.html) field of the [`Route53`](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-api-domainconfiguration.html#sam-api-domainconfiguration-route53) property of the [`Domain` property](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-api.html#sam-api-domain).
355
-
356
- This will create a DNS record on the given hosted zone for the api gateway endpoint created by the SAM deployment.
357
-
358
- ### tags
359
-
360
- ```
361
- {
362
- "TAG_NAME": "TAG_VALUE"
363
- }
364
- ```
365
-
366
- This config value will set the `Tags` field of all resources created by async-lambda. This will not set the field on `managed_queue_extras` resources.
367
-
368
- The keys `framework` and `framework-version` will always be set and the system values will override any values set by the user.
369
-
370
- For managed queues the tags `async-lambda-queue-type` will be set to `dlq`, `dlq-task`, or `managed` depending on the queue type.
371
-
372
- For `async_task` queues (non dlq-task) the `async-lambda-lane` will be set.
373
-
374
- ### logging_config
375
-
376
- ```
377
- {
378
- "ApplicationLogLevel": "TRACE" | "DEBUG" | "INFO" | "WARN" | "ERROR" | "FATAL",
379
- "LogFormat": "Text" | "JSON",
380
- "LogGroup": "",
381
- "SystemLogLevel": "DEBUG" | "INFO" | "WARN"
382
- }
383
- ```
384
-
385
- The value is passed directly to the [`LoggingConfig`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html#cfn-lambda-function-loggingconfig) Cloudformation parameter for lambda function/s.
386
-
387
- See above for full details on configuration schema and merging behavior.
388
-
389
- ## Advanced Usage
390
-
391
- ### Task Initialization
392
-
393
- Use the `init_tasks` argument to run setup logic during Lambda INIT phase. This is useful for caching, resource allocation, or one-time setup.
394
-
395
- ```python
396
- def setup_cache(task_id):
397
- ...
398
-
399
- @app.async_task("TaskID", init_tasks=[setup_cache])
400
- def async_task(event: ManagedSQSEvent):
401
- ...
402
- ```
403
-
404
- ### Defer Utility
405
-
406
- The `Defer` class allows you to cache values during INIT and only execute a function when its value is requested.
407
-
408
- ```python
409
- cache = Defer(get_a_value, 10, 100)
410
-
411
- @app.async_task("Task", init_tasks=[cache.execute])
412
- def task(event: ManagedSQSEvent):
413
- for i in range(cache.value):
414
- ...
415
- ```
416
-
417
- ## Known Limitations
418
-
419
- - Not all Lambda configuration options are supported (see code for extension points)
420
- - Payloads must be JSON serializable
421
- - Infinite loops are possible if tasks invoke each other recursively
422
-
423
- ## Project Structure
424
-
425
- - `async_lambda/`: Core framework code
426
- - `controller.py`: Main controller and orchestration logic
427
- - `models/`: Event, response, and task models
428
- - `middleware.py`: Middleware registration and execution
429
- - `client.py`, `env.py`, `config.py`: AWS clients and configuration
430
- - `defer.py`: Defer utility for caching
431
- - `payload_encoder.py`, `util.py`: Utilities
432
- - `example/`: Example usage and sample app
433
- - `scripts/`: Linting and testing scripts
434
- - `test/`: Unit tests
435
-
436
- ## License
437
-
438
- See LICENSE file for details.
@@ -1,34 +0,0 @@
1
- README.md
2
- pyproject.toml
3
- async_lambda/__init__.py
4
- async_lambda/build_config.py
5
- async_lambda/cli.py
6
- async_lambda/client.py
7
- async_lambda/config.py
8
- async_lambda/controller.py
9
- async_lambda/defer.py
10
- async_lambda/env.py
11
- async_lambda/middleware.py
12
- async_lambda/payload_encoder.py
13
- async_lambda/py.typed
14
- async_lambda/util.py
15
- async_lambda/models/__init__.py
16
- async_lambda/models/api_response.py
17
- async_lambda/models/case_insensitive_dict.py
18
- async_lambda/models/task.py
19
- async_lambda/models/events/__init__.py
20
- async_lambda/models/events/api_event.py
21
- async_lambda/models/events/base_event.py
22
- async_lambda/models/events/dynamodb_event.py
23
- async_lambda/models/events/managed_sqs_batch_event.py
24
- async_lambda/models/events/managed_sqs_event.py
25
- async_lambda/models/events/scheduled_event.py
26
- async_lambda/models/events/unmanaged_sqs_event.py
27
- async_lambda/models/mock/mock_context.py
28
- async_lambda/models/mock/mock_event.py
29
- async_lambda_unstable.egg-info/PKG-INFO
30
- async_lambda_unstable.egg-info/SOURCES.txt
31
- async_lambda_unstable.egg-info/dependency_links.txt
32
- async_lambda_unstable.egg-info/entry_points.txt
33
- async_lambda_unstable.egg-info/requires.txt
34
- async_lambda_unstable.egg-info/top_level.txt
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- async-lambda = async_lambda.cli:cli
@@ -1,6 +0,0 @@
1
- click>=8.0.0
2
- typing-extensions>=4.0.0
3
-
4
- [local]
5
- boto3
6
- flask<3.0.0,>=2.3.0
@@ -1,4 +0,0 @@
1
- [egg_info]
2
- tag_build =
3
- tag_date = 0
4
-