Skip to content

Commit 4c6d196

Browse files
feat(event_handler): mark API operation as deprecated for OpenAPI documentation (#5732)
* Add deprecated parameter with default to BaseRouter.get * Add parameter with default to BaseRouter.route * Pass deprecated param from .get() into .route() * Add param and pass along for post, put, delete, patch, head * Add param and pass along for ApiGatewayRestResolver.route * Ditto for Route.__init__, use when creating operation metadata * Add param and pass along in ApiGatewayResolver.route * Add param and pass along in Router.route, workaround for include_router * Functional tests * Formatting * Refactor to use defaultdict * Move deprecated operation tests into separate test case * Simplify test case * Put 'deprecated' param before 'middlewares' * Remove workaround * Add test case for deprecated POST operation * Add 'deprecated' param to BedrockAgentResolver methods * Small changes + trigger pipeline --------- Co-authored-by: Leandro Damascena <[email protected]>
1 parent b5c17e8 commit 4c6d196

File tree

8 files changed

+89
-16
lines changed

8 files changed

+89
-16
lines changed

aws_lambda_powertools/event_handler/api_gateway.py

+26-2
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@ def __init__(
309309
include_in_schema: bool = True,
310310
security: list[dict[str, list[str]]] | None = None,
311311
openapi_extensions: dict[str, Any] | None = None,
312+
deprecated: bool = False,
312313
middlewares: list[Callable[..., Response]] | None = None,
313314
):
314315
"""
@@ -348,6 +349,8 @@ def __init__(
348349
The OpenAPI security for this route
349350
openapi_extensions: dict[str, Any], optional
350351
Additional OpenAPI extensions as a dictionary.
352+
deprecated: bool
353+
Whether or not to mark this route as deprecated in the OpenAPI schema
351354
middlewares: list[Callable[..., Response]] | None
352355
The list of route middlewares to be called in order.
353356
"""
@@ -374,6 +377,7 @@ def __init__(
374377
self.openapi_extensions = openapi_extensions
375378
self.middlewares = middlewares or []
376379
self.operation_id = operation_id or self._generate_operation_id()
380+
self.deprecated = deprecated
377381

378382
# _middleware_stack_built is used to ensure the middleware stack is only built once.
379383
self._middleware_stack_built = False
@@ -670,6 +674,9 @@ def _openapi_operation_metadata(self, operation_ids: set[str]) -> dict[str, Any]
670674
operation_ids.add(self.operation_id)
671675
operation["operationId"] = self.operation_id
672676

677+
# Mark as deprecated if necessary
678+
operation["deprecated"] = self.deprecated or None
679+
673680
return operation
674681

675682
@staticmethod
@@ -924,6 +931,7 @@ def route(
924931
include_in_schema: bool = True,
925932
security: list[dict[str, list[str]]] | None = None,
926933
openapi_extensions: dict[str, Any] | None = None,
934+
deprecated: bool = False,
927935
middlewares: list[Callable[..., Any]] | None = None,
928936
) -> Callable[[AnyCallableT], AnyCallableT]:
929937
raise NotImplementedError()
@@ -984,6 +992,7 @@ def get(
984992
include_in_schema: bool = True,
985993
security: list[dict[str, list[str]]] | None = None,
986994
openapi_extensions: dict[str, Any] | None = None,
995+
deprecated: bool = False,
987996
middlewares: list[Callable[..., Any]] | None = None,
988997
) -> Callable[[AnyCallableT], AnyCallableT]:
989998
"""Get route decorator with GET `method`
@@ -1023,6 +1032,7 @@ def lambda_handler(event, context):
10231032
include_in_schema,
10241033
security,
10251034
openapi_extensions,
1035+
deprecated,
10261036
middlewares,
10271037
)
10281038

@@ -1041,6 +1051,7 @@ def post(
10411051
include_in_schema: bool = True,
10421052
security: list[dict[str, list[str]]] | None = None,
10431053
openapi_extensions: dict[str, Any] | None = None,
1054+
deprecated: bool = False,
10441055
middlewares: list[Callable[..., Any]] | None = None,
10451056
) -> Callable[[AnyCallableT], AnyCallableT]:
10461057
"""Post route decorator with POST `method`
@@ -1081,6 +1092,7 @@ def lambda_handler(event, context):
10811092
include_in_schema,
10821093
security,
10831094
openapi_extensions,
1095+
deprecated,
10841096
middlewares,
10851097
)
10861098

@@ -1099,6 +1111,7 @@ def put(
10991111
include_in_schema: bool = True,
11001112
security: list[dict[str, list[str]]] | None = None,
11011113
openapi_extensions: dict[str, Any] | None = None,
1114+
deprecated: bool = False,
11021115
middlewares: list[Callable[..., Any]] | None = None,
11031116
) -> Callable[[AnyCallableT], AnyCallableT]:
11041117
"""Put route decorator with PUT `method`
@@ -1139,6 +1152,7 @@ def lambda_handler(event, context):
11391152
include_in_schema,
11401153
security,
11411154
openapi_extensions,
1155+
deprecated,
11421156
middlewares,
11431157
)
11441158

@@ -1157,6 +1171,7 @@ def delete(
11571171
include_in_schema: bool = True,
11581172
security: list[dict[str, list[str]]] | None = None,
11591173
openapi_extensions: dict[str, Any] | None = None,
1174+
deprecated: bool = False,
11601175
middlewares: list[Callable[..., Any]] | None = None,
11611176
) -> Callable[[AnyCallableT], AnyCallableT]:
11621177
"""Delete route decorator with DELETE `method`
@@ -1196,6 +1211,7 @@ def lambda_handler(event, context):
11961211
include_in_schema,
11971212
security,
11981213
openapi_extensions,
1214+
deprecated,
11991215
middlewares,
12001216
)
12011217

