aws-lambda-powertools 3.0.1a9__py3-none-any.whl → 3.0.1.dev1__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.
Files changed (46) hide show
  1. aws_lambda_powertools/event_handler/api_gateway.py +54 -227
  2. aws_lambda_powertools/event_handler/appsync.py +97 -343
  3. aws_lambda_powertools/event_handler/bedrock_agent.py +0 -121
  4. aws_lambda_powertools/event_handler/exceptions.py +1 -1
  5. aws_lambda_powertools/event_handler/middlewares/openapi_validation.py +1 -1
  6. aws_lambda_powertools/event_handler/openapi/encoders.py +70 -94
  7. aws_lambda_powertools/event_handler/openapi/exceptions.py +0 -12
  8. aws_lambda_powertools/event_handler/openapi/models.py +6 -39
  9. aws_lambda_powertools/event_handler/openapi/swagger_ui/html.py +4 -1
  10. aws_lambda_powertools/event_handler/util.py +2 -55
  11. aws_lambda_powertools/logging/utils.py +5 -5
  12. aws_lambda_powertools/metrics/provider/cloudwatch_emf/metric_properties.py +0 -1
  13. aws_lambda_powertools/shared/cookies.py +2 -2
  14. aws_lambda_powertools/shared/dynamodb_deserializer.py +0 -13
  15. aws_lambda_powertools/shared/version.py +1 -1
  16. aws_lambda_powertools/tracing/tracer.py +2 -4
  17. aws_lambda_powertools/utilities/batch/base.py +4 -13
  18. aws_lambda_powertools/utilities/data_classes/__init__.py +0 -4
  19. aws_lambda_powertools/utilities/data_classes/cognito_user_pool_event.py +1 -260
  20. aws_lambda_powertools/utilities/data_classes/kafka_event.py +2 -2
  21. aws_lambda_powertools/utilities/data_classes/vpc_lattice.py +0 -7
  22. aws_lambda_powertools/utilities/data_masking/base.py +4 -4
  23. aws_lambda_powertools/utilities/feature_flags/appconfig.py +2 -21
  24. aws_lambda_powertools/utilities/idempotency/base.py +11 -8
  25. aws_lambda_powertools/utilities/idempotency/idempotency.py +2 -22
  26. aws_lambda_powertools/utilities/idempotency/persistence/base.py +1 -4
  27. aws_lambda_powertools/utilities/parameters/secrets.py +1 -1
  28. aws_lambda_powertools/utilities/parser/envelopes/base.py +6 -5
  29. aws_lambda_powertools/utilities/parser/functions.py +1 -40
  30. aws_lambda_powertools/utilities/parser/models/__init__.py +0 -6
  31. aws_lambda_powertools/utilities/parser/models/apigw.py +1 -12
  32. aws_lambda_powertools/utilities/parser/models/apigwv2.py +1 -7
  33. aws_lambda_powertools/utilities/parser/models/s3.py +2 -4
  34. aws_lambda_powertools/utilities/parser/parser.py +8 -8
  35. aws_lambda_powertools/utilities/validation/base.py +2 -27
  36. aws_lambda_powertools/utilities/validation/validator.py +4 -49
  37. {aws_lambda_powertools-3.0.1a9.dist-info → aws_lambda_powertools-3.0.1.dev1.dist-info}/METADATA +2 -13
  38. {aws_lambda_powertools-3.0.1a9.dist-info → aws_lambda_powertools-3.0.1.dev1.dist-info}/RECORD +40 -46
  39. aws_lambda_powertools/event_handler/graphql_appsync/__init__.py +0 -0
  40. aws_lambda_powertools/event_handler/graphql_appsync/_registry.py +0 -78
  41. aws_lambda_powertools/event_handler/graphql_appsync/base.py +0 -160
  42. aws_lambda_powertools/event_handler/graphql_appsync/exceptions.py +0 -10
  43. aws_lambda_powertools/event_handler/graphql_appsync/router.py +0 -62
  44. aws_lambda_powertools/utilities/data_classes/code_deploy_lifecycle_hook_event.py +0 -13
  45. {aws_lambda_powertools-3.0.1a9.dist-info → aws_lambda_powertools-3.0.1.dev1.dist-info}/LICENSE +0 -0
  46. {aws_lambda_powertools-3.0.1a9.dist-info → aws_lambda_powertools-3.0.1.dev1.dist-info}/WHEEL +0 -0
@@ -14,12 +14,10 @@ from http import HTTPStatus
14
14
  from pathlib import Path
