Skip to content
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
13 changes: 11 additions & 2 deletions lite_bootstrap/instruments/healthchecks_instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

import typing_extensions

from lite_bootstrap.exceptions import ConfigurationError
from lite_bootstrap.helpers.path import is_valid_path
from lite_bootstrap.instruments.base import BaseConfig, BaseInstrument


Expand All @@ -27,6 +29,13 @@ class HealthChecksConfig(BaseConfig):
health_checks_include_in_schema: bool = False
health_checks_probes: Mapping[str, HealthProbe] = dataclasses.field(default_factory=dict)

def __post_init__(self) -> None:
super().__post_init__()
path = self.health_checks_path
if path and path != "/" and not is_valid_path(path):
msg = f"Invalid health_checks_path: {path!r}"
raise ConfigurationError(msg)

@property
def health_check_data(self) -> HealthCheckTypedDict:
return {
Expand All @@ -41,11 +50,11 @@ def health_check_data(self) -> HealthCheckTypedDict:

@dataclasses.dataclass(kw_only=True, slots=True)
class HealthChecksInstrument(BaseInstrument[HealthChecksConfigT]):
not_configured_reason = "health_checks_enabled is False"
not_configured_reason = "health_checks_enabled is False or health_checks_path is empty"

@classmethod
def is_configured(cls, bootstrap_config: "HealthChecksConfig") -> bool:
return bootstrap_config.health_checks_enabled
return bootstrap_config.health_checks_enabled and bool(bootstrap_config.health_checks_path)

def render_health_check_data(self, *, health_status: bool = True) -> HealthCheckTypedDict:
health_check_data = self.bootstrap_config.health_check_data
Expand Down
14 changes: 10 additions & 4 deletions lite_bootstrap/instruments/prometheus_instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import dataclasses
import typing

from lite_bootstrap.exceptions import ConfigurationError
from lite_bootstrap.helpers.path import is_valid_path
from lite_bootstrap.instruments.base import BaseConfig, BaseInstrument

Expand All @@ -12,6 +13,13 @@ class PrometheusConfig(BaseConfig):
prometheus_metrics_path: str = "/metrics"
prometheus_metrics_include_in_schema: bool = False

def __post_init__(self) -> None:
super().__post_init__()
path = self.prometheus_metrics_path
if path and not is_valid_path(path):
msg = f"Invalid prometheus_metrics_path: {path!r}"
raise ConfigurationError(msg)


@dataclasses.dataclass(kw_only=True, frozen=True)
class PrometheusHttpConfig(PrometheusConfig):
Expand All @@ -29,10 +37,8 @@ def is_prometheus_http_metrics_on(self, *, opentelemetry_records_http_metrics: b

@dataclasses.dataclass(kw_only=True, slots=True)
class PrometheusInstrument(BaseInstrument[PrometheusConfigT]):
not_configured_reason = "prometheus_metrics_path is empty or not valid"
not_configured_reason = "prometheus_metrics_path is empty"

@classmethod
def is_configured(cls, bootstrap_config: "PrometheusConfig") -> bool:
return bool(bootstrap_config.prometheus_metrics_path) and is_valid_path(
bootstrap_config.prometheus_metrics_path
)
return bool(bootstrap_config.prometheus_metrics_path)
4 changes: 3 additions & 1 deletion tests/instruments/test_healthchecks_instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ def test_healthchecks_instrument_configured_by_default() -> None:
def test_healthchecks_instrument_not_configured_when_disabled() -> None:
config = HealthChecksConfig(health_checks_enabled=False)
assert not HealthChecksInstrument.is_configured(config)
assert HealthChecksInstrument.not_configured_reason == "health_checks_enabled is False"
assert (
HealthChecksInstrument.not_configured_reason == "health_checks_enabled is False or health_checks_path is empty"
)


def test_healthchecks_render_data_default() -> None:
Expand Down
8 changes: 1 addition & 7 deletions tests/instruments/test_prometheus_instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,7 @@ def test_prometheus_instrument_configured_with_default_path() -> None:
def test_prometheus_instrument_not_configured_with_empty_path() -> None:
config = PrometheusConfig(prometheus_metrics_path="")
assert not PrometheusInstrument.is_configured(config)
assert PrometheusInstrument.not_configured_reason == "prometheus_metrics_path is empty or not valid"


def test_prometheus_instrument_not_configured_with_invalid_path() -> None:
# No leading slash → invalid per is_valid_path regex.
config = PrometheusConfig(prometheus_metrics_path="metrics")
assert not PrometheusInstrument.is_configured(config)
assert PrometheusInstrument.not_configured_reason == "prometheus_metrics_path is empty"


def test_prometheus_instrument_configured_with_custom_valid_path() -> None:
Expand Down
11 changes: 11 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import pytest

from lite_bootstrap import FastAPIConfig, FastStreamConfig, FreeConfig, LitestarConfig
from lite_bootstrap.exceptions import ConfigurationError
from lite_bootstrap.instruments.base import BaseConfig
from tests.conftest import CustomInstrumentor

Expand Down Expand Up @@ -119,3 +120,13 @@ def test_service_name_and_version_fall_back_for_readers_that_need_a_string(
) -> None:
assert config.service_name_or_default == expected_name
assert config.service_version_or_default == expected_version


def test_invalid_prometheus_metrics_path_raises_at_construction() -> None:
with pytest.raises(ConfigurationError, match="prometheus_metrics_path"):
FastAPIConfig(prometheus_metrics_path="metrics")


def test_invalid_health_checks_path_raises_at_construction() -> None:
with pytest.raises(ConfigurationError, match="health_checks_path"):
FastAPIConfig(health_checks_path="health")
8 changes: 8 additions & 0 deletions tests/test_fastapi_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -740,3 +740,11 @@ def test_built_app_title_and_version_fall_back_when_unset() -> None:
application = FastAPIConfig().app
assert application.title == "micro-service"
assert application.version == "1.0.0"


def test_empty_observability_paths_disable_routes() -> None:
config = FastAPIConfig(health_checks_path="", prometheus_metrics_path="")
with _bootstrapped(config) as application, TestClient(application) as client:
assert client.get("/health/").status_code == status.HTTP_404_NOT_FOUND
assert client.get("/metrics").status_code == status.HTTP_404_NOT_FOUND
assert client.get("/").status_code == status.HTTP_404_NOT_FOUND
2 changes: 1 addition & 1 deletion tests/test_faststream_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ def test_faststream_prometheus_is_skipped_without_metrics_path(broker: RedisBrok
)
bootstrapper = FastStreamBootstrapper(bootstrap_config=bootstrap_config)
skipped = dict(bootstrapper.skipped_instruments)
assert skipped[FastStreamPrometheusInstrument] == "prometheus_metrics_path is empty or not valid"
assert skipped[FastStreamPrometheusInstrument] == "prometheus_metrics_path is empty"


async def test_faststream_prometheus_serves_the_default_registry(broker: RedisBroker) -> None:
Expand Down
Loading