@@ -1214,6 +1230,7 @@ def patch(
12141230
include_in_schema: bool = True,
12151231
security: list[dict[str, list[str]]] | None = None,
12161232
openapi_extensions: dict[str, Any] | None = None,
1233+
deprecated: bool = False,
12171234
middlewares: list[Callable] | None = None,
12181235
) -> Callable[[AnyCallableT], AnyCallableT]:
12191236
"""Patch route decorator with PATCH `method`
@@ -1256,6 +1273,7 @@ def lambda_handler(event, context):
12561273
include_in_schema,
12571274
security,
12581275
openapi_extensions,
1276+
deprecated,
12591277
middlewares,
12601278
)
12611279

@@ -1274,6 +1292,7 @@ def head(
12741292
include_in_schema: bool = True,
12751293
security: list[dict[str, list[str]]] | None = None,
12761294
openapi_extensions: dict[str, Any] | None = None,
1295+
deprecated: bool = False,
12771296
middlewares: list[Callable] | None = None,
12781297
) -> Callable[[AnyCallableT], AnyCallableT]:
12791298
"""Head route decorator with HEAD `method`
@@ -1315,6 +1334,7 @@ def lambda_handler(event, context):
13151334
include_in_schema,
13161335
security,
13171336
openapi_extensions,
1337+
deprecated,
13181338
middlewares,
13191339
)
13201340