15
15
  from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, Mapping, Match, Pattern, Sequence, TypeVar, cast
16
16
 
17
- from typing_extensions import override
18
-
19
17
  from aws_lambda_powertools.event_handler import content_types
20
18
  from aws_lambda_powertools.event_handler.exceptions import NotFoundError, ServiceError
21
19
  from aws_lambda_powertools.event_handler.openapi.constants import DEFAULT_API_VERSION, DEFAULT_OPENAPI_VERSION
22
- from aws_lambda_powertools.event_handler.openapi.exceptions import RequestValidationError, SchemaValidationError
20
+ from aws_lambda_powertools.event_handler.openapi.exceptions import RequestValidationError
23
21
  from aws_lambda_powertools.event_handler.openapi.types import (
24
22
  COMPONENT_REF_PREFIX,
25
23
  METHODS_WITH_BODY,
@@ -29,13 +27,7 @@ from aws_lambda_powertools.event_handler.openapi.types import (
29
27
  validation_error_definition,
30
28
  validation_error_response_definition,
31
29
  )
32
- from aws_lambda_powertools.event_handler.util import (
33
- _FrozenDict,
34
- _FrozenListDict,
35
- _validate_openapi_security_parameters,
36
- extract_origin_header,
37
- )
38
- from aws_lambda_powertools.shared.cookies import Cookie
30
+ from aws_lambda_powertools.event_handler.util import _FrozenDict, extract_origin_header
39
31
  from aws_lambda_powertools.shared.functions import powertools_dev_is_set
40
32
  from aws_lambda_powertools.shared.json_encoder import Encoder
41
33
  from aws_lambda_powertools.utilities.data_classes import (
@@ -104,22 +96,20 @@ class CORSConfig:
104
96
  Examples
105
97
  --------
106
98
 
107
- Simple CORS example using the default permissive CORS, note that this should only be used during early prototyping.
99
+ Simple cors example using the default permissive cors, not this should only be used during early prototyping
108
100
 
109
101
  ```python
110
- from aws_lambda_powertools.event_handler.api_gateway import (
111
- APIGatewayRestResolver, CORSConfig
112
- )
102
+ from aws_lambda_powertools.event_handler import APIGatewayRestResolver
113
103
 
114
- app = APIGatewayRestResolver(cors=CORSConfig())
104
+ app = APIGatewayRestResolver()
115
105
 
116
- @app.get("/my/path")
106
+ @app.get("/my/path", cors=True)
117
107
  def with_cors():
118
108
  return {"message": "Foo"}
119
109
  ```
120
110
 
121
111
  Using a custom CORSConfig where `with_cors` used the custom provided CORSConfig and `without_cors`
122
- do not include any CORS headers.
112
+ do not include any cors headers.
123
113
 
124
114
  ```python
125
115
  from aws_lambda_powertools.event_handler.api_gateway import (
@@ -176,12 +166,9 @@ class CORSConfig:
176
166
  allow_credentials: bool
177
167
  A boolean value that sets the value of `Access-Control-Allow-Credentials`
178
168
  """
179
-
180
169
  self._allowed_origins = [allow_origin]
181
-
182
170
  if extra_origins:
183
171
  self._allowed_origins.extend(extra_origins)
184
-
185
172
  self.allow_headers = set(self._REQUIRED_HEADERS + (allow_headers or []))
186
173
  self.expose_headers = expose_headers or []
187
174
  self.max_age = max_age
@@ -202,42 +189,17 @@ class CORSConfig:
202
189
  # The origin matched an allowed origin, so return the CORS headers
203
190
  headers = {
204
191
  "Access-Control-Allow-Origin": origin,
205
- "Access-Control-Allow-Headers": CORSConfig.build_allow_methods(self.allow_headers),
192
+ "Access-Control-Allow-Headers": ",".join(sorted(self.allow_headers)),
206
193
  }
207
194
 
208
195
  if self.expose_headers:
209
196
  headers["Access-Control-Expose-Headers"] = ",".join(self.expose_headers)
210
197
  if self.max_age is not None:
211
198
  headers["Access-Control-Max-Age"] = str(self.max_age)
212
- if origin != "*" and self.allow_credentials is True:
199
+ if self.allow_credentials is True:
213
200
  headers["Access-Control-Allow-Credentials"] = "true"
214
201
  return headers
215
202
 
216
- def allowed_origin(self, extracted_origin: str) -> str | None:
217
- if extracted_origin in self._allowed_origins:
218
- return extracted_origin
219
- if extracted_origin is not None and "*" in self._allowed_origins:
220
- return "*"
221
-
222
- return None
223
-
224
- @staticmethod
225
- def build_allow_methods(methods: set[str]) -> str:
226
- """Build sorted comma delimited methods for Access-Control-Allow-Methods header
227
-
228
- Parameters
229
- ----------
230
- methods : set[str]
231
- Set of HTTP Methods
232
-
233
- Returns
234
- -------
235
- set[str]
236
- Formatted string with all HTTP Methods allowed for CORS e.g., `GET, OPTIONS`
237
-
238
- """
239
- return ",".join(sorted(methods))
240
-
241
203
 
242
204
  class Response(Generic[ResponseT]):
243
205
  """Response data class that provides greater control over what is returned from the proxy event"""
@@ -298,17 +260,16 @@ class Route:
298
260
  func: Callable,
299
261
  cors: bool,
300
262
  compress: bool,
301
- cache_control: str | None = None,
302
- summary: str | None = None,
303
- description: str | None = None,
304
- responses: dict[int, OpenAPIResponse] | None = None,
305
- response_description: str | None = None,
306
- tags: list[str] | None = None,
307
- operation_id: str | None = None,
308
- include_in_schema: bool = True,
309
- security: list[dict[str, list[str]]] | None = None,
310
- openapi_extensions: dict[str, Any] | None = None,
311
- middlewares: list[Callable[..., Response]] | None = None,
263
+ cache_control: str | None,
264
+ summary: str | None,
265
+ description: str | None,
266
+ responses: dict[int, OpenAPIResponse] | None,
267
+ response_description: str | None,
268
+ tags: list[str] | None,
269
+ operation_id: str | None,
270
+ include_in_schema: bool,
271
+ security: list[dict[str, list[str]]] | None,
272
+ middlewares: list[Callable[..., Response]] | None,
312
273
  ):
313
274
  """
314
275
 
@@ -345,8 +306,6 @@ class Route:
345
306
  Whether or not to include this route in the OpenAPI schema
346
307
  security: list[dict[str, list[str]]], optional
347
308
  The OpenAPI security for this route
348
- openapi_extensions: dict[str, Any], optional
349
- Additional OpenAPI extensions as a dictionary.
350
309
  middlewares: list[Callable[..., Response]] | None
351
310
  The list of route middlewares to be called in order.
352
311
  """
@@ -370,7 +329,6 @@ class Route:
370
329
  self.tags = tags or []
371
330
  self.include_in_schema = include_in_schema
372
331
  self.security = security
373
- self.openapi_extensions = openapi_extensions
374
332
  self.middlewares = middlewares or []
375
333
  self.operation_id = operation_id or self._generate_operation_id()
376
334
 
@@ -522,10 +480,6 @@ class Route:
522
480
  if self.security:
523
481
  operation["security"] = self.security
524
482
 
525
- # Add OpenAPI extensions if present
526
- if self.openapi_extensions:
527
- operation.update(self.openapi_extensions)
528
-
529
483
  # Add the parameters to the OpenAPI operation
530
484
  if parameters:
531
485
  all_parameters = {(param["in"], param["name"]): param for param in parameters}
@@ -808,10 +762,7 @@ class ResponseBuilder(Generic[ResponseEventT]):
808
762
  def _add_cors(self, event: ResponseEventT, cors: CORSConfig):
809
763
  """Update headers to include the configured Access-Control headers"""
810
764
  extracted_origin_header = extract_origin_header(event.resolved_headers_field)
811
-
812
- origin = cors.allowed_origin(extracted_origin_header)
813
- if origin is not None:
814
- self.response.headers.update(cors.to_dict(origin))
765
+ self.response.headers.update(cors.to_dict(extracted_origin_header))
815
766
 
816
767
  def _add_cache_control(self, cache_control: str):
817
768
  """Set the specified cache control headers for 200 http responses. For non-200 `no-cache` is used."""
@@ -922,7 +873,6 @@ class BaseRouter(ABC):
922
873
  operation_id: str | None = None,
923
874
  include_in_schema: bool = True,
924
875
  security: list[dict[str, list[str]]] | None = None,
925
- openapi_extensions: dict[str, Any] | None = None,
926
876
  middlewares: list[Callable[..., Any]] | None = None,
927
877
  ):
928
878
  raise NotImplementedError()
@@ -982,7 +932,6 @@ class BaseRouter(ABC):
982
932
  operation_id: str | None = None,
983
933
  include_in_schema: bool = True,
984
934
  security: list[dict[str, list[str]]] | None = None,
985
- openapi_extensions: dict[str, Any] | None = None,
986
935
  middlewares: list[Callable[..., Any]] | None = None,
987
936
  ):
988
937
  """Get route decorator with GET `method`
@@ -1021,7 +970,6 @@ class BaseRouter(ABC):
1021
970
  operation_id,
1022
971
  include_in_schema,
1023
972
  security,
1024
- openapi_extensions,
1025
973
  middlewares,
1026
974
  )
1027
975
 
@@ -1039,7 +987,6 @@ class BaseRouter(ABC):
1039
987
  operation_id: str | None = None,
1040
988
  include_in_schema: bool = True,
1041
989
  security: list[dict[str, list[str]]] | None = None,
1042
- openapi_extensions: dict[str, Any] | None = None,
1043
990
  middlewares: list[Callable[..., Any]] | None = None,
1044
991
  ):
1045
992
  """Post route decorator with POST `method`
@@ -1079,7 +1026,6 @@ class BaseRouter(ABC):
1079
1026
  operation_id,
1080
1027
  include_in_schema,
1081
1028
  security,
1082
- openapi_extensions,
1083
1029
  middlewares,
1084
1030
  )
1085
1031
 
@@ -1097,7 +1043,6 @@ class BaseRouter(ABC):
1097
1043
  operation_id: str | None = None,
1098
1044
  include_in_schema: bool = True,
1099
1045
  security: list[dict[str, list[str]]] | None = None,
1100
- openapi_extensions: dict[str, Any] | None = None,
1101
1046
  middlewares: list[Callable[..., Any]] | None = None,
1102
1047
  ):
1103
1048
  """Put route decorator with PUT `method`
@@ -1137,7 +1082,6 @@ class BaseRouter(ABC):
1137
1082
  operation_id,
1138
1083
  include_in_schema,
1139
1084
  security,
1140
- openapi_extensions,
1141
1085
  middlewares,
1142
1086
  )
1143
1087
 
@@ -1155,7 +1099,6 @@ class BaseRouter(ABC):
1155
1099
  operation_id: str | None = None,
1156
1100
  include_in_schema: bool = True,
1157
1101
  security: list[dict[str, list[str]]] | None = None,
1158
- openapi_extensions: dict[str, Any] | None = None,
1159
1102
  middlewares: list[Callable[..., Any]] | None = None,
1160
1103
  ):
1161
1104
  """Delete route decorator with DELETE `method`
@@ -1194,7 +1137,6 @@ class BaseRouter(ABC):
1194
1137
  operation_id,
1195
1138
  include_in_schema,
1196
1139
  security,
1197
- openapi_extensions,
1198
1140
  middlewares,
1199
1141
  )
1200
1142
 
@@ -1212,7 +1154,6 @@ class BaseRouter(ABC):
1212
1154
  operation_id: str | None = None,
1213
1155
  include_in_schema: bool = True,
1214
1156
  security: list[dict[str, list[str]]] | None = None,
1215
- openapi_extensions: dict[str, Any] | None = None,
1216
1157
  middlewares: list[Callable] | None = None,
1217
1158
  ):
1218
1159
  """Patch route decorator with PATCH `method`
@@ -1254,7 +1195,6 @@ class BaseRouter(ABC):
1254
1195
  operation_id,
1255
1196
  include_in_schema,
1256
1197
  security,
1257
- openapi_extensions,
1258
1198
  middlewares,
1259
1199
  )
1260
1200
 
@@ -1272,7 +1212,6 @@ class BaseRouter(ABC):
1272
1212
  operation_id: str | None = None,
1273
1213
  include_in_schema: bool = True,
1274
1214
  security: list[dict[str, list[str]]] | None = None,
1275
- openapi_extensions: dict[str, Any] | None = None,
1276
1215
  middlewares: list[Callable] | None = None,
1277
1216
  ):
1278
1217
  """Head route decorator with HEAD `method`
@@ -1313,7 +1252,6 @@ class BaseRouter(ABC):
1313
1252
  operation_id,
1314
1253
  include_in_schema,
1315
1254
  security,
1316
- openapi_extensions,
1317
1255
  middlewares,
1318
1256
  )
1319
1257
 
@@ -1434,6 +1372,7 @@ def _registered_api_adapter(app: ApiGatewayResolver, next_middleware: Callable[.
1434
1372
  """
1435
1373
  route_args: dict = app.context.get("_route_args", {})
1436
1374
  logger.debug(f"Calling API Route Handler: {route_args}")
1375
+
1437
1376
  return app._to_response(next_middleware(**route_args))
1438
1377
 
1439
1378
 
@@ -1534,7 +1473,6 @@ class ApiGatewayResolver(BaseRouter):
1534
1473
  license_info: License | None = None,
1535
1474
  security_schemes: dict[str, SecurityScheme] | None = None,
1536
1475
  security: list[dict[str, list[str]]] | None = None,
1537
- openapi_extensions: dict[str, Any] | None = None,
1538
1476
  ) -> OpenAPI:
1539
1477
  """
1540
1478
  Returns the OpenAPI schema as a pydantic model.
@@ -1565,8 +1503,6 @@ class ApiGatewayResolver(BaseRouter):
1565
1503
  A declaration of the security schemes available to be used in the specification.
1566
1504
  security: list[dict[str, list[str]]], optional
1567
1505
  A declaration of which security mechanisms are applied globally across the API.
1568
- openapi_extensions: Dict[str, Any], optional
1569
- Additional OpenAPI extensions as a dictionary.
1570
1506
 
1571
1507
  Returns
1572
1508
  -------
@@ -1599,15 +1535,11 @@ class ApiGatewayResolver(BaseRouter):
1599
1535
 
1600
1536
  info.update({field: value for field, value in optional_fields.items() if value})
1601
1537
 
1602
- if not isinstance(openapi_extensions, dict):
1603
- openapi_extensions = {}
1604
-
1605
1538
  output: dict[str, Any] = {
1606
1539
  "openapi": openapi_version,
1607
1540
  "info": info,
1608
1541
  "servers": self._get_openapi_servers(servers),
1609
1542
  "security": self._get_openapi_security(security, security_schemes),
1610
- **openapi_extensions,
1611
1543
  }
1612
1544
 
1613
1545
  components: dict[str, dict[str, Any]] = {}
@@ -1628,16 +1560,6 @@ class ApiGatewayResolver(BaseRouter):
1628
1560
 
1629
1561
  # Add routes to the OpenAPI schema
1630
1562
  for route in all_routes:
1631
-
1632
- if route.security and not _validate_openapi_security_parameters(
1633
- security=route.security,
1634
- security_schemes=security_schemes,
1635
- ):
1636
- raise SchemaValidationError(
1637
- "Security configuration was not found in security_schemas or security_schema was not defined. "
1638
- "See: https://docs.powertools.aws.dev/lambda/python/latest/core/event_handler/api_gateway/#security-schemes",
1639
- )
1640
-
1641
1563
  if not route.include_in_schema:
1642
1564
  continue
1643
1565
 
@@ -1683,11 +1605,12 @@ class ApiGatewayResolver(BaseRouter):
1683
1605
  if not security:
1684
1606
  return None
1685
1607
 
1686
- if not _validate_openapi_security_parameters(security=security, security_schemes=security_schemes):
1687
- raise SchemaValidationError(
1688
- "Security configuration was not found in security_schemas or security_schema was not defined. "
1689
- "See: https://docs.powertools.aws.dev/lambda/python/latest/core/event_handler/api_gateway/#security-schemes",
1690
- )
1608
+ if not security_schemes:
1609
+ raise ValueError("security_schemes must be provided if security is provided")
1610
+
1611
+ # Check if all keys in security are present in the security_schemes
1612
+ if any(key not in security_schemes for sec in security for key in sec):
1613
+ raise ValueError("Some security schemes not found in security_schemes")
1691
1614
 
1692
1615
  return security
1693
1616
 
@@ -1718,7 +1641,6 @@ class ApiGatewayResolver(BaseRouter):
1718
1641
  license_info: License | None = None,
1719
1642
  security_schemes: dict[str, SecurityScheme] | None = None,
1720
1643
  security: list[dict[str, list[str]]] | None = None,
1721
- openapi_extensions: dict[str, Any] | None = None,
1722
1644
  ) -> str:
1723
1645
  """
1724
1646
  Returns the OpenAPI schema as a JSON serializable dict
@@ -1749,8 +1671,6 @@ class ApiGatewayResolver(BaseRouter):
1749
1671
  A declaration of the security schemes available to be used in the specification.
1750
1672
  security: list[dict[str, list[str]]], optional
1751
1673
  A declaration of which security mechanisms are applied globally across the API.
1752
- openapi_extensions: Dict[str, Any], optional
1753
- Additional OpenAPI extensions as a dictionary.
1754
1674
 
1755
1675
  Returns
1756
1676
  -------
@@ -1773,7 +1693,6 @@ class ApiGatewayResolver(BaseRouter):
1773
1693
  license_info=license_info,
1774
1694
  security_schemes=security_schemes,
1775
1695
  security=security,
1776
- openapi_extensions=openapi_extensions,
1777
1696
  ),
1778
1697
  by_alias=True,
1779
1698
  exclude_none=True,
@@ -1801,7 +1720,6 @@ class ApiGatewayResolver(BaseRouter):
1801
1720
  security: list[dict[str, list[str]]] | None = None,
1802
1721
  oauth2_config: OAuth2Config | None = None,
1803
1722
  persist_authorization: bool = False,
1804
- openapi_extensions: dict[str, Any] | None = None,
1805
1723
  ):
1806
1724
  """
1807
1725
  Returns the OpenAPI schema as a JSON serializable dict
@@ -1844,8 +1762,6 @@ class ApiGatewayResolver(BaseRouter):
1844
1762
  The OAuth2 configuration for the Swagger UI.
1845
1763
  persist_authorization: bool, optional
1846
1764
  Whether to persist authorization data on browser close/refresh.
1847
- openapi_extensions: dict[str, Any], optional
1848
- Additional OpenAPI extensions as a dictionary.
1849
1765
  """
1850
1766
  from aws_lambda_powertools.event_handler.openapi.compat import model_json
1851
1767
  from aws_lambda_powertools.event_handler.openapi.models import Server
@@ -1895,7 +1811,6 @@ class ApiGatewayResolver(BaseRouter):
1895
1811
  license_info=license_info,
1896
1812
  security_schemes=security_schemes,
1897
1813
  security=security,
1898
- openapi_extensions=openapi_extensions,
1899
1814
  )
1900
1815
 
1901
1816
  # The .replace('</', '<\\/') part is necessary to prevent a potential issue where the JSON string contains
@@ -1920,6 +1835,7 @@ class ApiGatewayResolver(BaseRouter):
1920
1835
 
1921
1836
  body = generate_swagger_html(
1922
1837
  escaped_spec,
1838
+ f"{base_path}{path}",
1923
1839
  swagger_js,
1924
1840
  swagger_css,
1925
1841
  swagger_base_url,
@@ -1948,7 +1864,6 @@ class ApiGatewayResolver(BaseRouter):
1948
1864
  operation_id: str | None = None,
1949
1865
  include_in_schema: bool = True,
1950
1866
  security: list[dict[str, list[str]]] | None = None,
1951
- openapi_extensions: dict[str, Any] | None = None,
1952
1867
  middlewares: list[Callable[..., Any]] | None = None,
1953
1868
  ):
1954
1869
  """Route decorator includes parameter `method`"""
@@ -1976,7 +1891,6 @@ class ApiGatewayResolver(BaseRouter):
1976
1891
  operation_id,
1977
1892
  include_in_schema,
1978
1893
  security,
1979
- openapi_extensions,
1980
1894
  middlewares,
1981
1895
  )
1982
1896
 
@@ -2002,36 +1916,6 @@ class ApiGatewayResolver(BaseRouter):
2002
1916
  def resolve(self, event, context) -> dict[str, Any]:
2003
1917
  """Resolves the response based on the provide event and decorator routes
2004
1918
 
2005
- ## Internals
2006
-
2007
- Request processing chain is triggered by a Route object being called _(`_call_route` -> `__call__`)_:
2008
-
2009
- 1. **When a route is matched**
2010
- 1.1. Exception handlers _(if any exception bubbled up and caught)_
2011
- 1.2. Global middlewares _(before, and after on the way back)_
2012
- 1.3. Path level middleware _(before, and after on the way back)_
2013
- 1.4. Middleware adapter to ensure Response is homogenous (_registered_api_adapter)
2014
- 1.5. Run actual route
2015
- 2. **When a route is NOT matched**
2016
- 2.1. Exception handlers _(if any exception bubbled up and caught)_
2017
- 2.2. Global middlewares _(before, and after on the way back)_
2018
- 2.3. Path level middleware _(before, and after on the way back)_
2019
- 2.4. Middleware adapter to ensure Response is homogenous (_registered_api_adapter)
2020
- 2.5. Run 404 route handler
2021
- 3. **When a route is a pre-flight CORS (often not matched)**
2022
- 3.1. Exception handlers _(if any exception bubbled up and caught)_
2023
- 3.2. Global middlewares _(before, and after on the way back)_
2024
- 3.3. Path level middleware _(before, and after on the way back)_
2025
- 3.4. Middleware adapter to ensure Response is homogenous (_registered_api_adapter)
2026
- 3.5. Return 204 with appropriate CORS headers
2027
- 4. **When a route is matched with Data Validation enabled**
2028
- 4.1. Exception handlers _(if any exception bubbled up and caught)_
2029
- 4.2. Data Validation middleware _(before, and after on the way back)_
2030
- 4.3. Global middlewares _(before, and after on the way back)_
2031
- 4.4. Path level middleware _(before, and after on the way back)_
2032
- 4.5. Middleware adapter to ensure Response is homogenous (_registered_api_adapter)
2033
- 4.6. Run actual route
2034
-
2035
1919
  Parameters
2036
1920
  ----------
2037
1921
  event: dict[str, Any]
@@ -2155,9 +2039,7 @@ class ApiGatewayResolver(BaseRouter):
2155
2039
  method = self.current_event.http_method.upper()
2156
2040
  path = self._remove_prefix(self.current_event.path)
2157
2041
 
2158
- registered_routes = self._static_routes + self._dynamic_routes
2159
-
2160
- for route in registered_routes:
2042
+ for route in self._static_routes + self._dynamic_routes:
2161
2043
  if method != route.method:
2162
2044
  continue
2163
2045
  match_results: Match | None = route.rule.match(path)
@@ -2169,7 +2051,8 @@ class ApiGatewayResolver(BaseRouter):
2169
2051
  route_keys = self._convert_matches_into_route_keys(match_results)
2170
2052
  return self._call_route(route, route_keys) # pass fn args
2171
2053
 
2172
- return self._handle_not_found(method=method, path=path)
2054
+ logger.debug(f"No match found for path {path} and method {method}")
2055
+ return self._not_found(method)
2173
2056
 
2174
2057
  def _remove_prefix(self, path: str) -> str:
2175
2058
  """Remove the configured prefix from the path"""
@@ -2207,65 +2090,36 @@ class ApiGatewayResolver(BaseRouter):
2207
2090
 
2208
2091
  return path.startswith(prefix + "/")
2209
2092
 
2210
- def _handle_not_found(self, method: str, path: str) -> ResponseBuilder:
2093
+ def _not_found(self, method: str) -> ResponseBuilder:
2211
2094
  """Called when no matching route was found and includes support for the cors preflight response"""
2212
- logger.debug(f"No match found for path {path} and method {method}")
2213
-
2214
- def not_found_handler():
2215
- """Route handler for 404s
2216
-
2217
- It handles in the following order:
2218
-
2219
- 1. Pre-flight CORS requests (OPTIONS)
2220
- 2. Detects and calls custom HTTP 404 handler
2221
- 3. Returns standard 404 along with CORS headers
2222
-
2223
- Returns
2224
- -------
2225
- Response
2226
- HTTP 404 response
2227
- """
2228
- _headers: dict[str, Any] = {}
2229
-
2230
- # Pre-flight request? Return immediately to avoid browser error
2231
- if self._cors and method == "OPTIONS":
2232
- logger.debug("Pre-flight request detected. Returning CORS with empty response")
2233
- _headers["Access-Control-Allow-Methods"] = CORSConfig.build_allow_methods(self._cors_methods)
2234
-
2235
- return Response(status_code=204, content_type=None, headers=_headers, body="")
2095
+ headers = {}
2096
+ if self._cors:
2097
+ logger.debug("CORS is enabled, updating headers.")
2098
+ extracted_origin_header = extract_origin_header(self.current_event.resolved_headers_field)
2099
+ headers.update(self._cors.to_dict(extracted_origin_header))
2100
+
2101
+ if method == "OPTIONS":
2102
+ logger.debug("Pre-flight request detected. Returning CORS with null response")
2103
+ headers["Access-Control-Allow-Methods"] = ",".join(sorted(self._cors_methods))
2104
+ return ResponseBuilder(
2105
+ response=Response(status_code=204, content_type=None, headers=headers, body=""),
2106
+ serializer=self._serializer,
2107
+ )
2236
2108
 
2237
- # Customer registered 404 route? Call it.
2238
- custom_not_found_handler = self._lookup_exception_handler(NotFoundError)
2239
- if custom_not_found_handler:
2240
- return custom_not_found_handler(NotFoundError())
2109
+ handler = self._lookup_exception_handler(NotFoundError)
2110
+ if handler:
2111
+ return self._response_builder_class(response=handler(NotFoundError()), serializer=self._serializer)
2241
2112
 
2242
- # No CORS and no custom 404 fn? Default response
2243
- return Response(
2113
+ return self._response_builder_class(
2114
+ response=Response(
2244
2115
  status_code=HTTPStatus.NOT_FOUND.value,
2245
2116
  content_type=content_types.APPLICATION_JSON,
2246
- headers=_headers,
2117
+ headers=headers,
2247
2118
  body={"statusCode": HTTPStatus.NOT_FOUND.value, "message": "Not found"},
2248
- )
2249
-
2250
- # We create a route to trigger entire request chain (middleware+exception handlers)
2251
- route = Route(
2252
- rule=self._compile_regex(r".*"),
2253
- method=method,
2254
- path=path,
2255
- func=not_found_handler,
2256
- cors=self._cors_enabled,
2257
- compress=False,
2119
+ ),
2120
+ serializer=self._serializer,
2258
2121
  )
2259
2122
 
2260
- # Add matched Route reference into the Resolver context
2261
- self.append_context(_route=route, _path=path)
2262
-
2263
- # Kick-off request chain:
2264
- # -> exception_handlers()
2265
- # --> middlewares()
2266
- # ---> not_found_route()
2267
- return self._call_route(route=route, route_arguments={})
2268
-
2269
2123
  def _call_route(self, route: Route, route_arguments: dict[str, str]) -> ResponseBuilder:
2270
2124
  """Actually call the matching route with any provided keyword arguments."""
2271
2125
  try:
@@ -2490,7 +2344,6 @@ class Router(BaseRouter):
2490
2344
  operation_id: str | None = None,
2491
2345
  include_in_schema: bool = True,
2492
2346
  security: list[dict[str, list[str]]] | None = None,
2493
- openapi_extensions: dict[str, Any] | None = None,
2494
2347
  middlewares: list[Callable[..., Any]] | None = None,
2495
2348
  ):
2496
2349
  def register_route(func: Callable):
@@ -2498,8 +2351,6 @@ class Router(BaseRouter):
2498
2351
  methods = (method,) if isinstance(method, str) else tuple(method)
2499
2352
  frozen_responses = _FrozenDict(responses) if responses else None
2500
2353
  frozen_tags = frozenset(tags) if tags else None
2501
- frozen_security = _FrozenListDict(security) if security else None
2502
- fronzen_openapi_extensions = _FrozenDict(openapi_extensions) if openapi_extensions else None
2503
2354
 
2504
2355
  route_key = (
2505
2356
  rule,
@@ -2514,8 +2365,7 @@ class Router(BaseRouter):
2514
2365
  frozen_tags,
2515
2366
  operation_id,
2516
2367
  include_in_schema,
2517
- frozen_security,
2518
- fronzen_openapi_extensions,
2368
+ security,
2519
2369
  )
2520
2370
 
2521
2371
  # Collate Middleware for routes
@@ -2596,7 +2446,6 @@ class APIGatewayRestResolver(ApiGatewayResolver):
2596
2446
  operation_id: str | None = None,
2597
2447
  include_in_schema: bool = True,
2598
2448
  security: list[dict[str, list[str]]] | None = None,
2599
- openapi_extensions: dict[str, Any] | None = None,
2600
2449
  middlewares: list[Callable[..., Any]] | None = None,
2601
2450
  ):
2602
2451
  # NOTE: see #1552 for more context.
@@ -2614,7 +2463,6 @@ class APIGatewayRestResolver(ApiGatewayResolver):
2614
2463
  operation_id,
2615
2464
  include_in_schema,
2616
2465
  security,
2617
- openapi_extensions,
2618
2466
  middlewares,
2619
2467
  )
2620
2468
 
@@ -2676,24 +2524,3 @@ class ALBResolver(ApiGatewayResolver):
2676
2524
  def _get_base_path(self) -> str:
2677
2525
  # ALB doesn't have a stage variable, so we just return an empty string
2678
2526
  return ""
2679
-
2680
- @override
2681
- def _to_response(self, result: dict | tuple | Response) -> Response:
2682
- """Convert the route's result to a Response
2683
-
2684
- ALB requires a non-null body otherwise it converts as HTTP 5xx
2685
-
2686
- 3 main result types are supported:
2687
-
2688
- - Dict[str, Any]: Rest api response with just the Dict to json stringify and content-type is set to
2689
- application/json
2690
- - Tuple[dict, int]: Same dict handling as above but with the option of including a status code
2691
- - Response: returned as is, and allows for more flexibility
2692
- """
2693
-
2694
- # NOTE: Minor override for early return on Response with null body for ALB
2695
- if isinstance(result, Response) and result.body is None:
2696
- logger.debug("ALB doesn't allow None responses; converting to empty string")
2697
- result.body = ""
2698
-
2699
- return super()._to_response(result)