Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for operationID #41

Merged
merged 1 commit into from
Aug 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/how_to_guides/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ How to guides
documenting.rst
error_handling.rst
headers_validation.rst
operation_id.rst
querystring_validation.rst
request_validation.rst
response_validation.rst
Expand Down
30 changes: 30 additions & 0 deletions docs/how_to_guides/operation_id.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
Operation IDs
=============

Operation IDs are used by certain OpenAPI clients when consuming the API spec. These are
by default generated using the method and function name:

.. code-block:: python

@app.get("/resource/")
async def resource():
...

@app.post("/resource/")
async def create_resource():
...

would by default assign Operation IDs of ``get_resource`` and ``post_create_resource``
respectively. To have more fine-grained control, part of the Operation ID can be
overridden:

.. code-block:: python

from quart_schema import operation_id

@app.post("/resource/")
@operation_id("make_resource")
async def create_resource():
...

which would assign an Operation ID of ``post_make_resource`` to the route.
3 changes: 2 additions & 1 deletion src/quart_schema/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
document_request,
document_response,
)
from .extension import deprecate, hide, QuartSchema, security_scheme, tag
from .extension import deprecate, hide, QuartSchema, operation_id, security_scheme, tag
from .mixins import SchemaValidationError
from .openapi import (
APIKeySecurityScheme,
Expand Down Expand Up @@ -49,6 +49,7 @@
"License",
"OAuth2SecurityScheme",
"OpenIdSecurityScheme",
"operation_id",
"QuartSchema",
"RequestSchemaValidationError",
"ResponseReturnValue",
Expand Down
30 changes: 29 additions & 1 deletion src/quart_schema/extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@

QUART_SCHEMA_HIDDEN_ATTRIBUTE = "_quart_schema_hidden"
QUART_SCHEMA_TAG_ATTRIBUTE = "_quart_schema_tag"
QUART_SCHEMA_OPERATION_ID_ATTRIBUTE = "_quart_schema_operation_id"
QUART_SCHEMA_SECURITY_ATTRIBUTE = "_quart_schema_security_tag"
QUART_SCHEMA_DEPRECATED = "_quart_schema_deprecated"
REF_PREFIX = "#/components/schemas/"
Expand Down Expand Up @@ -359,6 +360,25 @@ async def decorator(result: ResponseReturnValue) -> Response:
return decorator


def operation_id(operationid: str) -> Callable:
"""Override the operationId of the route.

This allows for overriding the operationId, which is normally calculated from the
function name. The HTTP method will still be prepended to the overridden name.

Arguments:
operationid: The operation ID to associate.

"""

def decorator(func: Callable) -> Callable:
setattr(func, QUART_SCHEMA_OPERATION_ID_ATTRIBUTE, str(operationid))

return func

return decorator


def tag(tags: Iterable[str]) -> Callable:
"""Add tag names to the route.

Expand Down Expand Up @@ -531,7 +551,15 @@ def _build_path(func: Callable, rule: Rule, app: Quart) -> Tuple[dict, dict]:
for method in rule.methods:
if method == "HEAD" or (method == "OPTIONS" and rule.provide_automatic_options): # type: ignore # noqa: E501
continue
paths[path][method.lower()] = operation_object

per_method_operation_object = operation_object.copy()

if getattr(func, QUART_SCHEMA_OPERATION_ID_ATTRIBUTE, None) is not None:
per_method_operation_object["operationId"] = f"{method.lower()}_{getattr(func, QUART_SCHEMA_OPERATION_ID_ATTRIBUTE)}"
else:
per_method_operation_object["operationId"] = f"{method.lower()}_{func.__name__}"

paths[path][method.lower()] = per_method_operation_object
return paths, components


Expand Down
5 changes: 4 additions & 1 deletion tests/test_openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
from quart_schema import (
deprecate,
QuartSchema,
operation_id,
security_scheme,
validate_headers,
validate_querystring,
validate_request,
validate_response,
)


@dataclass
class QueryItem:
count_le: Optional[int] = Field(description="count_le description")
Expand Down Expand Up @@ -61,6 +61,7 @@ async def read_item() -> Tuple[Result, int, Headers]:
@app.post("/")
@validate_request(Details)
@validate_response(Result, 201, Headers)
@operation_id("make_item")
@deprecate()
async def create_item() -> Tuple[Result, int, Headers]:
return Result(name="bob"), 201, Headers(x_name="jeff")
Expand All @@ -81,6 +82,7 @@ async def ws() -> None:
"summary": "Summary",
"description": "Multi-line\ndescription.\n\nThis is a new paragraph\n\n "
"And this is an indented codeblock.\n\nAnd another paragraph.",
"operationId": "get_read_item",
"parameters": [
{
"in": "query",
Expand Down Expand Up @@ -125,6 +127,7 @@ async def ws() -> None:
},
"post": {
"deprecated": True,
"operationId": "post_make_item",
"parameters": [],
"requestBody": {
"content": {
Expand Down