@@ -1629,7 +1649,6 @@ def get_openapi_schema(
16291649

16301650
# Add routes to the OpenAPI schema
16311651
for route in all_routes:
1632-
16331652
if route.security and not _validate_openapi_security_parameters(
16341653
security=route.security,
16351654
security_schemes=security_schemes,
@@ -1694,7 +1713,6 @@ def _get_openapi_security(
16941713

16951714
@staticmethod
16961715
def _determine_openapi_version(openapi_version: str):
1697-
16981716
# Pydantic V2 has no support for OpenAPI schema 3.0
16991717
if not openapi_version.startswith("3.1"):
17001718
warnings.warn(
@@ -1950,6 +1968,7 @@ def route(
19501968
include_in_schema: bool = True,
19511969
security: list[dict[str, list[str]]] | None = None,
19521970
openapi_extensions: dict[str, Any] | None = None,
1971+
deprecated: bool = False,
19531972
middlewares: list[Callable[..., Any]] | None = None,
19541973
) -> Callable[[AnyCallableT], AnyCallableT]:
19551974
"""Route decorator includes parameter `method`"""
@@ -1978,6 +1997,7 @@ def register_resolver(func: AnyCallableT) -> AnyCallableT:
19781997
include_in_schema,
19791998
security,
19801999
openapi_extensions,
2000+
deprecated,
19812001
middlewares,
19822002
)
19832003

@@ -2492,6 +2512,7 @@ def route(
24922512
include_in_schema: bool = True,
24932513
security: list[dict[str, list[str]]] | None = None,
24942514
openapi_extensions: dict[str, Any] | None = None,
2515+
deprecated: bool = False,
24952516
middlewares: list[Callable[..., Any]] | None = None,
24962517
) -> Callable[[AnyCallableT], AnyCallableT]:
24972518
def register_route(func: AnyCallableT) -> AnyCallableT:
@@ -2517,6 +2538,7 @@ def register_route(func: AnyCallableT) -> AnyCallableT:
25172538
include_in_schema,
25182539
frozen_security,
25192540
fronzen_openapi_extensions,
2541+
deprecated,
25202542
)
25212543

25222544
# Collate Middleware for routes
@@ -2598,6 +2620,7 @@ def route(
25982620
include_in_schema: bool = True,
25992621
security: list[dict[str, list[str]]] | None = None,
26002622
openapi_extensions: dict[str, Any] | None = None,
2623+
deprecated: bool = False,
26012624
middlewares: list[Callable[..., Any]] | None = None,
26022625
) -> Callable[[AnyCallableT], AnyCallableT]:
26032626
# NOTE: see #1552 for more context.
@@ -2616,6 +2639,7 @@ def route(
26162639
include_in_schema,
26172640
security,
26182641
openapi_extensions,
2642+
deprecated,
26192643
middlewares,
26202644
)
26212645

aws_lambda_powertools/event_handler/bedrock_agent.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,9 @@ def get( # type: ignore[override]
108108
tags: list[str] | None = None,
109109
operation_id: str | None = None,
110110
include_in_schema: bool = True,
111+
deprecated: bool = False,
111112
middlewares: list[Callable[..., Any]] | None = None,
112113
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
113-
114114
openapi_extensions = None
115115
security = None
116116

@@ -128,6 +128,7 @@ def get( # type: ignore[override]
128128
include_in_schema,
129129
security,
130130
openapi_extensions,
131+
deprecated,
131132
middlewares,
132133
)
133134

@@ -146,6 +147,7 @@ def post( # type: ignore[override]
146147
tags: list[str] | None = None,
147148
operation_id: str | None = None,
148149
include_in_schema: bool = True,
150+
deprecated: bool = False,
149151
middlewares: list[Callable[..., Any]] | None = None,
150152
):
151153
openapi_extensions = None
@@ -165,6 +167,7 @@ def post( # type: ignore[override]
165167
include_in_schema,
166168
security,
167169
openapi_extensions,
170+
deprecated,
168171
middlewares,
169172
)
170173

@@ -183,6 +186,7 @@ def put( # type: ignore[override]
183186
tags: list[str] | None = None,
184187
operation_id: str | None = None,
185188
include_in_schema: bool = True,
189+
deprecated: bool = False,
186190
middlewares: list[Callable[..., Any]] | None = None,
187191
):
188192
openapi_extensions = None
@@ -202,6 +206,7 @@ def put( # type: ignore[override]
202206
include_in_schema,
203207
security,
204208
openapi_extensions,
209+
deprecated,
205210
middlewares,
206211
)
207212

@@ -220,6 +225,7 @@ def patch( # type: ignore[override]
220225
tags: list[str] | None = None,
221226
operation_id: str | None = None,
222227
include_in_schema: bool = True,
228+
deprecated: bool = False,
223229
middlewares: list[Callable] | None = None,
224230
):
225231
openapi_extensions = None
@@ -239,6 +245,7 @@ def patch( # type: ignore[override]
239245
include_in_schema,
240246
security,
241247
openapi_extensions,
248+
deprecated,
242249
middlewares,
243250
)
244251

@@ -257,6 +264,7 @@ def delete( # type: ignore[override]
257264
tags: list[str] | None = None,
258265
operation_id: str | None = None,
259266
include_in_schema: bool = True,
267+
deprecated: bool = False,
260268
middlewares: list[Callable[..., Any]] | None = None,
261269
):
262270
openapi_extensions = None
@@ -276,6 +284,7 @@ def delete( # type: ignore[override]
276284
include_in_schema,
277285
security,
278286
openapi_extensions,
287+
deprecated,
279288
middlewares,
280289
)
281290

aws_lambda_powertools/metrics/provider/cloudwatch_emf/cloudwatch.py

-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
from aws_lambda_powertools.shared.functions import resolve_env_var_choice
2525
from aws_lambda_powertools.warnings import PowertoolsUserWarning
2626

27-
2827
if TYPE_CHECKING:
2928
from aws_lambda_powertools.metrics.provider.cloudwatch_emf.types import CloudWatchEMFOutput
3029
from aws_lambda_powertools.metrics.types import MetricNameUnitResolution
@@ -295,8 +294,6 @@ def add_dimension(self, name: str, value: str) -> None:
295294

296295
self.dimension_set[name] = value
297296

298-
299-
300297
def add_metadata(self, key: str, value: Any) -> None:
301298
"""Adds high cardinal metadata for metrics object
302299

aws_lambda_powertools/utilities/parser/models/apigw_websocket.py

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ class APIGatewayWebSocketEventIdentity(BaseModel):
99
source_ip: IPvAnyNetwork = Field(alias="sourceIp")
1010
user_agent: Optional[str] = Field(None, alias="userAgent")
1111

12+
1213
class APIGatewayWebSocketEventRequestContextBase(BaseModel):
1314
extended_request_id: str = Field(alias="extendedRequestId")
1415
request_time: str = Field(alias="requestTime")

docs/core/event_handler/_openapi_customization_operations.md

+1
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ Here's a breakdown of various customizable fields:
1313
| `tags` | `List[str]` | Tags are a way to categorize and group endpoints within the API documentation. They can help organize the operations by resources or other heuristic. |
1414
| `operation_id` | `str` | A unique identifier for the operation, which can be used for referencing this operation in documentation or code. This ID must be unique across all operations described in the API. |
1515
| `include_in_schema` | `bool` | A boolean value that determines whether or not this operation should be included in the OpenAPI schema. Setting it to `False` can hide the endpoint from generated documentation and schema exports, which might be useful for private or experimental endpoints. |
16+
| `deprecated` | `bool` | A boolean value that determines whether or not this operation should be marked as deprecated in the OpenAPI schema. |

tests/functional/event_handler/_pydantic/test_openapi_params.py

+41
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def handler():
4444
get = path.get
4545
assert get.summary == "GET /"
4646
assert get.operationId == "handler__get"
47+
assert get.deprecated is None
4748

4849
assert get.responses is not None
4950
assert 200 in get.responses.keys()
@@ -388,6 +389,46 @@ def handler(user: Annotated[User, Body(description="This is a user")]):
388389
assert request_body.content[JSON_CONTENT_TYPE].schema_.description == "This is a user"
389390

390391

392+
def test_openapi_with_deprecated_operations():
393+
app = APIGatewayRestResolver()
394+
395+
@app.get("/", deprecated=True)
396+
def _get():
397+
raise NotImplementedError()
398+
399+
@app.post("/", deprecated=True)
400+
def _post():
401+
raise NotImplementedError()
402+
403+
schema = app.get_openapi_schema()
404+
405+
get = schema.paths["/"].get
406+
assert get.deprecated is True
407+
408+
post = schema.paths["/"].post
409+
assert post.deprecated is True
410+
411+
412+
def test_openapi_without_deprecated_operations():
413+
app = APIGatewayRestResolver()
414+
415+
@app.get("/")
416+
def _get():
417+
raise NotImplementedError()
418+
419+
@app.post("/", deprecated=False)
420+
def _post():
421+
raise NotImplementedError()
422+
423+
schema = app.get_openapi_schema()
424+
425+
get = schema.paths["/"].get
426+
assert get.deprecated is None
427+
428+
post = schema.paths["/"].post
429+
assert post.deprecated is None
430+
431+
391432
def test_openapi_with_excluded_operations():
392433
app = APIGatewayRestResolver()
393434

0 commit comments

Comments
 (0)