Skip to content
Permalink

Comparing changes

This is a direct comparison between two commits made in this repository or its related repositories. View the default comparison for this range or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: litestar-org/litestar
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 2ed4674a8bbbf67799788dbc6504669c65c9f954
Choose a base ref
..
head repository: litestar-org/litestar
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 6fb06298312e18ce97d2395de64818a5ef74b8dc
Choose a head ref
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -24,7 +24,7 @@ repos:
- id: unasyncd
additional_dependencies: ["ruff"]
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: "v0.7.3"
rev: "v0.7.4"
hooks:
- id: ruff
args: ["--fix"]
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from litestar import Litestar
from litestar.contrib.prometheus import PrometheusConfig, PrometheusController
from litestar.plugins.prometheus import PrometheusConfig, PrometheusController


def create_app(group_path: bool = False):
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import Any, Dict

from litestar import Litestar, Request
from litestar.contrib.prometheus import PrometheusConfig, PrometheusController
from litestar.plugins.prometheus import PrometheusConfig, PrometheusController


# We can modify the path of our custom handler and override the metrics format by subclassing the PrometheusController.
@@ -38,7 +38,7 @@ def custom_exemplar(request: Request[Any, Any, Any]) -> Dict[str, str]:
app_name="litestar-example",
prefix="litestar",
labels=extra_labels,
buckets=buckets,
buckets=buckets, # pyright: ignore[reportArgumentType]
exemplars=custom_exemplar,
excluded_http_methods=["POST"],
)
1 change: 1 addition & 0 deletions docs/reference/plugins/index.rst
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@ plugins
flash_messages
htmx
problem_details
prometheus
pydantic
structlog
sqlalchemy
5 changes: 5 additions & 0 deletions docs/reference/plugins/prometheus.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
prometheus
==========

.. automodule:: litestar.plugins.prometheus
:members:
6 changes: 3 additions & 3 deletions docs/usage/metrics/prometheus.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Prometheus
==========

Litestar includes optional Prometheus exporter that is exported from ``litestar.contrib.prometheus``. To use
Litestar includes optional Prometheus exporter that is exported from ``litestar.plugins.prometheus``. To use
this package, you should first install the required dependencies:

.. code-block:: bash
@@ -17,12 +17,12 @@ this package, you should first install the required dependencies:
Once these requirements are satisfied, you can instrument your Litestar application:

.. literalinclude:: /examples/contrib/prometheus/using_prometheus_exporter.py
.. literalinclude:: /examples/plugins/prometheus/using_prometheus_exporter.py
:language: python
:caption: Using the Prometheus Exporter

You can also customize the configuration:

.. literalinclude:: /examples/contrib/prometheus/using_prometheus_exporter_with_extra_configs.py
.. literalinclude:: /examples/plugins/prometheus/using_prometheus_exporter_with_extra_configs.py
:language: python
:caption: Configuring the Prometheus Exporter
39 changes: 36 additions & 3 deletions litestar/contrib/prometheus/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,38 @@
from .config import PrometheusConfig
from .controller import PrometheusController
from .middleware import PrometheusMiddleware
# ruff: noqa: TCH004, F401
from __future__ import annotations

from typing import TYPE_CHECKING

from litestar.utils import warn_deprecation

__all__ = ("PrometheusMiddleware", "PrometheusConfig", "PrometheusController")


def __getattr__(attr_name: str) -> object:
if attr_name in __all__:
from litestar.plugins.prometheus import (
PrometheusConfig,
PrometheusController,
PrometheusMiddleware,
)

warn_deprecation(
deprecated_name=f"litestar.contrib.prometheus.{attr_name}",
version="2.13.0",
kind="import",
removal_in="3.0",
info=f"importing {attr_name} from 'litestar.contrib.prometheus' is deprecated, please "
f"import it from 'litestar.plugins.prometheus' instead",
)
value = globals()[attr_name] = locals()[attr_name]
return value

raise AttributeError(f"module {__name__!r} has no attribute {attr_name!r}") # pragma: no cover


if TYPE_CHECKING:
from litestar.plugins.prometheus import (
PrometheusConfig,
PrometheusController,
PrometheusMiddleware,
)
75 changes: 19 additions & 56 deletions litestar/contrib/prometheus/config.py
Original file line number Diff line number Diff line change
@@ -1,67 +1,30 @@
# ruff: noqa: TCH004, F401
from __future__ import annotations

from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Callable, Mapping, Sequence
from typing import TYPE_CHECKING

from litestar.contrib.prometheus.middleware import (
PrometheusMiddleware,
)
from litestar.exceptions import MissingDependencyException
from litestar.middleware.base import DefineMiddleware
from litestar.utils import warn_deprecation

__all__ = ("PrometheusConfig",)


try:
import prometheus_client # noqa: F401
except ImportError as e:
raise MissingDependencyException("prometheus_client", "prometheus-client", "prometheus") from e
def __getattr__(attr_name: str) -> object:
if attr_name in __all__:
from litestar.plugins.prometheus import PrometheusConfig

