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.
@@ -0,0 +1 @@
1
+ # -*- coding: utf-8 -*-
@@ -0,0 +1,22 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ """
4
+ Public API. Users import from here::
5
+
6
+ from simple_aws_lambda.api import list_layers
7
+ import simple_aws_lambda.api as simple_aws_lambda
8
+ """
9
+
10
+ from .model import LatestMatchingLayerVersion
11
+ from .model import Layer
12
+ from .model import LayerIterproxy
13
+ from .model import LayerContent
14
+ from .model import LayerVersion
15
+ from .model import LayerVersionIterproxy
16
+ from .client import list_layers
17
+ from .client import list_layer_versions
18
+ from .client import get_layer_version
19
+ from .recipe import get_latest_layer_version
20
+ from .recipe import cleanup_old_layer_versions
21
+ from .recipe import grant_aws_account_or_aws_organization_lambda_layer_version_access
22
+ from .recipe import revoke_aws_account_or_aws_organization_lambda_layer_version_access
@@ -0,0 +1,140 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ """
4
+ Improve the original lambda boto3 API.
5
+
6
+ Ref:
7
+
8
+ - https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/lambda.html
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import typing as T
14
+
15
+ import botocore.exceptions
16
+ from func_args.api import OPT
17
+ from func_args.api import remove_optional
18
+
19
+ from .model import Layer
20
+ from .model import LayerVersion
21
+ from .model import LayerIterproxy
22
+ from .model import LayerVersionIterproxy
23
+
24
+ if T.TYPE_CHECKING: # pragma: no cover
25
+ from mypy_boto3_lambda.client import LambdaClient
26
+
27
+
28
+ def list_layers(
29
+ lambda_client: LambdaClient,
30
+ compatible_runtime: str = OPT,
31
+ compatible_architecture: str = OPT,
32
+ max_items: int = 9999,
33
+ page_size: int = 50,
34
+ ) -> LayerIterproxy:
35
+ """
36
+ List available AWS Lambda layers in the account.
37
+
38
+ Ref:
39
+
40
+ - `list_layers <https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/lambda/client/list_layers.html>`_
41
+ """
42
+
43
+ def func():
44
+ paginator = lambda_client.get_paginator("list_layers")
45
+ response_iterator = paginator.paginate(
46
+ **remove_optional(
47
+ CompatibleRuntime=compatible_runtime,
48
+ CompatibleArchitecture=compatible_architecture,
49
+ PaginationConfig={
50
+ "MaxItems": max_items,
51
+ "PageSize": page_size,
52
+ },
53
+ )
54
+ )
55
+ for response in response_iterator:
56
+ for _data in response.get("Layers", []):
57
+ yield Layer(_data=_data)
58
+
59
+ return LayerIterproxy(func())
60
+
61
+
62
+ def list_layer_versions(
63
+ lambda_client: LambdaClient,
64
+ layer_name: str,
65
+ compatible_runtime: str = OPT,
66
+ compatible_architecture: str = OPT,
67
+ max_items: int = 9999,
68
+ page_size: int = 50,
69
+ _sort_descending: bool = False,
70
+ ) -> LayerVersionIterproxy:
71
+ """
72
+ List all versions of Lambda layers in the account.
73
+
74
+ :param _sort_descending: if True, load all layer versions in to memory and
75
+ then sort them in memory in descending order by version number. This is
76
+ the default behavior of official AWS API. However, the test tool moto does not
77
+ implement it correctly (it returns in ascending order). So this parameter
78
+ is a workaround for moto only. You don't need to set this parameter
79
+ when you run against real AWS API.
80
+
81
+ .. note::
82
+
83
+ this API always returns the latest version first.
84
+
85
+ Ref:
86
+
87
+ - `list_layer_versions <https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/lambda/client/list_layer_versions.html>`_
88
+ """
89
+
90
+ def func():
91
+ paginator = lambda_client.get_paginator("list_layer_versions")
92
+ response_iterator = paginator.paginate(
93
+ **remove_optional(
94
+ LayerName=layer_name,
95
+ CompatibleRuntime=compatible_runtime,
96
+ CompatibleArchitecture=compatible_architecture,
97
+ PaginationConfig={
98
+ "MaxItems": max_items,
99
+ "PageSize": page_size,
100
+ },
101
+ )
102
+ )
103
+ for response in response_iterator:
104
+ for _data in response.get("LayerVersions", []):
105
+ yield LayerVersion(_data=_data)
106
+
107
+ if _sort_descending:
108
+ return LayerVersionIterproxy(
109
+ sorted(
110
+ func(),
111
+ key=lambda v: v.version,
112
+ reverse=True,
113
+ )
114
+ )
115
+ else:
116
+ return LayerVersionIterproxy(func())
117
+
118
+
119
+ def get_layer_version(
120
+ lambda_client: LambdaClient,
121
+ layer_name: str,
122
+ version_number: int,
123
+ ) -> LayerVersion | None:
124
+ """
125
+ Retrieve details for a specific Lambda layer version.
126
+
127
+ Ref:
128
+
129
+ - `get_layer_version <https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/lambda/client/get_layer_version.html>`_
130
+ """
131
+ try:
132
+ response = lambda_client.get_layer_version(
133
+ LayerName=layer_name,
134
+ VersionNumber=version_number,
135
+ )
136
+ return LayerVersion(_data=response)
137
+ except botocore.exceptions.ClientError as e:
138
+ if e.response["Error"]["Code"] == "ResourceNotFoundException":
139
+ return None
140
+ raise # pragma: no cover
@@ -0,0 +1,488 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ """
4
+ Base model implementation for AWS Lambda objects.
5
+
6
+ This module provides the foundational data models for the AWS Lambda library,
7
+ implementing common patterns for representing and interacting with AWS Lambda resources.
8
+ The models follow three key design patterns:
9
+
10
+ 1. **Raw Data Storage Pattern**:
11
+
12
+ All models store the original API response data in a `_data` attribute, treating the
13
+ API response schema as potentially unstable. Properties provide a stable interface
14
+ for accessing the underlying data, making the code more resilient to API changes.
15
+
16
+ 2. **Property-Based Access Pattern**:
17
+
18
+ All attributes are exposed through properties rather than direct instance attributes.
19
+ This approach allows for lazy loading, data validation, and type conversion while
20
+ maintaining a clean public interface.
21
+
22
+ 3. **Core Data Extraction Pattern**:
23
+
24
+ Each model implements a `core_data` property that returns a standardized, minimal
25
+ representation of the object. This provides a consistent way to access essential
26
+ information across different model types.
27
+
28
+ These models are designed to be instantiated by the API client methods, not directly
29
+ by users of the library. They provide a Pythonic interface to the JSON data returned
30
+ by the native boto3 AWS Lambda API.
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ import typing as T
36
+ import dataclasses
37
+ from datetime import datetime
38
+ from datetime import timezone
39
+
40
+ from func_args.api import T_KWARGS
41
+ from func_args.api import REQ
42
+ from iterproxy import IterProxy
43
+
44
+ if T.TYPE_CHECKING: # pragma: no cover
45
+ from mypy_boto3_lambda.type_defs import LayersListItemTypeDef
46
+ from mypy_boto3_lambda.type_defs import LayerVersionsListItemTypeDef
47
+ from mypy_boto3_lambda.type_defs import GetLayerVersionResponseTypeDef
48
+ from mypy_boto3_lambda.type_defs import LayerVersionContentOutputTypeDef
49
+
50
+
51
+ @dataclasses.dataclass(frozen=True)
52
+ class Base:
53
+ _data: dict[str, T.Any] = dataclasses.field(default=REQ)
54
+
55
+ @property
56
+ def core_data(self) -> T_KWARGS:
57
+ raise NotImplementedError
58
+
59
+
60
+ @dataclasses.dataclass(frozen=True)
61
+ class LatestMatchingLayerVersion(Base):
62
+ """
63
+ Represents the latest matching version of a Lambda layer.
64
+
65
+ Ref:
66
+
67
+ - `list_layers <https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/lambda/client/list_layers.html>`_
68
+
69
+ :param _data: Raw data structure as returned by AWS SDK:
70
+
71
+ .. code-block:: python
72
+
73
+ {
74
+ 'LayerVersionArn': 'string',
75
+ 'Version': 123,
76
+ 'Description': 'string',
77
+ ...
78
+ }
79
+ """
80
+
81
+ _data: LayerVersionsListItemTypeDef = dataclasses.field(default=REQ)
82
+
83
+ @property
84
+ def layer_version_arn(self) -> str | None:
85
+ """The ARN of the layer version."""
86
+ return self._data["LayerVersionArn"]
87
+
88
+ @property
89
+ def version(self) -> int | None:
90
+ """The version number of the layer."""
91
+ return self._data["Version"]
92
+
93
+ @property
94
+ def description(self) -> str | None:
95
+ """The description of the layer version."""
96
+ return self._data.get("Description")
97
+
98
+ @property
99
+ def created_date(self) -> str | None:
100
+ """The date that the layer version was created (ISO 8601 format)."""
101
+ return self._data.get("CreatedDate")
102
+
103
+ @property
104
+ def compatible_runtimes(self) -> list[str] | None:
105
+ """The layer's compatible runtimes."""
106
+ return self._data.get("CompatibleRuntimes")
107
+
108
+ @property
109
+ def license_info(self) -> str | None:
110
+ """The layer's software license."""
111
+ return self._data.get("LicenseInfo")
112
+
113
+ @property
114
+ def compatible_architectures(self) -> list[str] | None:
115
+ """A list of compatible instruction set architectures."""
116
+ return self._data.get("CompatibleArchitectures")
117
+
118
+ @property
119
+ def has_python_runtime(self) -> bool:
120
+ """Check if this layer version supports any Python runtime."""
121
+ if not self.compatible_runtimes:
122
+ return False
123
+ return any(runtime.startswith("python") for runtime in self.compatible_runtimes)
124
+
125
+ @property
126
+ def has_nodejs_runtime(self) -> bool:
127
+ """Check if this layer version supports any Node.js runtime."""
128
+ if not self.compatible_runtimes:
129
+ return False
130
+ return any(runtime.startswith("nodejs") for runtime in self.compatible_runtimes)
131
+
132
+ @property
133
+ def supports_arm64(self) -> bool:
134
+ """Check if this layer version supports ARM64 architecture."""
135
+ if not self.compatible_architectures:
136
+ return False
137
+ return "arm64" in self.compatible_architectures
138
+
139
+ @property
140
+ def supports_x86_64(self) -> bool:
141
+ """Check if this layer version supports x86_64 architecture."""
142
+ if not self.compatible_architectures:
143
+ return False
144
+ return "x86_64" in self.compatible_architectures
145
+
146
+ @property
147
+ def core_data(self) -> T_KWARGS:
148
+ """Extract core data for standardized representation."""
149
+ return {
150
+ "layer_version_arn": self.layer_version_arn,
151
+ "version": self.version,
152
+ "description": self.description,
153
+ "created_date": self.created_date,
154
+ "compatible_runtimes": self.compatible_runtimes,
155
+ "compatible_architectures": self.compatible_architectures,
156
+ }
157
+
158
+
159
+ @dataclasses.dataclass(frozen=True)
160
+ class Layer(Base):
161
+ """
162
+ Represents an AWS Lambda layer.
163
+
164
+ Ref:
165
+
166
+ - `list_layers <https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/lambda/client/list_layers.html>`_
167
+
168
+ :param _data: Raw data structure as returned by AWS SDK:
169
+
170
+ .. code-block:: python
171
+
172
+ {
173
+ 'LayerName': 'string',
174
+ 'LayerArn': 'string',
175
+ 'LatestMatchingVersion': {
176
+ ...
177
+ }
178
+ }
179
+ """
180
+
181
+ _data: LayersListItemTypeDef = dataclasses.field(default=REQ)
182
+
183
+ @property
184
+ def layer_name(self) -> str:
185
+ """The name of the layer."""
186
+ return self._data["LayerName"]
187
+
188
+ @property
189
+ def layer_arn(self) -> str:
190
+ """The Amazon Resource Name (ARN) of the layer."""
191
+ return self._data["LayerArn"]
192
+
193
+ @property
194
+ def latest_matching_version(self) -> LatestMatchingLayerVersion | None:
195
+ """The latest matching version of the layer."""
196
+ version_data = self._data.get("LatestMatchingVersion")
197
+ if version_data:
198
+ return LatestMatchingLayerVersion(_data=version_data)
199
+ return None
200
+
201
+ @property
202
+ def has_latest_version(self) -> bool:
203
+ """Check if this layer has a latest matching version."""
204
+ return self.latest_matching_version is not None
205
+
206
+ @property
207
+ def core_data(self) -> T_KWARGS:
208
+ """Extract core data for standardized representation."""
209
+ return {
210
+ "layer_name": self.layer_name,
211
+ "layer_arn": self.layer_arn,
212
+ }
213
+
214
+
215
+ class LayerIterproxy(IterProxy[Layer]):
216
+ """
217
+ Iterator proxy for collections of Layer objects with enhanced iteration capabilities.
218
+ """
219
+
220
+
221
+ @dataclasses.dataclass(frozen=True)
222
+ class LayerContent(Base):
223
+ """
224
+ Represents the content details of a Lambda layer version.
225
+
226
+ :param _data: Raw data structure as returned by AWS SDK:
227
+
228
+ .. code-block:: python
229
+
230
+ {
231
+ 'Location': 'string',
232
+ 'CodeSha256': 'string',
233
+ 'CodeSize': 123,
234
+ 'SigningProfileVersionArn': 'string',
235
+ 'SigningJobArn': 'string'
236
+ }
237
+ """
238
+
239
+ _data: LayerVersionContentOutputTypeDef = dataclasses.field()
240
+
241
+ @property
242
+ def location(self) -> str | None:
243
+ """A link to the layer archive in Amazon S3 that is valid for 10 minutes."""
244
+ return self._data.get("Location")
245
+
246
+ @property
247
+ def code_sha256(self) -> str | None:
248
+ """The SHA-256 hash of the layer archive."""
249
+ return self._data.get("CodeSha256")
250
+
251
+ @property
252
+ def code_size(self) -> int | None:
253
+ """The size of the layer archive in bytes."""
254
+ return self._data.get("CodeSize")
255
+
256
+ @property
257
+ def signing_profile_version_arn(self) -> str | None:
258
+ """The Amazon Resource Name (ARN) for a signing profile version."""
259
+ return self._data.get("SigningProfileVersionArn")
260
+
261
+ @property
262
+ def signing_job_arn(self) -> str | None:
263
+ """The Amazon Resource Name (ARN) of a signing job."""
264
+ return self._data.get("SigningJobArn")
265
+
266
+ @property
267
+ def core_data(self) -> T_KWARGS:
268
+ """Extract core data for standardized representation."""
269
+ return {
270
+ "location": self.location,
271
+ "code_sha256": self.code_sha256,
272
+ "code_size": self.code_size,
273
+ }
274
+
275
+
276
+ @dataclasses.dataclass(frozen=True)
277
+ class LayerVersion(Base):
278
+ """
279
+ Represents a specific version of an AWS Lambda layer.
280
+
281
+ Ref:
282
+
283
+ - `list_layer_versions <https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/lambda/client/list_layer_versions.html>`_
284
+ - `get_layer_version <https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/lambda/client/get_layer_version.html>`_
285
+
286
+ :param _data: Raw data structure as returned by AWS SDK:
287
+
288
+ .. code-block:: python
289
+
290
+ {
291
+ 'LayerVersionArn': 'string',
292
+ 'Version': 123,
293
+ 'Description': 'string',
294
+ ...
295
+ }
296
+ # or (from get_layer_version)
297
+ {
298
+ 'Content': {
299
+ 'Location': 'string',
300
+ 'CodeSha256': 'string',
301
+ 'CodeSize': 123,
302
+ 'SigningProfileVersionArn': 'string',
303
+ 'SigningJobArn': 'string'
304
+ },
305
+ 'LayerArn': 'string',
306
+ 'LayerVersionArn': 'string',
307
+ 'Description': 'string',
308
+ ...
309
+ }
310
+ """
311
+
312
+ # fmt: off
313
+ _data: LayerVersionsListItemTypeDef | GetLayerVersionResponseTypeDef = dataclasses.field(default=REQ)
314
+ # fmt: on
315
+
316
+ @property
317
+ def layer_version_arn(self) -> str:
318
+ """The ARN of the layer version."""
319
+ return self._data["LayerVersionArn"]
320
+
321
+ @property
322
+ def version(self) -> int:
323
+ """The version number of the layer."""
324
+ return self._data["Version"]
325
+
326
+ @property
327
+ def description(self) -> str | None:
328
+ """The description of the layer version."""
329
+ return self._data.get("Description")
330
+
331
+ @property
332
+ def created_date(self) -> str | None:
333
+ """The date that the layer version was created (ISO 8601 format)."""
334
+ return self._data.get("CreatedDate")
335
+
336
+ @property
337
+ def compatible_runtimes(self) -> list[str] | None:
338
+ """The layer's compatible runtimes."""
339
+ return self._data.get("CompatibleRuntimes")
340
+
341
+ @property
342
+ def license_info(self) -> str | None:
343
+ """The layer's software license."""
344
+ return self._data.get("LicenseInfo")
345
+
346
+ @property
347
+ def compatible_architectures(self) -> list[str] | None:
348
+ """A list of compatible instruction set architectures."""
349
+ return self._data.get("CompatibleArchitectures")
350
+
351
+ @property
352
+ def layer_arn(self) -> str | None:
353
+ """The ARN of the layer (only available from get_layer_version)."""
354
+ return self._data.get("LayerArn")
355
+
356
+ # Content-related properties (only available from get_layer_version)
357
+ @property
358
+ def content(self) -> LayerContent | None:
359
+ """Information about the layer's deployment package (only from get_layer_version)."""
360
+ content_data = self._data.get("Content")
361
+ if content_data:
362
+ return LayerContent(_data=content_data)
363
+ return None
364
+
365
+ @property
366
+ def created_datetime(self) -> datetime:
367
+ """
368
+ Convert the created_date string to a datetime object.
369
+ """
370
+ dt = datetime.fromisoformat(self.created_date)
371
+ if dt.tzinfo is None:
372
+ dt = dt.replace(tzinfo=timezone.utc)
373
+ return dt
374
+
375
+ @property
376
+ def layer_name(self) -> str:
377
+ """Extract the layer name from the layer version ARN."""
378
+ return self.layer_version_arn.split(":")[-2]
379
+
380
+ @property
381
+ def has_content_details(self) -> bool:
382
+ """Check if this layer version includes content details (from get_layer_version)."""
383
+ return self.content is not None
384
+
385
+ # Computed properties for runtime and architecture checks
386
+ @property
387
+ def has_python_runtime(self) -> bool:
388
+ """Check if this layer version supports any Python runtime."""
389
+ if not self.compatible_runtimes:
390
+ return False
391
+ return any(runtime.startswith("python") for runtime in self.compatible_runtimes)
392
+
393
+ @property
394
+ def has_nodejs_runtime(self) -> bool:
395
+ """Check if this layer version supports any Node.js runtime."""
396
+ if not self.compatible_runtimes:
397
+ return False
398
+ return any(runtime.startswith("nodejs") for runtime in self.compatible_runtimes)
399
+
400
+ @property
401
+ def has_java_runtime(self) -> bool:
402
+ """Check if this layer version supports any Java runtime."""
403
+ if not self.compatible_runtimes:
404
+ return False
405
+ return any(runtime.startswith("java") for runtime in self.compatible_runtimes)
406
+
407
+ @property
408
+ def has_dotnet_runtime(self) -> bool:
409
+ """Check if this layer version supports any .NET runtime."""
410
+ if not self.compatible_runtimes:
411
+ return False
412
+ return any(
413
+ runtime.startswith("dotnetcore") or runtime.startswith("dotnet")
414
+ for runtime in self.compatible_runtimes
415
+ )
416
+
417
+ @property
418
+ def has_go_runtime(self) -> bool:
419
+ """Check if this layer version supports Go runtime."""
420
+ if not self.compatible_runtimes:
421
+ return False
422
+ return any(runtime.startswith("go") for runtime in self.compatible_runtimes)
423
+
424
+ @property
425
+ def has_ruby_runtime(self) -> bool:
426
+ """Check if this layer version supports any Ruby runtime."""
427
+ if not self.compatible_runtimes:
428
+ return False
429
+ return any(runtime.startswith("ruby") for runtime in self.compatible_runtimes)
430
+
431
+ @property
432
+ def has_provided_runtime(self) -> bool:
433
+ """Check if this layer version supports provided runtime."""
434
+ if not self.compatible_runtimes:
435
+ return False
436
+ return any(
437
+ runtime.startswith("provided") for runtime in self.compatible_runtimes
438
+ )
439
+
440
+ @property
441
+ def supports_arm64(self) -> bool:
442
+ """Check if this layer version supports ARM64 architecture."""
443
+ if not self.compatible_architectures:
444
+ return False
445
+ return "arm64" in self.compatible_architectures
446
+
447
+ @property
448
+ def supports_x86_64(self) -> bool:
449
+ """Check if this layer version supports x86_64 architecture."""
450
+ if not self.compatible_architectures:
451
+ return False
452
+ return "x86_64" in self.compatible_architectures
453
+
454
+ @property
455
+ def supports_multi_arch(self) -> bool:
456
+ """Check if this layer version supports multiple architectures."""
457
+ return self.supports_arm64 and self.supports_x86_64
458
+
459
+ @property
460
+ def runtime_count(self) -> int:
461
+ """Number of compatible runtimes."""
462
+ return len(self.compatible_runtimes) if self.compatible_runtimes else 0
463
+
464
+ @property
465
+ def architecture_count(self) -> int:
466
+ """Number of compatible architectures."""
467
+ return (
468
+ len(self.compatible_architectures) if self.compatible_architectures else 0
469
+ )
470
+
471
+ @property
472
+ def core_data(self) -> T_KWARGS:
473
+ """Extract core data for standardized representation."""
474
+ return {
475
+ "layer_version_arn": self.layer_version_arn,
476
+ "layer_arn": self.layer_arn,
477
+ "version": self.version,
478
+ "description": self.description,
479
+ "created_date": self.created_date,
480
+ "compatible_runtimes": self.compatible_runtimes,
481
+ "compatible_architectures": self.compatible_architectures,
482
+ }
483
+
484
+
485
+ class LayerVersionIterproxy(IterProxy[LayerVersion]):
486
+ """
487
+ Iterator proxy for collections of LayerVersion objects with enhanced iteration capabilities.
488
+ """