Skip to content

Commit

Permalink
Merge branch 'main' into fix-2358
Browse files Browse the repository at this point in the history
  • Loading branch information
cofin authored Sep 30, 2023
2 parents e0123b9 + cf131a5 commit 51573a8
Show file tree
Hide file tree
Showing 18 changed files with 191 additions and 156 deletions.
11 changes: 11 additions & 0 deletions .all-contributorsrc
Original file line number Diff line number Diff line change
Expand Up @@ -1287,6 +1287,17 @@
"contributions": [
"code"
]
},
{
"login": "lsanpablo",
"name": "Luis San Pablo",
"avatar_url": "https://avatars.githubusercontent.com/u/7145688?v=4",
"profile": "https://github.com/lsanpablo",
"contributions": [
"code",
"test",
"doc"
]
}
],
"contributorsPerLine": 7,
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/syshenyu"><img src="https://avatars.githubusercontent.com/u/92897003?v=4?s=100" width="100px;" alt="DICE_Lab"/><br /><sub><b>DICE_Lab</b></sub></a><br /><a href="https://github.com/litestar-org/litestar/commits?author=syshenyu" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lsanpablo"><img src="https://avatars.githubusercontent.com/u/7145688?v=4?s=100" width="100px;" alt="Luis San Pablo"/><br /><sub><b>Luis San Pablo</b></sub></a><br /><a href="https://github.com/litestar-org/litestar/commits?author=lsanpablo" title="Code">💻</a> <a href="https://github.com/litestar-org/litestar/commits?author=lsanpablo" title="Tests">⚠️</a> <a href="https://github.com/litestar-org/litestar/commits?author=lsanpablo" title="Documentation">📖</a></td>
</tr>
</tbody>
</table>
Expand Down
17 changes: 10 additions & 7 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Litestar is a powerful, flexible, highly performant, and opinionated ASGI framew
The Litestar framework supports :doc:`/usage/plugins`, ships
with :doc:`dependency injection </usage/dependency-injection>`, :doc:`security primitives </usage/security/index>`,
:doc:`OpenAPI schema generation </usage/openapi>`, `MessagePack <https://msgpack.org/>`_,
:doc:`middlewares </usage/middleware/index>`, and much more.
:doc:`middlewares </usage/middleware/index>`, a great :doc:`CLI </usage/cli>` experience, and much more.

Installation
------------
Expand All @@ -15,10 +15,7 @@ Installation
pip install litestar
.. tip:: ``litestar[standard]`` includes everything you need to get started with Litestar. It has: ``click`` and ``rich`` for a great CLI experience, ``jinja2`` for templating,
and ``uvicorn`` for running your app.

You can also install just the :doc:`CLI </usage/cli>` with ``litestar[cli]``!
.. tip:: ``litestar[standard]`` includes commonly used extras like ``uvicorn`` and ``jinja2`` (for templating).

.. dropdown:: Extras
:icon: star
Expand Down Expand Up @@ -57,6 +54,12 @@ Installation
:code:`pip install litestar[sqlalchemy]`

:doc:`CLI </usage/cli>`
.. deprecated:: 2.1.1
The ``litestar`` base installation now includes the CLI dependencies and this group is no longer required
to use the CLI.
If you need the optional CLI dependencies, install the ``standard`` group instead.
**Will be removed in 3.0**

:code:`pip install litestar[cli]`

:doc:`Jinja Templating </usage/templating>`
Expand All @@ -65,7 +68,7 @@ Installation
:doc:`Mako Templating </usage/templating>`
:code:`pip install litestar[mako]`

Standard Installation (includes CLI and Jinja templating):
Standard Installation (includes Uvicorn and Jinja2 templating):
:code:`pip install litestar[standard]`

All Extras:
Expand All @@ -77,7 +80,7 @@ Installation
Minimal Example
---------------

At a minimum, make sure you have installed ``litestar[standard]``, which includes ``litestar``, the CLI, and uvicorn.
At a minimum, make sure you have installed ``litestar[standard]``, which includes uvicorn.

First, create a file named ``app.py`` with the following contents:

Expand Down
2 changes: 1 addition & 1 deletion docs/tutorials/todo-app/2-interacting-with-the-list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ default values for the dataclass we have defined:

.. figure:: images/swagger-dict-vs-dataclass.png

Documentation for the ``create_item`` route with ``data`` typed as a ``dict`` vs
Documentation for the ``add_item`` route with ``data`` typed as a ``dict`` vs
``dataclass``

Using a dataclass also gives you better validation: Omitting a key such as ``title``
Expand Down
4 changes: 2 additions & 2 deletions docs/usage/applications.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ order, once the ASGI server (uvicorn, hypercorn, etc.) emits the respective even
.. mermaid::