warn_deprecation(
deprecated_name=f"litestar.contrib.prometheus.config.{attr_name}",
version="2.13.0",
kind="import",
removal_in="3.0",
info=f"importing {attr_name} from 'litestar.contrib.prometheus.config' is deprecated, please "
f"import it from 'litestar.plugins.prometheus' instead",
)
value = globals()[attr_name] = locals()[attr_name]
return value

if TYPE_CHECKING:
from litestar.connection.request import Request
from litestar.types import Method, Scopes


@dataclass
class PrometheusConfig:
"""Configuration class for the PrometheusConfig middleware."""
raise AttributeError(f"module {__name__!r} has no attribute {attr_name!r}") # pragma: no cover

app_name: str = field(default="litestar")
"""The name of the application to use in the metrics."""
prefix: str = "litestar"
"""The prefix to use for the metrics."""
labels: Mapping[str, str | Callable] | None = field(default=None)
"""A mapping of labels to add to the metrics. The values can be either a string or a callable that returns a string."""
exemplars: Callable[[Request], dict] | None = field(default=None)
"""A callable that returns a list of exemplars to add to the metrics. Only supported in opementrics-text exposition format."""
buckets: list[str | float] | None = field(default=None)
"""A list of buckets to use for the histogram."""
excluded_http_methods: Method | Sequence[Method] | None = field(default=None)
"""A list of http methods to exclude from the metrics."""
exclude_unhandled_paths: bool = field(default=False)
"""Whether to ignore requests for unhandled paths from the metrics."""
exclude: str | list[str] | None = field(default=None)
"""A pattern or list of patterns for routes to exclude from the metrics."""
exclude_opt_key: str | None = field(default=None)
"""A key or list of keys in ``opt`` with which a route handler can "opt-out" of the middleware."""
scopes: Scopes | None = field(default=None)
"""ASGI scopes processed by the middleware, if None both ``http`` and ``websocket`` will be processed."""
middleware_class: type[PrometheusMiddleware] = field(default=PrometheusMiddleware)
"""The middleware class to use.
"""
group_path: bool = field(default=False)
"""Whether to group paths in the metrics to avoid cardinality explosion.
"""

@property
def middleware(self) -> DefineMiddleware:
"""Create an instance of :class:`DefineMiddleware <litestar.middleware.base.DefineMiddleware>` that wraps with.
[PrometheusMiddleware][litestar.contrib.prometheus.PrometheusMiddleware]. or a subclass
of this middleware.
Returns:
An instance of ``DefineMiddleware``.
"""
return DefineMiddleware(self.middleware_class, config=self)
if TYPE_CHECKING:
from litestar.plugins.prometheus import PrometheusConfig
79 changes: 28 additions & 51 deletions litestar/contrib/prometheus/controller.py
Original file line number Diff line number Diff line change
@@ -1,53 +1,30 @@
# ruff: noqa: TCH004, F401
from __future__ import annotations

import os

from litestar import Controller, get
from litestar.exceptions import MissingDependencyException
from litestar.response import Response

try:
import prometheus_client # noqa: F401
except ImportError as e:
raise MissingDependencyException("prometheus_client", "prometheus-client", "prometheus") from e

from prometheus_client import (
CONTENT_TYPE_LATEST,
REGISTRY,
CollectorRegistry,
generate_latest,
multiprocess,
)
from prometheus_client.openmetrics.exposition import (
CONTENT_TYPE_LATEST as OPENMETRICS_CONTENT_TYPE_LATEST,
)
from prometheus_client.openmetrics.exposition import (
generate_latest as openmetrics_generate_latest,
)

__all__ = [
"PrometheusController",
]


class PrometheusController(Controller):
"""Controller for Prometheus endpoints."""

path: str = "/metrics"
"""The path to expose the metrics on."""
openmetrics_format: bool = False
"""Whether to expose the metrics in OpenMetrics format."""

@get()
async def get(self) -> Response:
registry = REGISTRY
if "prometheus_multiproc_dir" in os.environ or "PROMETHEUS_MULTIPROC_DIR" in os.environ:
registry = CollectorRegistry()
multiprocess.MultiProcessCollector(registry) # type: ignore[no-untyped-call]

if self.openmetrics_format:
headers = {"Content-Type": OPENMETRICS_CONTENT_TYPE_LATEST}
return Response(openmetrics_generate_latest(registry), status_code=200, headers=headers) # type: ignore[no-untyped-call]

headers = {"Content-Type": CONTENT_TYPE_LATEST}
return Response(generate_latest(registry), status_code=200, headers=headers)
from typing import TYPE_CHECKING

from litestar.utils import warn_deprecation

__all__ = ("PrometheusController",)


def __getattr__(attr_name: str) -> object:
if attr_name in __all__:
from litestar.plugins.prometheus import PrometheusController

warn_deprecation(
deprecated_name=f"litestar.contrib.prometheus.controller.{attr_name}",
version="2.13.0",
kind="import",
removal_in="3.0",
info=f"importing {attr_name} from 'litestar.contrib.prometheus.controller' is deprecated, please "
f"import it from 'litestar.plugins.prometheus' instead",
)
value = globals()[attr_name] = locals()[attr_name]
return value

raise AttributeError(f"module {__name__!r} has no attribute {attr_name!r}") # pragma: no cover


if TYPE_CHECKING:
from litestar.plugins.prometheus import PrometheusController
Loading