Skip to content

Commit

Permalink
feat: deprecate litestar.contrib.prometheus (#3863)
Browse files Browse the repository at this point in the history
moves `litestar.contrib.prometheus` to `litestar.plugins.prometheus`
  • Loading branch information
cofin authored Nov 21, 2024
1 parent 1b9d1c8 commit 44d42e6
Show file tree
Hide file tree
Showing 18 changed files with 702 additions and 505 deletions.
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):
Expand Down
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.
Expand Down Expand Up @@ -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"],
)
Expand Down
1 change: 1 addition & 0 deletions docs/reference/plugins/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,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
Expand All @@ -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

0 comments on commit 44d42e6

Please sign in to comment.