flowchart LR
Startup[ASGI-Event: lifespan.startup] --> before_startup --> on_startup --> after_startup
Shutdown[ASGI-Event: lifespan.shutdown] --> before_shutdown --> on_shutdown --> after_shutdown
Startup[ASGI-Event: lifespan.startup] --> on_startup
Shutdown[ASGI-Event: lifespan.shutdown] --> on_shutdown

A classic use case for this is database connectivity. Often, we want to establish a database connection on application
startup, and then close it gracefully upon shutdown.
Expand Down
24 changes: 9 additions & 15 deletions docs/usage/cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,22 @@ CLI
===

Litestar provides a convenient command line interface (CLI) for running and managing Litestar applications. The CLI is
powered by `click <https://click.palletsprojects.com/>`_ and `rich <https://rich.readthedocs.io>`_.
powered by `click <https://click.palletsprojects.com/>`_, `rich <https://rich.readthedocs.io>`_,
and `rich-click <https://github.com/ewels/rich-click>`_.

Enabling the CLI
----------------
Enabling all CLI features
-------------------------

By default, the CLI dependencies are not included during the installation of Litestar to minimize the required packages.
To enable the CLI, you need to install Litestar with the ``cli`` or ``standard`` extras:

.. code-block:: shell
pip install litestar[cli]
The CLI and its hard dependencies are included by default. However, if you want to run your application
(using ``litestar run`` ) or beautify the Typescript generated by the ``litestar schema typescript``
command, you'll need ``uvicorn`` and ``jsbeautifier`` . They can be installed independently, but we
recommend installing the ``standard`` group which conveniently bundles commonly used optional dependencies.

.. code-block:: shell
pip install litestar[standard]
Once you have installed either of these, you can access the CLI functionality through the ``litestar`` command.

.. note::
Installing the CLI automatically includes the ``click``, ``rich``, and ``rich-click`` packages. While we recommend
using ``rich-click`` for the best experience, it is an optional dependency. If you prefer not to use it, you can
manually install ``click`` and ``rich`` in your project instead of using the built-in Litestar extras flag.
Once you have installed ``standard``, you'll have access to the ``litestar run`` command.

