Skip to content

Commit

Permalink
feat(layered-include_in_schema): Add include_in_schema option on Rout…
Browse files Browse the repository at this point in the history
…ers and/or Controllers
  • Loading branch information
Alc-Alc committed Sep 11, 2023
1 parent 7a5cf26 commit 1cf19ca
Show file tree
Hide file tree
Showing 11 changed files with 151 additions and 10 deletions.
1 change: 1 addition & 0 deletions docs/usage/applications.rst
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ Parameters that support layering are:
* :ref:`etag <usage/responses:etag>`
* :doc:`exception_handlers </usage/exceptions>`
* :doc:`guards </usage/security/guards>`
* :ref:`include_in_schema <usage/openapi:configuring schema generation on a route handler>`
* :doc:`middleware </usage/middleware/index>`
* :ref:`opt <handler_opts>`
* :ref:`response_class <usage/responses:custom responses>`
Expand Down
2 changes: 1 addition & 1 deletion litestar/_openapi/path_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def create_path_item(
for http_method, handler_tuple in route.route_handler_map.items():
route_handler, _ = handler_tuple

if route_handler.include_in_schema:
if route_handler.resolve_include_in_schema():
handler_fields = route_handler.signature_model._fields if route_handler.signature_model else {}
parameters = (
create_parameter_for_handler(
Expand Down
10 changes: 9 additions & 1 deletion litestar/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ class Litestar(Router):
"csrf_config",
"event_emitter",
"get_logger",
"include_in_schema",
"logger",
"logging_config",
"multipart_form_part_limit",
Expand Down Expand Up @@ -183,6 +184,7 @@ def __init__(
event_emitter_backend: type[BaseEventEmitterBackend] = SimpleEventEmitter,
exception_handlers: ExceptionHandlersMap | None = None,
guards: OptionalSequence[Guard] | None = None,
include_in_schema: bool | EmptyType | None = Empty,
listeners: OptionalSequence[EventListener] | None = None,
logging_config: BaseLoggingConfig | EmptyType | None = Empty,
middleware: OptionalSequence[Middleware] | None = None,
Expand Down Expand Up @@ -248,6 +250,7 @@ def __init__(
:class:`BaseEventEmitterBackend <.events.emitter.BaseEventEmitterBackend>`.
exception_handlers: A mapping of status codes and/or exception types to handler functions.
guards: A sequence of :class:`Guard <.types.Guard>` callables.
include_in_schema: A boolean flag dictating whether the route handler should be documented in the OpenAPI schema.
lifespan: A list of callables returning async context managers, wrapping the lifespan of the ASGI application
listeners: A sequence of :class:`EventListener <.events.listener.EventListener>`.
logging_config: A subclass of :class:`BaseLoggingConfig <.logging.config.BaseLoggingConfig>`.
Expand Down Expand Up @@ -326,6 +329,7 @@ def __init__(
event_emitter_backend=event_emitter_backend,
exception_handlers=exception_handlers or {},
guards=list(guards or []),
include_in_schema=include_in_schema,
lifespan=lifespan or [],
listeners=list(listeners or []),
logging_config=cast("BaseLoggingConfig | None", logging_config),
Expand Down Expand Up @@ -394,6 +398,7 @@ def __init__(
self.websocket_class = config.websocket_class or WebSocket
self.debug = config.debug
self.pdb_on_exception: bool = config.pdb_on_exception
self.include_in_schema = include_in_schema

if self.pdb_on_exception:
warn_pdb_on_exception()
Expand Down Expand Up @@ -423,6 +428,7 @@ def __init__(
tags=config.tags,
type_encoders=config.type_encoders,
type_decoders=config.type_decoders,
include_in_schema=config.include_in_schema,
)

for route_handler in config.route_handlers:
Expand Down Expand Up @@ -812,7 +818,9 @@ def update_openapi_schema(self) -> None:
for route in self.routes:
if (
isinstance(route, HTTPRoute)
and any(route_handler.include_in_schema for route_handler, _ in route.route_handler_map.values())
and any(
route_handler.resolve_include_in_schema() for route_handler, _ in route.route_handler_map.values()
)
and (route.path_format or "/") not in self._openapi_schema.paths
):
path_item, created_operation_ids = create_path_item(
Expand Down
2 changes: 2 additions & 0 deletions litestar/config/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ class AppConfig:
"""A dictionary that maps handler functions to status codes and/or exception types."""
guards: list[Guard] = field(default_factory=list)
"""A list of :class:`Guard <.types.Guard>` callables."""
include_in_schema: bool | EmptyType | None = field(default=Empty)
"""A boolean flag dictating whether the route handler should be documented in the OpenAPI schema"""
lifespan: list[Callable[[Litestar], AbstractAsyncContextManager] | AbstractAsyncContextManager] = field(
default_factory=list
)
Expand Down
6 changes: 6 additions & 0 deletions litestar/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class Controller:
"etag",
"exception_handlers",
"guards",
"include_in_schema",
"middleware",
"opt",
"owner",
Expand Down Expand Up @@ -106,6 +107,8 @@ class Controller:
"""A map of handler functions to status codes and/or exception types."""
guards: OptionalSequence[Guard]
"""A sequence of :class:`Guard <.types.Guard>` callables."""
include_in_schema: bool | EmptyType | None
"""A boolean flag dictating whether the route handler should be documented in the OpenAPI schema"""
middleware: OptionalSequence[Middleware]
"""A sequence of :class:`Middleware <.types.Middleware>`."""
opt: Mapping[str, Any] | None
Expand Down Expand Up @@ -168,6 +171,9 @@ def __init__(self, owner: Router) -> None:
if not hasattr(self, "return_dto"):
self.return_dto = Empty

if not hasattr(self, "include_in_schema"):
self.include_in_schema = Empty

for key in self.__slots__:
if not hasattr(self, key):
setattr(self, key, None)
Expand Down
22 changes: 21 additions & 1 deletion litestar/handlers/http_handlers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ class HTTPRouteHandler(BaseRouteHandler):
"_resolved_after_response",
"_resolved_before_request",
"_response_handler_mapping",
"_resolved_include_in_schema",
"after_request",
"after_response",
"background",
Expand Down Expand Up @@ -143,7 +144,7 @@ def __init__(
content_media_type: str | None = None,
deprecated: bool = False,
description: str | None = None,
include_in_schema: bool = True,
include_in_schema: bool | EmptyType | None = Empty,
operation_class: type[Operation] = Operation,
operation_id: str | OperationIDCreator | None = None,
raises: Sequence[type[HTTPException]] | None = None,
Expand Down Expand Up @@ -278,6 +279,7 @@ def __init__(
self._resolved_after_response: AsyncCallable | None | EmptyType = Empty
self._resolved_before_request: AsyncCallable | None | EmptyType = Empty
self._response_handler_mapping: ResponseHandlerMap = {"default_handler": Empty, "response_type_handler": Empty}
self._resolved_include_in_schema: bool | EmptyType | None = Empty

def __call__(self, fn: AnyCallable) -> HTTPRouteHandler:
"""Replace a function with itself."""
Expand Down Expand Up @@ -387,6 +389,23 @@ def resolve_after_response(self) -> AsyncCallable | None:

return cast("AsyncCallable | None", self._resolved_after_response)

def resolve_include_in_schema(self) -> bool | None:
"""Resolve the 'include_in_schema' property by starting from the route handler and moving up.
If 'include_in_schema' is found in any of the ownership layers, the last value found is returned.
If not found in any layer, the default value ``True`` is returned.
Returns:
bool | None: The resolved 'include_in_schema' property.
"""
if self._resolved_include_in_schema is Empty:
include_in_schemas = [
i.include_in_schema for i in self.ownership_layers if i.include_in_schema is not Empty
]
self._resolved_include_in_schema = include_in_schemas[-1] if include_in_schemas else True

return cast(bool | None, self._resolved_include_in_schema)

def get_response_handler(self, is_response_type_data: bool = False) -> Callable[[Any], Awaitable[ASGIApp]]:
"""Resolve the response_handler function for the route handler.
Expand Down Expand Up @@ -480,6 +499,7 @@ def on_registration(self, app: Litestar) -> None:

super().on_registration(app)
self.resolve_after_response()
self.resolve_include_in_schema()

if self.sync_to_thread and not is_async_callable(self.fn.value):
self.fn.value = async_partial(self.fn.value)
Expand Down
12 changes: 6 additions & 6 deletions litestar/handlers/http_handlers/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def __init__(
content_media_type: str | None = None,
deprecated: bool = False,
description: str | None = None,
include_in_schema: bool = True,
include_in_schema: bool | EmptyType | None = Empty,
operation_class: type[Operation] = Operation,
operation_id: str | OperationIDCreator | None = None,
raises: list[type[HTTPException]] | None = None,
Expand Down Expand Up @@ -246,7 +246,7 @@ def __init__(
content_media_type: str | None = None,
deprecated: bool = False,
description: str | None = None,
include_in_schema: bool = True,
include_in_schema: bool | EmptyType | None = Empty,
operation_class: type[Operation] = Operation,
operation_id: str | OperationIDCreator | None = None,
raises: list[type[HTTPException]] | None = None,
Expand Down Expand Up @@ -410,7 +410,7 @@ def __init__(
content_media_type: str | None = None,
deprecated: bool = False,
description: str | None = None,
include_in_schema: bool = True,
include_in_schema: bool | EmptyType | None = Empty,
operation_class: type[Operation] = Operation,
operation_id: str | OperationIDCreator | None = None,
raises: list[type[HTTPException]] | None = None,
Expand Down Expand Up @@ -593,7 +593,7 @@ def __init__(
content_media_type: str | None = None,
deprecated: bool = False,
description: str | None = None,
include_in_schema: bool = True,
include_in_schema: bool | EmptyType | None = Empty,
operation_class: type[Operation] = Operation,
operation_id: str | OperationIDCreator | None = None,
raises: list[type[HTTPException]] | None = None,
Expand Down Expand Up @@ -757,7 +757,7 @@ def __init__(
content_media_type: str | None = None,
deprecated: bool = False,
description: str | None = None,
include_in_schema: bool = True,
include_in_schema: bool | EmptyType | None = Empty,
operation_class: type[Operation] = Operation,
operation_id: str | OperationIDCreator | None = None,
raises: list[type[HTTPException]] | None = None,
Expand Down Expand Up @@ -921,7 +921,7 @@ def __init__(
content_media_type: str | None = None,
deprecated: bool = False,
description: str | None = None,
include_in_schema: bool = True,
include_in_schema: bool | EmptyType | None = Empty,
operation_class: type[Operation] = Operation,
operation_id: str | OperationIDCreator | None = None,
raises: list[type[HTTPException]] | None = None,
Expand Down
4 changes: 4 additions & 0 deletions litestar/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class Router:
"etag",
"exception_handlers",
"guards",
"include_in_schema",
"middleware",
"opt",
"owner",
Expand Down Expand Up @@ -89,6 +90,7 @@ def __init__(
etag: ETag | None = None,
exception_handlers: ExceptionHandlersMap | None = None,
guards: Sequence[Guard] | None = None,
include_in_schema: bool | EmptyType | None = Empty,
middleware: Sequence[Middleware] | None = None,
opt: Mapping[str, Any] | None = None,
parameters: ParametersMap | None = None,
Expand Down Expand Up @@ -123,6 +125,7 @@ def __init__(
etag: An ``etag`` header of type :class:`ETag <.datastructures.ETag>` to add to route handlers of this app.
exception_handlers: A mapping of status codes and/or exception types to handler functions.
guards: A sequence of :data:`Guard <.types.Guard>` callables.
include_in_schema: A boolean flag dictating whether the route handler should be documented in the OpenAPI schema.
middleware: A sequence of :data:`Middleware <.types.Middleware>`.
opt: A string keyed mapping of arbitrary values that can be accessed in :data:`Guards <.types.Guard>` or
wherever you have access to :class:`Request <.connection.Request>` or
Expand Down Expand Up @@ -160,6 +163,7 @@ def __init__(
self.dependencies = dict(dependencies or {})
self.exception_handlers = dict(exception_handlers or {})
self.guards = list(guards or [])
self.include_in_schema = include_in_schema
self.middleware = list(middleware or [])
self.opt = dict(opt or {})
self.owner: Router | None = None
Expand Down
6 changes: 6 additions & 0 deletions litestar/testing/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ def create_test_client(
event_emitter_backend: type[BaseEventEmitterBackend] = SimpleEventEmitter,
exception_handlers: ExceptionHandlersMap | None = None,
guards: OptionalSequence[Guard] | None = None,
include_in_schema: bool | EmptyType | None = Empty,
listeners: OptionalSequence[EventListener] | None = None,
logging_config: BaseLoggingConfig | EmptyType | None = Empty,
middleware: OptionalSequence[Middleware] | None = None,
Expand Down Expand Up @@ -175,6 +176,7 @@ def test_my_handler() -> None:
:class:`BaseEventEmitterBackend <.events.emitter.BaseEventEmitterBackend>`.
exception_handlers: A mapping of status codes and/or exception types to handler functions.
guards: A sequence of :class:`Guard <.types.Guard>` callables.
include_in_schema: A boolean flag dictating whether the route handler should be documented in the OpenAPI schema.
lifespan: A list of callables returning async context managers, wrapping the lifespan of the ASGI application
listeners: A sequence of :class:`EventListener <.events.listener.EventListener>`.
logging_config: A subclass of :class:`BaseLoggingConfig <.logging.config.BaseLoggingConfig>`.
Expand Down Expand Up @@ -252,6 +254,7 @@ def test_my_handler() -> None:
event_emitter_backend=event_emitter_backend,
exception_handlers=exception_handlers,
guards=guards,
include_in_schema=include_in_schema,
listeners=listeners,
logging_config=logging_config,
middleware=middleware,
Expand Down Expand Up @@ -317,6 +320,7 @@ def create_async_test_client(
event_emitter_backend: type[BaseEventEmitterBackend] = SimpleEventEmitter,
exception_handlers: ExceptionHandlersMap | None = None,
guards: OptionalSequence[Guard] | None = None,
include_in_schema: bool | EmptyType | None = Empty,
lifespan: list[Callable[[Litestar], AbstractAsyncContextManager] | AbstractAsyncContextManager] | None = None,
listeners: OptionalSequence[EventListener] | None = None,
logging_config: BaseLoggingConfig | EmptyType | None = Empty,
Expand Down Expand Up @@ -417,6 +421,7 @@ def test_my_handler() -> None:
:class:`BaseEventEmitterBackend <.events.emitter.BaseEventEmitterBackend>`.
exception_handlers: A mapping of status codes and/or exception types to handler functions.
guards: A sequence of :class:`Guard <.types.Guard>` callables.
include_in_schema: A boolean flag dictating whether the route handler should be documented in the OpenAPI schema.
lifespan: A list of callables returning async context managers, wrapping the lifespan of the ASGI application
listeners: A sequence of :class:`EventListener <.events.listener.EventListener>`.
logging_config: A subclass of :class:`BaseLoggingConfig <.logging.config.BaseLoggingConfig>`.
Expand Down Expand Up @@ -493,6 +498,7 @@ def test_my_handler() -> None:
event_emitter_backend=event_emitter_backend,
exception_handlers=exception_handlers,
guards=guards,
include_in_schema=include_in_schema,
lifespan=lifespan,
listeners=listeners,
logging_config=logging_config,
Expand Down
Loading

0 comments on commit 1cf19ca

Please sign in to comment.