simple_aws_lambda 0.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- simple_aws_lambda/__init__.py +1 -0
- simple_aws_lambda/api.py +22 -0
- simple_aws_lambda/client.py +140 -0
- simple_aws_lambda/model.py +488 -0
- simple_aws_lambda/paths.py +60 -0
- simple_aws_lambda/recipe.py +320 -0
- simple_aws_lambda/vendor/__init__.py +2 -0
- simple_aws_lambda/vendor/pytest_cov_helper.py +148 -0
- simple_aws_lambda-0.3.1.dist-info/METADATA +134 -0
- simple_aws_lambda-0.3.1.dist-info/RECORD +14 -0
- simple_aws_lambda-0.3.1.dist-info/WHEEL +5 -0
- simple_aws_lambda-0.3.1.dist-info/licenses/AUTHORS.rst +15 -0
- simple_aws_lambda-0.3.1.dist-info/licenses/LICENSE.txt +21 -0
- simple_aws_lambda-0.3.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from functools import cached_property
|
|
5
|
+
|
|
6
|
+
_dir_here = Path(__file__).absolute().parent
|
|
7
|
+
PACKAGE_NAME = _dir_here.name
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PathEnum:
|
|
11
|
+
"""
|
|
12
|
+
Centralized enumeration of all project paths with absolute path references.
|
|
13
|
+
|
|
14
|
+
Provides IDE-autocomplete-friendly access to all project directories and files using
|
|
15
|
+
absolute paths to eliminate current directory dependencies and ensure consistent path
|
|
16
|
+
resolution across different execution contexts and DevOps workflows.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
@cached_property
|
|
20
|
+
def dir_home(self):
|
|
21
|
+
return Path.home()
|
|
22
|
+
|
|
23
|
+
dir_project_root = _dir_here.parent
|
|
24
|
+
dir_tmp = dir_project_root / "tmp"
|
|
25
|
+
|
|
26
|
+
# Source Code
|
|
27
|
+
dir_package = _dir_here
|
|
28
|
+
path_pyproject_toml = dir_project_root / "pyproject.toml"
|
|
29
|
+
path_requirements_txt = dir_project_root / "requirements.txt"
|
|
30
|
+
path_authors = dir_project_root / "AUTHORS.rst"
|
|
31
|
+
path_license = dir_project_root / "LICENSE.txt"
|
|
32
|
+
path_release_history = dir_project_root / "release-history.rst"
|
|
33
|
+
|
|
34
|
+
# Virtual Environment
|
|
35
|
+
dir_venv = dir_project_root / ".venv"
|
|
36
|
+
dir_venv_bin = dir_venv / "bin"
|
|
37
|
+
path_venv_bin_pip = dir_venv_bin / "pip"
|
|
38
|
+
path_venv_bin_python = dir_venv_bin / "python"
|
|
39
|
+
path_venv_bin_pytest = dir_venv_bin / "pytest"
|
|
40
|
+
|
|
41
|
+
# Test
|
|
42
|
+
dir_htmlcov = dir_project_root / "htmlcov"
|
|
43
|
+
path_cov_index_html = dir_htmlcov / "index.html"
|
|
44
|
+
dir_unit_test = dir_project_root / "tests"
|
|
45
|
+
dir_int_test = dir_project_root / "tests_int"
|
|
46
|
+
dir_load_test = dir_project_root / "tests_load"
|
|
47
|
+
|
|
48
|
+
# Documentation
|
|
49
|
+
dir_docs_source = dir_project_root / "docs" / "source"
|
|
50
|
+
dir_docs_build_html = dir_project_root / "docs" / "build" / "html"
|
|
51
|
+
|
|
52
|
+
# Build
|
|
53
|
+
dir_build = dir_project_root / "build"
|
|
54
|
+
dir_dist = dir_project_root / "dist"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
path_enum = PathEnum()
|
|
58
|
+
"""
|
|
59
|
+
Single entry point for all project paths with absolute path references.
|
|
60
|
+
"""
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
High-level recipes built on top of the client functions, such as finding the
|
|
5
|
+
latest layer version, cleaning up old versions, and managing cross account access.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import typing as T
|
|
11
|
+
import enum
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from datetime import timezone
|
|
14
|
+
from datetime import timedelta
|
|
15
|
+
|
|
16
|
+
import botocore.exceptions
|
|
17
|
+
from func_args.api import OPT
|
|
18
|
+
|
|
19
|
+
from .model import LayerVersion
|
|
20
|
+
from .client import list_layer_versions
|
|
21
|
+
|
|
22
|
+
if T.TYPE_CHECKING: # pragma: no cover
|
|
23
|
+
from mypy_boto3_lambda.client import LambdaClient
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_latest_layer_version(
|
|
27
|
+
lambda_client: LambdaClient,
|
|
28
|
+
layer_name: str,
|
|
29
|
+
compatible_runtime: str = OPT,
|
|
30
|
+
compatible_architecture: str = OPT,
|
|
31
|
+
_sort_descending: bool = False,
|
|
32
|
+
) -> LayerVersion | None:
|
|
33
|
+
"""
|
|
34
|
+
Call the AWS Lambda Layer API to retrieve the latest deployed layer version.
|
|
35
|
+
If it returns ``None``, it indicates that no layer has been deployed yet.
|
|
36
|
+
|
|
37
|
+
Example: if layer has version 1, 2, 3, then this function return 3.
|
|
38
|
+
If there's no layer version created yet, then this function returns None.
|
|
39
|
+
|
|
40
|
+
Reference:
|
|
41
|
+
|
|
42
|
+
- https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/lambda.html#Lambda.Client.list_layer_versions
|
|
43
|
+
"""
|
|
44
|
+
if _sort_descending:
|
|
45
|
+
max_items = 10
|
|
46
|
+
else:
|
|
47
|
+
max_items = 1
|
|
48
|
+
layer_versions = list_layer_versions(
|
|
49
|
+
lambda_client=lambda_client,
|
|
50
|
+
layer_name=layer_name,
|
|
51
|
+
compatible_runtime=compatible_runtime,
|
|
52
|
+
compatible_architecture=compatible_architecture,
|
|
53
|
+
max_items=max_items,
|
|
54
|
+
_sort_descending=_sort_descending,
|
|
55
|
+
).all()
|
|
56
|
+
if len(layer_versions) == 0:
|
|
57
|
+
return None
|
|
58
|
+
else:
|
|
59
|
+
return layer_versions[0]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def cleanup_old_layer_versions(
|
|
63
|
+
lambda_client: LambdaClient,
|
|
64
|
+
layer_name: str,
|
|
65
|
+
keep_last_n_versions: int = 5,
|
|
66
|
+
keep_versions_newer_than_seconds: int = 90 * 24 * 60 * 60,
|
|
67
|
+
real_run: bool = False,
|
|
68
|
+
_sort_descending: bool = False,
|
|
69
|
+
) -> list[int]:
|
|
70
|
+
"""
|
|
71
|
+
Delete old Lambda layer versions based on retention policy.
|
|
72
|
+
|
|
73
|
+
Keeps layer versions if they meet ANY of these conditions:
|
|
74
|
+
|
|
75
|
+
- Among the last N versions (most recent)
|
|
76
|
+
- Created within the last N seconds
|
|
77
|
+
|
|
78
|
+
:param lambda_client: AWS Lambda client
|
|
79
|
+
:param layer_name: Name of the Lambda layer
|
|
80
|
+
:param keep_last_n_versions: Number of most recent versions to keep
|
|
81
|
+
:param keep_versions_newer_than_seconds: Keep versions newer than this many seconds
|
|
82
|
+
:param real_run: If True, actually delete versions. If False, only return what would be deleted
|
|
83
|
+
|
|
84
|
+
:returns: List of version numbers that were deleted (or would be deleted in simulation mode)
|
|
85
|
+
|
|
86
|
+
Ref:
|
|
87
|
+
|
|
88
|
+
- `delete_layer_version <https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/lambda/client/delete_layer_version.html>`_
|
|
89
|
+
"""
|
|
90
|
+
# Get all layer versions
|
|
91
|
+
all_versions = list_layer_versions(
|
|
92
|
+
lambda_client=lambda_client,
|
|
93
|
+
layer_name=layer_name,
|
|
94
|
+
max_items=9999,
|
|
95
|
+
_sort_descending=True,
|
|
96
|
+
).all()
|
|
97
|
+
|
|
98
|
+
# Only exam versions beyond the last N to keep
|
|
99
|
+
other_versions = all_versions[keep_last_n_versions:]
|
|
100
|
+
|
|
101
|
+
if len(other_versions) == 0:
|
|
102
|
+
return []
|
|
103
|
+
|
|
104
|
+
# Calculate cutoff date
|
|
105
|
+
delta = timedelta(seconds=keep_versions_newer_than_seconds)
|
|
106
|
+
cutoff_date = datetime.now(timezone.utc) - delta
|
|
107
|
+
|
|
108
|
+
versions_to_delete = []
|
|
109
|
+
|
|
110
|
+
for version in other_versions:
|
|
111
|
+
# Keep if it's newer than cutoff date
|
|
112
|
+
if version.created_datetime > cutoff_date: # pragma: no cover
|
|
113
|
+
continue
|
|
114
|
+
|
|
115
|
+
# This version should be deleted
|
|
116
|
+
versions_to_delete.append(version.version)
|
|
117
|
+
|
|
118
|
+
# Delete the versions (if real_run is True)
|
|
119
|
+
deleted_versions = []
|
|
120
|
+
for version_number in versions_to_delete:
|
|
121
|
+
deleted_versions.append(version_number)
|
|
122
|
+
if real_run:
|
|
123
|
+
try:
|
|
124
|
+
lambda_client.delete_layer_version(
|
|
125
|
+
LayerName=layer_name,
|
|
126
|
+
VersionNumber=version_number,
|
|
127
|
+
)
|
|
128
|
+
except botocore.exceptions.ClientError: # pragma: no cover
|
|
129
|
+
# Continue with other versions even if one fails
|
|
130
|
+
pass
|
|
131
|
+
|
|
132
|
+
return deleted_versions
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class LambdaPermissionActionEnum(str, enum.Enum):
|
|
136
|
+
"""
|
|
137
|
+
Enum for different Lambda layer permission actions.
|
|
138
|
+
|
|
139
|
+
See: https://docs.aws.amazon.com/lambda/latest/dg/permissions-layer-cross-account.html
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
get_layer_version = "lambda:GetLayerVersion"
|
|
143
|
+
list_layer_versions = "lambda:ListLayerVersions"
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# Map action to a more human friendly name for statement id naming convention
|
|
147
|
+
action_to_name_mapper: dict[str, str] = {
|
|
148
|
+
LambdaPermissionActionEnum.get_layer_version.value: "GetLayerVersion",
|
|
149
|
+
LambdaPermissionActionEnum.list_layer_versions.value: "ListLayerVersions",
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class LayerPrincipalTypeEnum(str, enum.Enum):
|
|
154
|
+
"""
|
|
155
|
+
Enum for different types of layer principals.
|
|
156
|
+
|
|
157
|
+
Based on this AWS doc https://docs.aws.amazon.com/lambda/latest/dg/permissions-layer-cross-account.html
|
|
158
|
+
There are only three cross account Lambda layer permission patterns
|
|
159
|
+
The grant_aws_account_or_aws_organization_lambda_layer_version_access
|
|
160
|
+
and revoke_aws_account_or_aws_organization_lambda_layer_version_access
|
|
161
|
+
recipes only support these three patterns.
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
public = "public"
|
|
165
|
+
aws_account = "aws_account"
|
|
166
|
+
aws_organization = "aws_organization"
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def identify_principal_type(principal: str) -> LayerPrincipalTypeEnum:
|
|
170
|
+
"""
|
|
171
|
+
Identify the type of principal based on its format.
|
|
172
|
+
|
|
173
|
+
:param principal: The principal string to identify:
|
|
174
|
+
- "*" for public access
|
|
175
|
+
- "123456789012" for specific AWS account (12-digit account ID)
|
|
176
|
+
- "o-example123456" for AWS organization ID
|
|
177
|
+
|
|
178
|
+
:returns: The identified LayerPrincipalTypeEnum
|
|
179
|
+
"""
|
|
180
|
+
if principal == "*":
|
|
181
|
+
return LayerPrincipalTypeEnum.public
|
|
182
|
+
elif principal.isdigit() and len(principal) == 12:
|
|
183
|
+
return LayerPrincipalTypeEnum.aws_account
|
|
184
|
+
elif principal.startswith("o-"):
|
|
185
|
+
return LayerPrincipalTypeEnum.aws_organization
|
|
186
|
+
else: # pragma: no cover
|
|
187
|
+
raise ValueError(f"Invalid principal format: {principal}")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def get_layer_permission_statement_id(
|
|
191
|
+
action: str,
|
|
192
|
+
principal: str,
|
|
193
|
+
) -> str:
|
|
194
|
+
"""
|
|
195
|
+
Encode the statement ID for Lambda layer permission based on action and principal.
|
|
196
|
+
|
|
197
|
+
A statement ID may only contain ``[a-zA-Z0-9-_]``, so the public principal
|
|
198
|
+
``"*"`` is written as ``public``.
|
|
199
|
+
"""
|
|
200
|
+
name = action_to_name_mapper[action]
|
|
201
|
+
if principal == "*":
|
|
202
|
+
principal_part = "public"
|
|
203
|
+
else:
|
|
204
|
+
principal_part = principal
|
|
205
|
+
return f"allow-{principal_part}-{name}"
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def grant_aws_account_or_aws_organization_lambda_layer_version_access(
|
|
209
|
+
lambda_client: LambdaClient,
|
|
210
|
+
layer_name: str,
|
|
211
|
+
version_number: int,
|
|
212
|
+
principal: str,
|
|
213
|
+
):
|
|
214
|
+
"""
|
|
215
|
+
Grant other AWS accounts Lambda layer access to a specific layer version.
|
|
216
|
+
|
|
217
|
+
Idempotent version of the AWS Lambda add_layer_version_permission API that
|
|
218
|
+
automatically handles statement ID generation and manages conflicts by
|
|
219
|
+
allowing existing permissions to remain.
|
|
220
|
+
|
|
221
|
+
Grants the GetLayerVersion permission to the specified principal
|
|
222
|
+
(AWS account, AWS organization, or public access).
|
|
223
|
+
|
|
224
|
+
:param lambda_client: AWS Lambda client for the account that owns the layer
|
|
225
|
+
:param layer_name: Name of the Lambda layer
|
|
226
|
+
:param version_number: Version number of the layer to grant access to
|
|
227
|
+
:param principal: Principal to grant access to:
|
|
228
|
+
- "*" for public access
|
|
229
|
+
- "123456789012" for specific AWS account (12-digit account ID)
|
|
230
|
+
- "o-example123456" for AWS organization ID
|
|
231
|
+
|
|
232
|
+
Ref:
|
|
233
|
+
|
|
234
|
+
- `add_layer_version_permission <https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/lambda/client/add_layer_version_permission.html>`_
|
|
235
|
+
"""
|
|
236
|
+
layer_principal_type = identify_principal_type(principal)
|
|
237
|
+
if layer_principal_type == LayerPrincipalTypeEnum.public:
|
|
238
|
+
kwargs = {"Principal": principal}
|
|
239
|
+
elif layer_principal_type == LayerPrincipalTypeEnum.aws_account:
|
|
240
|
+
kwargs = {"Principal": principal}
|
|
241
|
+
elif layer_principal_type == LayerPrincipalTypeEnum.aws_organization:
|
|
242
|
+
kwargs = {
|
|
243
|
+
"Principal": "*",
|
|
244
|
+
"OrganizationId": principal,
|
|
245
|
+
}
|
|
246
|
+
else: # pragma: no cover
|
|
247
|
+
raise ValueError(f"Unsupported principal type: {principal}")
|
|
248
|
+
|
|
249
|
+
def add_layer_version_permission(action: str):
|
|
250
|
+
statement_id = get_layer_permission_statement_id(
|
|
251
|
+
action=action,
|
|
252
|
+
principal=principal,
|
|
253
|
+
)
|
|
254
|
+
try:
|
|
255
|
+
lambda_client.add_layer_version_permission(
|
|
256
|
+
LayerName=layer_name,
|
|
257
|
+
VersionNumber=version_number,
|
|
258
|
+
StatementId=statement_id,
|
|
259
|
+
Action=action,
|
|
260
|
+
**kwargs,
|
|
261
|
+
)
|
|
262
|
+
except botocore.exceptions.ClientError as e: # pragma: no cover
|
|
263
|
+
if e.response["Error"]["Code"] == "ResourceConflictException":
|
|
264
|
+
pass
|
|
265
|
+
else:
|
|
266
|
+
raise
|
|
267
|
+
|
|
268
|
+
# Currently, AWS Lambda only supports granting GetLayerVersion permission
|
|
269
|
+
add_layer_version_permission(LambdaPermissionActionEnum.get_layer_version.value)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def revoke_aws_account_or_aws_organization_lambda_layer_version_access(
|
|
273
|
+
lambda_client: LambdaClient,
|
|
274
|
+
layer_name: str,
|
|
275
|
+
version_number: int,
|
|
276
|
+
principal: str,
|
|
277
|
+
):
|
|
278
|
+
"""
|
|
279
|
+
Revoke AWS accounts Lambda layer access from a specific layer version.
|
|
280
|
+
|
|
281
|
+
Idempotent version of the AWS Lambda remove_layer_version_permission API that
|
|
282
|
+
automatically handles statement ID generation and gracefully handles cases
|
|
283
|
+
where permissions don't exist.
|
|
284
|
+
|
|
285
|
+
Removes the GetLayerVersion permission from the specified principal
|
|
286
|
+
(AWS account, AWS organization, or public access).
|
|
287
|
+
|
|
288
|
+
:param lambda_client: AWS Lambda client for the account that owns the layer
|
|
289
|
+
:param layer_name: Name of the Lambda layer
|
|
290
|
+
:param version_number: Version number of the layer to revoke access from
|
|
291
|
+
:param principal: Principal to revoke access from:
|
|
292
|
+
- "*" for public access
|
|
293
|
+
- "123456789012" for specific AWS account (12-digit account ID)
|
|
294
|
+
- "o-example123456" for AWS organization ID
|
|
295
|
+
|
|
296
|
+
Ref:
|
|
297
|
+
|
|
298
|
+
- `remove_layer_version_permission <https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/lambda/client/remove_layer_version_permission.html>`_
|
|
299
|
+
"""
|
|
300
|
+
identify_principal_type(principal)
|
|
301
|
+
|
|
302
|
+
def remove_layer_version_permission(action: str):
|
|
303
|
+
statement_id = get_layer_permission_statement_id(
|
|
304
|
+
action=action,
|
|
305
|
+
principal=principal,
|
|
306
|
+
)
|
|
307
|
+
try:
|
|
308
|
+
lambda_client.remove_layer_version_permission(
|
|
309
|
+
LayerName=layer_name,
|
|
310
|
+
VersionNumber=version_number,
|
|
311
|
+
StatementId=statement_id,
|
|
312
|
+
)
|
|
313
|
+
except botocore.exceptions.ClientError as e: # pragma: no cover
|
|
314
|
+
if e.response["Error"]["Code"] == "ResourceNotFoundException":
|
|
315
|
+
pass
|
|
316
|
+
else:
|
|
317
|
+
raise
|
|
318
|
+
|
|
319
|
+
# Currently, AWS Lambda only supports granting GetLayerVersion permission
|
|
320
|
+
remove_layer_version_permission(LambdaPermissionActionEnum.get_layer_version.value)
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import contextlib
|
|
6
|
+
import subprocess
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
__version__ = "0.2.1"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@contextlib.contextmanager
|
|
13
|
+
def temp_cwd(path: Path):
|
|
14
|
+
"""
|
|
15
|
+
Temporarily set the current working directory (CWD) and automatically
|
|
16
|
+
switch back when it's done.
|
|
17
|
+
"""
|
|
18
|
+
cwd = os.getcwd()
|
|
19
|
+
os.chdir(str(path))
|
|
20
|
+
try:
|
|
21
|
+
yield path
|
|
22
|
+
finally:
|
|
23
|
+
os.chdir(cwd)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def run_unit_test(
|
|
27
|
+
script: str,
|
|
28
|
+
root_dir: str,
|
|
29
|
+
):
|
|
30
|
+
"""
|
|
31
|
+
Run ``pytest -s --tb=native /path/to/script.py`` Command.
|
|
32
|
+
|
|
33
|
+
:param script: the path to test script
|
|
34
|
+
:param root_dir: the dir you want to temporarily set as cwd
|
|
35
|
+
"""
|
|
36
|
+
bin_pytest = Path(sys.executable).parent / "pytest"
|
|
37
|
+
args = [
|
|
38
|
+
f"{bin_pytest}",
|
|
39
|
+
"-s",
|
|
40
|
+
"--tb=native",
|
|
41
|
+
script,
|
|
42
|
+
]
|
|
43
|
+
with temp_cwd(Path(root_dir)):
|
|
44
|
+
subprocess.run(args)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def run_cov_test(
|
|
48
|
+
script: str,
|
|
49
|
+
module: str,
|
|
50
|
+
root_dir: str,
|
|
51
|
+
htmlcov_dir: str,
|
|
52
|
+
preview: bool = False,
|
|
53
|
+
is_folder: bool = False,
|
|
54
|
+
):
|
|
55
|
+
"""
|
|
56
|
+
The pytest-cov plugin gives you the coverage for entire project. What if
|
|
57
|
+
I want run per-module test independently and get per-module coverage?
|
|
58
|
+
|
|
59
|
+
This is a simple wrapper around pytest + coverage cli command. Allow you to run
|
|
60
|
+
coverage test from Python script and set the code coverage measurement scope.
|
|
61
|
+
|
|
62
|
+
Usage example:
|
|
63
|
+
|
|
64
|
+
suppose you have a source code folder structure like this::
|
|
65
|
+
|
|
66
|
+
/dir_git_repo/
|
|
67
|
+
/dir_git_repo/my_library
|
|
68
|
+
/dir_git_repo/my_library/__init__.py
|
|
69
|
+
/dir_git_repo/my_library/module1.py
|
|
70
|
+
/dir_git_repo/my_library/module2.py
|
|
71
|
+
|
|
72
|
+
In your module 1 unit test script, you can do this:
|
|
73
|
+
|
|
74
|
+
.. code-block:: python
|
|
75
|
+
|
|
76
|
+
from my_library.module1 import func1, func2
|
|
77
|
+
|
|
78
|
+
def test_func1():
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
def test_func2():
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
if __name__ == "__main__":
|
|
85
|
+
from fixa.pytest_cov_helper import run_cov_test
|
|
86
|
+
|
|
87
|
+
run_cov_test(
|
|
88
|
+
script=__file__,
|
|
89
|
+
module="my_library.module1", # test scope is the module1.py
|
|
90
|
+
root_dir="/path/to/dir_git_repo",
|
|
91
|
+
htmlcov_dir="/path/to/dir_git_repo/htmlcov",
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
In your all modules unit test script, you can do this:
|
|
95
|
+
|
|
96
|
+
.. code-block:: python
|
|
97
|
+
|
|
98
|
+
if __name__ == "__main__":
|
|
99
|
+
from fixa.pytest_cov_helper import run_cov_test
|
|
100
|
+
|
|
101
|
+
run_cov_test(
|
|
102
|
+
script=__file__,
|
|
103
|
+
module="my_library", # test scope is the my_library/
|
|
104
|
+
root_dir="/path/to/dir_git_repo",
|
|
105
|
+
htmlcov_dir="/path/to/dir_git_repo/htmlcov",
|
|
106
|
+
is_folder=True, # my_library is a folder
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
:param script: the test script absolute path
|
|
110
|
+
:param module: the dot notation to the python module you want to calculate
|
|
111
|
+
coverage
|
|
112
|
+
:param root_dir: the dir to dump coverage results binary file
|
|
113
|
+
:param htmlcov_dir: the dir to dump HTML output
|
|
114
|
+
:param preview: whether to open the HTML output in web browser after the test
|
|
115
|
+
:param is_folder: whether the module is a folder
|
|
116
|
+
|
|
117
|
+
Reference:
|
|
118
|
+
|
|
119
|
+
- https://pypi.org/project/pytest-cov/
|
|
120
|
+
"""
|
|
121
|
+
bin_pytest = Path(sys.executable).parent / "pytest"
|
|
122
|
+
if is_folder:
|
|
123
|
+
script = f"{Path(script).parent}"
|
|
124
|
+
if module.endswith(".py"): # pragma: no cover
|
|
125
|
+
module = module[:-3]
|
|
126
|
+
args = [
|
|
127
|
+
f"{bin_pytest}",
|
|
128
|
+
"-s",
|
|
129
|
+
"--tb=native",
|
|
130
|
+
f"--rootdir={root_dir}",
|
|
131
|
+
f"--cov={module}",
|
|
132
|
+
"--cov-report",
|
|
133
|
+
"term-missing",
|
|
134
|
+
"--cov-report",
|
|
135
|
+
f"html:{htmlcov_dir}",
|
|
136
|
+
script,
|
|
137
|
+
]
|
|
138
|
+
with temp_cwd(Path(root_dir)):
|
|
139
|
+
subprocess.run(args)
|
|
140
|
+
if preview: # pragma: no cover
|
|
141
|
+
platform = sys.platform
|
|
142
|
+
if platform in ["win32", "cygwin"]:
|
|
143
|
+
open_command = "start"
|
|
144
|
+
elif platform in ["darwin", "linux"]:
|
|
145
|
+
open_command = "open"
|
|
146
|
+
else:
|
|
147
|
+
raise NotImplementedError
|
|
148
|
+
subprocess.run([open_command, f"{Path(htmlcov_dir).joinpath('index.html')}"])
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: simple_aws_lambda
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Summary: Objective Oriented Python SDK for AWS Lambda.
|
|
5
|
+
Author-email: Sanhe Hu <husanhe@gmail.com>
|
|
6
|
+
Maintainer-email: Sanhe Hu <husanhe@gmail.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/MacHu-GWU/simple_aws_lambda-project
|
|
9
|
+
Project-URL: Documentation, https://simple-aws-lambda.readthedocs.io/en/latest/
|
|
10
|
+
Project-URL: Repository, https://github.com/MacHu-GWU/simple_aws_lambda-project
|
|
11
|
+
Project-URL: Issues, https://github.com/MacHu-GWU/simple_aws_lambda-project/issues
|
|
12
|
+
Project-URL: Changelog, https://github.com/MacHu-GWU/simple_aws_lambda-project/blob/main/release-history.rst
|
|
13
|
+
Project-URL: Download, https://pypi.org/pypi/simple-aws-lambda#files
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Natural Language :: English
|
|
17
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
18
|
+
Classifier: Operating System :: MacOS
|
|
19
|
+
Classifier: Operating System :: Unix
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
25
|
+
Requires-Python: <4.0,>=3.11
|
|
26
|
+
Description-Content-Type: text/x-rst
|
|
27
|
+
License-File: LICENSE.txt
|
|
28
|
+
License-File: AUTHORS.rst
|
|
29
|
+
Requires-Dist: boto3<2.0.0,>=1.35.99
|
|
30
|
+
Requires-Dist: iterproxy<1.0.0,>=0.3.1
|
|
31
|
+
Requires-Dist: func_args<2.0.0,>=1.0.1
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: rich<15.0.0,>=14.3.4; extra == "dev"
|
|
34
|
+
Requires-Dist: boto3-stubs[lambda]<2.0.0,>=1.35.99; extra == "dev"
|
|
35
|
+
Provides-Extra: test
|
|
36
|
+
Requires-Dist: pytest<10.0.0,>=9.1.1; extra == "test"
|
|
37
|
+
Requires-Dist: pytest-cov<8.0.0,>=7.1.0; extra == "test"
|
|
38
|
+
Requires-Dist: moto<6.0.0,>=5.1.10; extra == "test"
|
|
39
|
+
Provides-Extra: doc
|
|
40
|
+
Requires-Dist: Sphinx<9.0.0,>=8.2.3; extra == "doc"
|
|
41
|
+
Requires-Dist: sphinx-copybutton<1.0.0,>=0.5.2; extra == "doc"
|
|
42
|
+
Requires-Dist: sphinx-design<1.0.0,>=0.7.0; extra == "doc"
|
|
43
|
+
Requires-Dist: sphinx-jinja<3.0.0,>=2.0.2; extra == "doc"
|
|
44
|
+
Requires-Dist: furo==2025.12.19; extra == "doc"
|
|
45
|
+
Requires-Dist: pygments<3.0.0,>=2.18.0; extra == "doc"
|
|
46
|
+
Requires-Dist: ipython<8.19.0,>=8.18.1; extra == "doc"
|
|
47
|
+
Requires-Dist: ipykernel<8.0.0,>=7.3.0; extra == "doc"
|
|
48
|
+
Requires-Dist: nbsphinx<1.0.0,>=0.9.8; extra == "doc"
|
|
49
|
+
Requires-Dist: rstobj<3.0.0,>=2.0.0; extra == "doc"
|
|
50
|
+
Requires-Dist: docfly<4.0.0,>=3.0.3; extra == "doc"
|
|
51
|
+
Provides-Extra: mise
|
|
52
|
+
Requires-Dist: PyGithub<3.0.0,>=2.8.0; extra == "mise"
|
|
53
|
+
Requires-Dist: httpx2<3.0.0,>=2.13.1; extra == "mise"
|
|
54
|
+
Dynamic: license-file
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
.. image:: https://readthedocs.org/projects/simple-aws-lambda/badge/?version=latest
|
|
58
|
+
:target: https://simple-aws-lambda.readthedocs.io/en/latest/
|
|
59
|
+
:alt: Documentation Status
|
|
60
|
+
|
|
61
|
+
.. image:: https://github.com/MacHu-GWU/simple_aws_lambda-project/actions/workflows/main.yml/badge.svg
|
|
62
|
+
:target: https://github.com/MacHu-GWU/simple_aws_lambda-project/actions?query=workflow:CI
|
|
63
|
+
|
|
64
|
+
.. image:: https://codecov.io/gh/MacHu-GWU/simple_aws_lambda-project/branch/main/graph/badge.svg
|
|
65
|
+
:target: https://codecov.io/gh/MacHu-GWU/simple_aws_lambda-project
|
|
66
|
+
|
|
67
|
+
.. image:: https://img.shields.io/pypi/v/simple-aws-lambda.svg
|
|
68
|
+
:target: https://pypi.python.org/pypi/simple-aws-lambda
|
|
69
|
+
|
|
70
|
+
.. image:: https://img.shields.io/pypi/l/simple-aws-lambda.svg
|
|
71
|
+
:target: https://pypi.python.org/pypi/simple-aws-lambda
|
|
72
|
+
|
|
73
|
+
.. image:: https://img.shields.io/pypi/pyversions/simple-aws-lambda.svg
|
|
74
|
+
:target: https://pypi.python.org/pypi/simple-aws-lambda
|
|
75
|
+
|
|
76
|
+
.. image:: https://img.shields.io/badge/✍️_Release_History!--None.svg?style=social&logo=github
|
|
77
|
+
:target: https://github.com/MacHu-GWU/simple_aws_lambda-project/blob/main/release-history.rst
|
|
78
|
+
|
|
79
|
+
.. image:: https://img.shields.io/badge/⭐_Star_me_on_GitHub!--None.svg?style=social&logo=github
|
|
80
|
+
:target: https://github.com/MacHu-GWU/simple_aws_lambda-project
|
|
81
|
+
|
|
82
|
+
------
|
|
83
|
+
|
|
84
|
+
.. image:: https://img.shields.io/badge/Link-API-blue.svg
|
|
85
|
+
:target: https://simple-aws-lambda.readthedocs.io/en/latest/py-modindex.html
|
|
86
|
+
|
|
87
|
+
.. image:: https://img.shields.io/badge/Link-Install-blue.svg
|
|
88
|
+
:target: `install`_
|
|
89
|
+
|
|
90
|
+
.. image:: https://img.shields.io/badge/Link-GitHub-blue.svg
|
|
91
|
+
:target: https://github.com/MacHu-GWU/simple_aws_lambda-project
|
|
92
|
+
|
|
93
|
+
.. image:: https://img.shields.io/badge/Link-Submit_Issue-blue.svg
|
|
94
|
+
:target: https://github.com/MacHu-GWU/simple_aws_lambda-project/issues
|
|
95
|
+
|
|
96
|
+
.. image:: https://img.shields.io/badge/Link-Request_Feature-blue.svg
|
|
97
|
+
:target: https://github.com/MacHu-GWU/simple_aws_lambda-project/issues
|
|
98
|
+
|
|
99
|
+
.. image:: https://img.shields.io/badge/Link-Download-blue.svg
|
|
100
|
+
:target: https://pypi.org/pypi/simple-aws-lambda#files
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
Welcome to ``simple_aws_lambda`` Documentation
|
|
104
|
+
==============================================================================
|
|
105
|
+
.. image:: https://simple-aws-lambda.readthedocs.io/en/latest/_static/simple_aws_lambda-logo.png
|
|
106
|
+
:target: https://simple-aws-lambda.readthedocs.io/en/latest/
|
|
107
|
+
|
|
108
|
+
simple_aws_lambda is a Pythonic library that provides a simplified, high-level interface for AWS Lambda operations. Built on top of boto3, it offers intuitive data models, property-based access patterns, and comprehensive type hints to make working with AWS Lambda resources more developer-friendly and maintainable.
|
|
109
|
+
|
|
110
|
+
**Key Features:**
|
|
111
|
+
|
|
112
|
+
`Data Models <https://simple-aws-lambda.readthedocs.io/en/latest/02-Data-Model/index.html>`_ - Transform raw boto3 responses into Pythonic objects with property-based access, following the Raw Data Storage, Property-Based Access, and Core Data Extraction patterns to ensure API resilience and clean interfaces.
|
|
113
|
+
|
|
114
|
+
`Better Client <https://simple-aws-lambda.readthedocs.io/en/latest/01-Quick-Start/index.html>`_ - Enhance the standard boto3 Lambda client with idempotent operations, automatic pagination, better error handling, and direct return of data model objects instead of raw dictionaries.
|
|
115
|
+
|
|
116
|
+
`Recipes <https://simple-aws-lambda.readthedocs.io/en/latest/03-Cleanup-Old-Layer-Versions/index.html>`_ - Provide high-level functions for common Lambda layer management tasks such as version cleanup, cross-account access management, and intelligent layer discovery that combine multiple API calls with best practices.
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
.. _install:
|
|
120
|
+
|
|
121
|
+
Install
|
|
122
|
+
------------------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
``simple_aws_lambda`` is released on PyPI, so all you need is to:
|
|
125
|
+
|
|
126
|
+
.. code-block:: console
|
|
127
|
+
|
|
128
|
+
$ pip install simple-aws-lambda
|
|
129
|
+
|
|
130
|
+
To upgrade to latest version:
|
|
131
|
+
|
|
132
|
+
.. code-block:: console
|
|
133
|
+
|
|
134
|
+
$ pip install --upgrade simple-aws-lambda
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
simple_aws_lambda/__init__.py,sha256=iwhKnzeBJLKxpRVjvzwiRE63_zNpIBfaKLITauVph-0,24
|
|
2
|
+
simple_aws_lambda/api.py,sha256=lcnw2LKEVWVPDsdy8Vjr9BCOMqd6-aZiiBzp5a6E320,761
|
|
3
|
+
simple_aws_lambda/client.py,sha256=Mu4OwobuS5NhJxi8R9sZrWR3gY0dUI-haHhOIImQiVQ,4216
|
|
4
|
+
simple_aws_lambda/model.py,sha256=jtepEmAnYzVuz-adFJ248idl81qHkUfGNdl_32vuysY,16085
|
|
5
|
+
simple_aws_lambda/paths.py,sha256=oL2TejRi1R5WntezHlZEtbOoEzTDrUwH3wnUIjB0YIU,1897
|
|
6
|
+
simple_aws_lambda/recipe.py,sha256=wqDGNQ2yCl102ALVU-lqgfprIRbSE6lncNOLlchYOD0,11219
|
|
7
|
+
simple_aws_lambda/vendor/__init__.py,sha256=O9CT1B2F-cVB8elT0EoCJbgkcffjvlmqteqavs4giDg,25
|
|
8
|
+
simple_aws_lambda/vendor/pytest_cov_helper.py,sha256=H2BJtFq3bwJHZtt-bxZ1yF9LnXHVDRZZ0NH-OvkRJdE,4063
|
|
9
|
+
simple_aws_lambda-0.3.1.dist-info/licenses/AUTHORS.rst,sha256=oo38V9AD_y57Ac69mKmiItWquV29SRi2aTCdKQo531A,767
|
|
10
|
+
simple_aws_lambda-0.3.1.dist-info/licenses/LICENSE.txt,sha256=Vc2EgX0hR-6Qw_RxS1IR-2qllqh4PiqSWFvrdnqZSDU,1084
|
|
11
|
+
simple_aws_lambda-0.3.1.dist-info/METADATA,sha256=icixOk-T_wg-R_idrHBrc-F85xqGAUy4uUjQ1zTfpSo,6516
|
|
12
|
+
simple_aws_lambda-0.3.1.dist-info/WHEEL,sha256=a-udL2m4V_6Yn8b-uKzWb12RjRxwwMo6ohYFM24tpaY,91
|
|
13
|
+
simple_aws_lambda-0.3.1.dist-info/top_level.txt,sha256=mbr6KY_Z2qyDT959NtI0sDtzWj-OPUaDXj-qu95C8Uk,18
|
|
14
|
+
simple_aws_lambda-0.3.1.dist-info/RECORD,,
|