Autodiscovery
-------------
Expand Down
5 changes: 2 additions & 3 deletions litestar/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -528,9 +528,8 @@ async def lifespan(self) -> AsyncGenerator[None, None]:
It will be entered when the ``lifespan`` message has been received from the
server, and exit after the ``asgi.shutdown`` message. During this period, it is
responsible for calling the ``before_startup``, ``after_startup``,
`on_startup``, ``before_shutdown``, ``on_shutdown`` and ``after_shutdown``
hooks, as well as custom lifespan managers.
responsible for calling the ``on_startup``, ``on_shutdown`` hooks, as well as
custom lifespan managers.
"""
async with AsyncExitStack() as exit_stack:
for hook in self.on_shutdown[::-1]:
Expand Down
15 changes: 12 additions & 3 deletions litestar/cli/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,20 @@
from litestar.utils import get_name

RICH_CLICK_INSTALLED = False
try:
with contextlib.suppress(ImportError):
import rich_click # noqa: F401

RICH_CLICK_INSTALLED = True
except ImportError:
pass
UVICORN_INSTALLED = False
with contextlib.suppress(ImportError):
import uvicorn # noqa: F401

UVICORN_INSTALLED = True
JSBEAUTIFIER_INSTALLED = False
with contextlib.suppress(ImportError):
import jsbeautifier # noqa: F401

JSBEAUTIFIER_INSTALLED = True
if TYPE_CHECKING or not RICH_CLICK_INSTALLED: # pragma: no cover
from click import ClickException, Command, Context, Group, pass_context
else:
Expand All @@ -37,6 +44,8 @@

__all__ = (
"RICH_CLICK_INSTALLED",
"UVICORN_INSTALLED",
"JSBEAUTIFIER_INSTALLED",
"LoadedApp",
"LitestarCLIException",
"LitestarEnv",
Expand Down
19 changes: 15 additions & 4 deletions litestar/cli/commands/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
import sys
from typing import TYPE_CHECKING, Any

import uvicorn
from rich.tree import Tree

from litestar.cli._utils import RICH_CLICK_INSTALLED, LitestarEnv, console, show_app_info
from litestar.cli._utils import RICH_CLICK_INSTALLED, UVICORN_INSTALLED, LitestarEnv, console, show_app_info
from litestar.routes import HTTPRoute, WebSocketRoute
from litestar.utils.helpers import unwrap_partial

if UVICORN_INSTALLED:
import uvicorn

if TYPE_CHECKING or not RICH_CLICK_INSTALLED: # pragma: no cover
import click
from click import Context, command, option
Expand Down Expand Up @@ -95,7 +97,7 @@ def run_command(
pdb: bool,
ctx: Context,
) -> None:
"""Run a Litestar app.
"""Run a Litestar app; requires ``uvicorn``.
The app can be either passed as a module path in the form of <module name>.<submodule>:<app instance or factory>,
set as an environment variable LITESTAR_APP with the same format or automatically discovered from one of these
Expand All @@ -110,6 +112,12 @@ def run_command(
if pdb:
os.environ["LITESTAR_PDB"] = "1"

if not UVICORN_INSTALLED:
console.print(
r"uvicorn is not installed. Please install the standard group, litestar\[standard], to use this command."
)
sys.exit(1)

if callable(ctx.obj):
ctx.obj = ctx.obj()
else:
Expand All @@ -135,7 +143,10 @@ def run_command(
show_app_info(app)

if workers == 1 and not reload:
uvicorn.run(
# A guard statement at the beginning of this function prevents uvicorn from being unbound
# See "reportUnboundVariable in:
# https://microsoft.github.io/pyright/#/configuration?id=type-check-diagnostics-settings
uvicorn.run( # pyright: ignore
app=env.app_path,
host=host,
port=port,
Expand Down
16 changes: 10 additions & 6 deletions litestar/cli/commands/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@
from pathlib import Path
from typing import TYPE_CHECKING

from jsbeautifier import Beautifier
from yaml import dump as dump_yaml

from litestar import Litestar
from litestar._openapi.typescript_converter.converter import (
convert_openapi_to_typescript,
)
from litestar.cli._utils import RICH_CLICK_INSTALLED, LitestarCLIException, LitestarGroup
from litestar.cli._utils import JSBEAUTIFIER_INSTALLED, RICH_CLICK_INSTALLED, LitestarCLIException, LitestarGroup

if TYPE_CHECKING or not RICH_CLICK_INSTALLED: # pragma: no cover
from click import Path as ClickPath
Expand All @@ -18,11 +17,13 @@
from rich_click import Path as ClickPath
from rich_click import group, option

if JSBEAUTIFIER_INSTALLED: # pragma: no cover
from jsbeautifier import Beautifier

__all__ = ("generate_openapi_schema", "generate_typescript_specs", "schema_group")
beautifier = Beautifier()


beautifier = Beautifier()
__all__ = ("generate_openapi_schema", "generate_typescript_specs", "schema_group")


@group(cls=LitestarGroup, name="schema")
Expand Down Expand Up @@ -64,7 +65,10 @@ def generate_typescript_specs(app: Litestar, output: Path, namespace: str) -> No
"""Generate TypeScript specs from the OpenAPI schema."""
try:
specs = convert_openapi_to_typescript(app.openapi_schema, namespace)
beautified_output = beautifier.beautify(specs.write())
output.write_text(beautified_output)
# beautifier will be defined if JSBEAUTIFIER_INSTALLED is True
specs_output = (
beautifier.beautify(specs.write()) if JSBEAUTIFIER_INSTALLED else specs.write() # pyright: ignore
)
output.write_text(specs_output)
except OSError as e: # pragma: no cover
raise LitestarCLIException(f"failed to write schema to path {output}") from e
5 changes: 3 additions & 2 deletions litestar/middleware/exceptions/_debug_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ def get_symbol_name(frame: FrameInfo) -> str:
locals_dict = frame.frame.f_locals
# this piece assumes that the code uses standard names "self" and "cls"
# in instance and class methods
instance_or_cls = locals_dict.get("self") or locals_dict.get("cls")
classname = f"{get_name(instance_or_cls)}." if instance_or_cls else ""
instance_or_cls = inst if (inst := locals_dict.get("self")) is not None else locals_dict.get("cls")

classname = f"{get_name(instance_or_cls)}." if instance_or_cls is not None else ""

return f"{classname}{frame.function}"

Expand Down
71 changes: 21 additions & 50 deletions litestar/types/callable_types.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
from __future__ import annotations

from typing import (
TYPE_CHECKING,
Any,
AsyncGenerator,
Awaitable,
Callable,
Generator,
List,
Union,
)
from typing import TYPE_CHECKING, Any, AsyncGenerator, Awaitable, Callable, Generator

if TYPE_CHECKING:
from typing_extensions import TypeAlias
Expand All @@ -25,43 +16,23 @@
from litestar.types.internal_types import LitestarType, PathParameterDefinition
from litestar.types.protocols import Logger

AfterExceptionHookHandler: TypeAlias = Callable[[Exception, Scope], SyncOrAsyncUnion[None]]
AfterRequestHookHandler: TypeAlias = Union[
Callable[[ASGIApp], SyncOrAsyncUnion[ASGIApp]], Callable[[Response], SyncOrAsyncUnion[Response]]
]
AfterResponseHookHandler: TypeAlias = Callable[[Request], SyncOrAsyncUnion[None]]
AsyncAnyCallable: TypeAlias = Callable[..., Awaitable[Any]]
AnyCallable: TypeAlias = Callable[..., Any]
AnyGenerator: TypeAlias = Union[Generator[Any, Any, Any], AsyncGenerator[Any, Any]]
BeforeMessageSendHookHandler: TypeAlias = Callable[[Message, Scope], SyncOrAsyncUnion[None]]
BeforeRequestHookHandler: TypeAlias = Callable[[Request], Union[Any, Awaitable[Any]]]
CacheKeyBuilder: TypeAlias = Callable[[Request], str]
ExceptionHandler: TypeAlias = Callable[[Request, Exception], Response]
ExceptionLoggingHandler: TypeAlias = Callable[[Logger, Scope, List[str]], None]
GetLogger: TypeAlias = Callable[..., Logger]
Guard: TypeAlias = Callable[[ASGIConnection, BaseRouteHandler], SyncOrAsyncUnion[None]]
LifespanHook: TypeAlias = Union[
Callable[[LitestarType], SyncOrAsyncUnion[Any]],
Callable[[], SyncOrAsyncUnion[Any]],
]
OnAppInitHandler: TypeAlias = Callable[[AppConfig], AppConfig]
OperationIDCreator: TypeAlias = Callable[[HTTPRouteHandler, Method, List[Union[str, PathParameterDefinition]]], str]
Serializer: TypeAlias = Callable[[Any], Any]
else:
AfterExceptionHookHandler: TypeAlias = Any
AfterRequestHookHandler: TypeAlias = Any
AfterResponseHookHandler: TypeAlias = Any
AsyncAnyCallable: TypeAlias = Any
AnyCallable: TypeAlias = Any
AnyGenerator: TypeAlias = Any
BeforeMessageSendHookHandler: TypeAlias = Any
BeforeRequestHookHandler: TypeAlias = Any
CacheKeyBuilder: TypeAlias = Any
ExceptionHandler: TypeAlias = Any
ExceptionLoggingHandler: TypeAlias = Any
GetLogger: TypeAlias = Any
Guard: TypeAlias = Any
LifespanHook: TypeAlias = Any
OnAppInitHandler: TypeAlias = Any
OperationIDCreator: TypeAlias = Any
Serializer: TypeAlias = Any

AfterExceptionHookHandler: TypeAlias = "Callable[[Exception, Scope], SyncOrAsyncUnion[None]]"
AfterRequestHookHandler: TypeAlias = (
"Callable[[ASGIApp], SyncOrAsyncUnion[ASGIApp]] | Callable[[Response], SyncOrAsyncUnion[Response]]"
)
AfterResponseHookHandler: TypeAlias = "Callable[[Request], SyncOrAsyncUnion[None]]"
AsyncAnyCallable: TypeAlias = Callable[..., Awaitable[Any]]
AnyCallable: TypeAlias = Callable[..., Any]
AnyGenerator: TypeAlias = "Generator[Any, Any, Any] | AsyncGenerator[Any, Any]"
BeforeMessageSendHookHandler: TypeAlias = "Callable[[Message, Scope], SyncOrAsyncUnion[None]]"
BeforeRequestHookHandler: TypeAlias = "Callable[[Request], Any | Awaitable[Any]]"
CacheKeyBuilder: TypeAlias = "Callable[[Request], str]"
ExceptionHandler: TypeAlias = "Callable[[Request, Exception], Response]"
ExceptionLoggingHandler: TypeAlias = "Callable[[Logger, Scope, list[str]], None]"
GetLogger: TypeAlias = "Callable[..., Logger]"
Guard: TypeAlias = "Callable[[ASGIConnection, BaseRouteHandler], SyncOrAsyncUnion[None]]"
LifespanHook: TypeAlias = "Callable[[LitestarType], SyncOrAsyncUnion[Any]] | Callable[[], SyncOrAsyncUnion[Any]]"
OnAppInitHandler: TypeAlias = "Callable[[AppConfig], AppConfig]"
OperationIDCreator: TypeAlias = "Callable[[HTTPRouteHandler, Method, list[str | PathParameterDefinition]], str]"
Serializer: TypeAlias = Callable[[Any], Any]
Loading

0 comments on commit 51573a8

Please sign in to comment.