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

feat: move litestar.contrib.prometheus to litestar.plugins.prometheus #3863

Merged
merged 6 commits into from
Nov 21, 2024
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
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
Loading