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
4 changes: 1 addition & 3 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ A *bootstrapper* is ready when its framework package is importable: `is_ready()`
`__init__`, raising `BootstrapperNotReadyError` when false. It is about the environment, never about
the config, and it is a different question from whether an instrument is **configured**.
_Avoid_: configured, for this sense. The attribute names follow the split: a bootstrapper carries
`not_ready_message`, an instrument carries `not_configured_reason`. An instrument written against
the old `not_ready_message` still works until 2.0: `BaseInstrument.__init_subclass__` forwards it
and emits a `DeprecationWarning`.
`not_ready_message`, an instrument carries `not_configured_reason`.

**Skipped**:
An instrument the bootstrapper decided not to instantiate. Two paths, deliberately different
Expand Down
10 changes: 0 additions & 10 deletions lite_bootstrap/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import typing

from lite_bootstrap.bootstrappers.fastapi_bootstrapper import FastAPIBootstrapper, FastAPIConfig
from lite_bootstrap.bootstrappers.fastmcp_bootstrapper import FastMcpBootstrapper, FastMcpConfig
from lite_bootstrap.bootstrappers.faststream_bootstrapper import FastStreamBootstrapper, FastStreamConfig
Expand All @@ -13,14 +11,9 @@
LiteBootstrapError,
TeardownError,
)
from lite_bootstrap.helpers.warn import deprecated_module_getattr
from lite_bootstrap.instruments.pyroscope_instrument import PyroscopeConfig, PyroscopeInstrument


if typing.TYPE_CHECKING:
from lite_bootstrap.bootstrappers.free_bootstrapper import FreeBootstrapperConfig as FreeBootstrapperConfig


__all__ = [
"BootstrapperNotReadyError",
"ConfigurationError",
Expand All @@ -41,6 +34,3 @@
"PyroscopeInstrument",
"TeardownError",
]


__getattr__ = deprecated_module_getattr(__name__, {"FreeBootstrapperConfig": (FreeConfig, "FreeConfig")})
17 changes: 0 additions & 17 deletions lite_bootstrap/bootstrappers/fastapi_bootstrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,23 +80,6 @@ class FastAPIConfig(
"starlette": "StarletteIntegration",
"fastapi": "FastApiIntegration",
}
# Deprecated names, removed in 2.0.
_renamed_fields: typing.ClassVar[Mapping[str, str]] = {
"prometheus_instrumentator_params": "prometheus_instrumentator_additional_params",
"prometheus_instrument_params": "prometheus_instrument_additional_params",
"prometheus_expose_params": "prometheus_expose_additional_params",
"fastapi_logging_middleware_enabled": "logging_access_log_enabled",
}
prometheus_instrumentator_params: dict[str, typing.Any] | UnsetType = dataclasses.field(
default=UNSET, repr=False, compare=False
)
prometheus_instrument_params: dict[str, typing.Any] | UnsetType = dataclasses.field(
default=UNSET, repr=False, compare=False
)
prometheus_expose_params: dict[str, typing.Any] | UnsetType = dataclasses.field(
default=UNSET, repr=False, compare=False
)
fastapi_logging_middleware_enabled: bool | UnsetType = dataclasses.field(default=UNSET, repr=False, compare=False)

def __post_init__(self) -> None:
# @dataclass(slots=True) replaces the class object, breaking bare super().
Expand Down
14 changes: 0 additions & 14 deletions lite_bootstrap/bootstrappers/fastmcp_bootstrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
disable_sentry_integrations,
warn_about_sentry_integrations_installed_earlier,
)
from lite_bootstrap.types import UNSET, UnsetType


if import_checker.is_fastmcp_installed:
Expand Down Expand Up @@ -214,19 +213,6 @@ class FastMcpConfig(
prometheus_instrumentator_additional_params: dict[str, typing.Any] = dataclasses.field(default_factory=dict)
prometheus_instrument_additional_params: dict[str, typing.Any] = dataclasses.field(default_factory=dict)
_sentry_lean_replaced_integrations: typing.ClassVar[Mapping[str, str]] = {"starlette": "StarletteIntegration"}
# Deprecated names, removed in 2.0.
_renamed_fields: typing.ClassVar[Mapping[str, str]] = {
"fastmcp_logging_middleware_enabled": "logging_access_log_enabled",
"prometheus_instrumentator_params": "prometheus_instrumentator_additional_params",
"prometheus_instrument_params": "prometheus_instrument_additional_params",
}
fastmcp_logging_middleware_enabled: bool | UnsetType = dataclasses.field(default=UNSET, repr=False, compare=False)
prometheus_instrumentator_params: dict[str, typing.Any] | UnsetType = dataclasses.field(
default=UNSET, repr=False, compare=False
)
prometheus_instrument_params: dict[str, typing.Any] | UnsetType = dataclasses.field(
default=UNSET, repr=False, compare=False
)


@dataclasses.dataclass(kw_only=True)
Expand Down
7 changes: 0 additions & 7 deletions lite_bootstrap/bootstrappers/free_bootstrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import typing

from lite_bootstrap.bootstrappers.base import BaseBootstrapper
from lite_bootstrap.helpers.warn import deprecated_module_getattr
from lite_bootstrap.instruments.logging_instrument import LoggingConfig, LoggingInstrument
from lite_bootstrap.instruments.opentelemetry_instrument import OpenTelemetryConfig, OpenTelemetryInstrument
from lite_bootstrap.instruments.pyroscope_instrument import PyroscopeConfig, PyroscopeInstrument
Expand Down Expand Up @@ -33,9 +32,3 @@ def __init__(self, bootstrap_config: FreeConfig) -> None:

def _prepare_application(self) -> None:
return None


if typing.TYPE_CHECKING:
FreeBootstrapperConfig = FreeConfig

__getattr__ = deprecated_module_getattr(__name__, {"FreeBootstrapperConfig": (FreeConfig, "FreeConfig")})
10 changes: 0 additions & 10 deletions lite_bootstrap/bootstrappers/litestar_bootstrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
warn_about_sentry_integrations_installed_earlier,
)
from lite_bootstrap.instruments.swagger_instrument import SwaggerConfig, SwaggerInstrument
from lite_bootstrap.types import UNSET, UnsetType


if typing.TYPE_CHECKING:
Expand Down Expand Up @@ -262,16 +261,7 @@ class LitestarConfig(
# Bounds path-label cardinality (Litestar defaults False -> raw URLs leak memory). See litestar#4891.
prometheus_group_path: bool = True
swagger_additional_params: dict[str, typing.Any] = dataclasses.field(default_factory=dict)
# Deprecated names, removed in 2.0.
_sentry_lean_replaced_integrations: typing.ClassVar[Mapping[str, str]] = {"litestar": "LitestarIntegration"}
_renamed_fields: typing.ClassVar[Mapping[str, str]] = {
"litestar_logging_middleware_enabled": "logging_access_log_enabled",
"swagger_extra_params": "swagger_additional_params",
}
litestar_logging_middleware_enabled: bool | UnsetType = dataclasses.field(default=UNSET, repr=False, compare=False)
swagger_extra_params: dict[str, typing.Any] | UnsetType = dataclasses.field(
default=UNSET, repr=False, compare=False
)

def __post_init__(self) -> None:
# @dataclass(slots=True) replaces the class object, breaking bare super().
Expand Down
3 changes: 2 additions & 1 deletion lite_bootstrap/helpers/warn.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

_DATACLASS_GENERATED_INIT: typing.Final = "<string>"
_INTERNAL_MODULES: typing.Final = frozenset({__name__.split(".")[0], "dataclasses"})
NEXT_MAJOR_VERSION: typing.Final = "3.0"


def _is_internal_frame(frame: types.FrameType) -> bool:
Expand Down Expand Up @@ -52,7 +53,7 @@ def __getattr__(name: str) -> object: # noqa: N807
target, replacement = aliases[name]
advice = f"use {replacement} instead" if replacement else "nothing replaces it"
warn_at_caller(
f"{name} is deprecated and will be removed in lite-bootstrap 2.0; {advice}.",
f"{name} is deprecated and will be removed in lite-bootstrap {NEXT_MAJOR_VERSION}; {advice}.",
category=DeprecationWarning,
)
return target
Expand Down
24 changes: 3 additions & 21 deletions lite_bootstrap/instruments/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from collections.abc import Mapping

from lite_bootstrap.exceptions import ConfigurationError
from lite_bootstrap.helpers.warn import warn_at_caller
from lite_bootstrap.helpers.warn import NEXT_MAJOR_VERSION, warn_at_caller
from lite_bootstrap.types import UNSET, UnsetType


Expand Down Expand Up @@ -40,7 +40,8 @@ def __post_init__(self) -> None:
msg = f"{old_name} and {new_name} are both set; {old_name} is deprecated, set only {new_name}."
raise ConfigurationError(msg)
warn_at_caller(
f"{old_name} is deprecated and will be removed in lite-bootstrap 2.0; use {new_name} instead.",
f"{old_name} is deprecated and will be removed in lite-bootstrap {NEXT_MAJOR_VERSION}; "
f"use {new_name} instead.",
category=DeprecationWarning,
)
object.__setattr__(self, new_name, old_value)
Expand All @@ -63,31 +64,12 @@ def from_object(cls, obj: object) -> typing.Self:
ConfigT = typing.TypeVar("ConfigT", bound=BaseConfig)


# Legacy name -> current name; __init_subclass__ forwards an out-of-tree instrument's old spelling.
_RENAMED_ATTRIBUTES: typing.Final = {
"not_ready_message": "not_configured_reason",
"check_dependencies": "dependencies_installed",
}


@dataclasses.dataclass(kw_only=True, slots=True)
class BaseInstrument(typing.Generic[ConfigT]):
bootstrap_config: ConfigT
not_configured_reason = ""
missing_dependency_message = ""

def __init_subclass__(cls, **kwargs: object) -> None:
# @dataclass(slots=True) replaces the class object, breaking bare super().
super(BaseInstrument, cls).__init_subclass__(**kwargs)
for legacy_name, current_name in _RENAMED_ATTRIBUTES.items():
if legacy_name in cls.__dict__ and current_name not in cls.__dict__:
warn_at_caller(
f"{cls.__name__}.{legacy_name} is deprecated and will be removed in lite-bootstrap 2.0; "
f"use {current_name} instead.",
category=DeprecationWarning,
)
setattr(cls, current_name, cls.__dict__[legacy_name])

def bootstrap(self) -> None: ...

def teardown(self) -> None: ...
Expand Down
30 changes: 0 additions & 30 deletions lite_bootstrap/instruments/logging_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,12 @@
import typing

from lite_bootstrap import import_checker
from lite_bootstrap.helpers.warn import deprecated_module_getattr


if import_checker.is_orjson_installed:
import orjson


_ScopeType = typing.MutableMapping[str, typing.Any]


class _AddressProtocol(typing.Protocol):
host: str
port: int


class _RequestProtocol(typing.Protocol):
client: _AddressProtocol
scope: _ScopeType
method: str


if typing.TYPE_CHECKING:
ScopeType = _ScopeType
AddressProtocol = _AddressProtocol
RequestProtocol = _RequestProtocol

_DEPRECATED_PROTOCOLS: typing.Final = {
"AddressProtocol": (_AddressProtocol, None),
"RequestProtocol": (_RequestProtocol, None),
"ScopeType": (_ScopeType, None),
}


@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
class _MemoryLoggerFactoryConfig:
logging_buffer_capacity: int
Expand Down Expand Up @@ -163,6 +136,3 @@ def close_handlers(self) -> None:
target.close()
self._created_handlers.clear()
self._leveled_loggers.clear()


__getattr__ = deprecated_module_getattr(__name__, _DEPRECATED_PROTOCOLS)
13 changes: 0 additions & 13 deletions lite_bootstrap/instruments/logging_instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,9 @@

from lite_bootstrap import import_checker
from lite_bootstrap.exceptions import collect_teardown_errors
from lite_bootstrap.helpers.warn import deprecated_module_getattr
from lite_bootstrap.instruments.base import BaseConfig, BaseInstrument
from lite_bootstrap.instruments.logging_factory import (
_DEPRECATED_PROTOCOLS,
_AddressProtocol,
_MemoryLoggerFactoryConfig,
_RequestProtocol,
_ScopeType,
_serialize_log_to_string,
)

Expand Down Expand Up @@ -239,11 +234,3 @@ def teardown(self) -> None:
self._logger_factory.close_handlers()
finally:
self._logger_factory = None


if typing.TYPE_CHECKING:
AddressProtocol = _AddressProtocol
RequestProtocol = _RequestProtocol
ScopeType = _ScopeType

__getattr__ = deprecated_module_getattr(__name__, _DEPRECATED_PROTOCOLS)
8 changes: 1 addition & 7 deletions lite_bootstrap/instruments/opentelemetry_instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from lite_bootstrap import import_checker
from lite_bootstrap.exceptions import InstrumentDependencyMissingWarning, collect_teardown_errors
from lite_bootstrap.helpers.warn import deprecated_module_getattr, warn_at_caller
from lite_bootstrap.helpers.warn import warn_at_caller
from lite_bootstrap.instruments.base import BaseConfig, BaseInstrument


Expand Down Expand Up @@ -387,9 +387,3 @@ def teardown(self) -> None:
self._meter_provider.shutdown()
finally:
self._meter_provider = None


if typing.TYPE_CHECKING:
OpentelemetryConfig = OpenTelemetryConfig

__getattr__ = deprecated_module_getattr(__name__, {"OpentelemetryConfig": (OpenTelemetryConfig, "OpenTelemetryConfig")})
13 changes: 2 additions & 11 deletions lite_bootstrap/instruments/sentry_instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
from collections.abc import Mapping

from lite_bootstrap import import_checker
from lite_bootstrap.helpers.warn import deprecated_module_getattr, warn_at_caller
from lite_bootstrap.helpers.warn import warn_at_caller
from lite_bootstrap.instruments.base import BaseConfig, BaseInstrument
from lite_bootstrap.instruments.logging_factory import STRUCTLOG_META_KEYS, StructuredLogPayload
from lite_bootstrap.instruments.logging_factory import StructuredLogPayload


if typing.TYPE_CHECKING:
Expand Down Expand Up @@ -269,12 +269,3 @@ def teardown(self) -> None:
sentry_sdk.flush(timeout=2)
finally:
sentry_sdk.init()


if typing.TYPE_CHECKING:
IGNORED_STRUCTLOG_ATTRIBUTES: typing.Final = STRUCTLOG_META_KEYS

__getattr__ = deprecated_module_getattr(
__name__,
{"IGNORED_STRUCTLOG_ATTRIBUTES": (STRUCTLOG_META_KEYS, "STRUCTLOG_META_KEYS")},
)
48 changes: 0 additions & 48 deletions tests/instruments/test_base_instrument.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
import dataclasses

import pytest

from lite_bootstrap.instruments.base import BaseConfig, BaseInstrument


Expand All @@ -10,47 +6,3 @@ def test_base_instrument_defaults() -> None:
assert BaseInstrument.missing_dependency_message == ""
assert BaseInstrument.dependencies_installed() is True
assert BaseInstrument.is_configured(BaseConfig()) is True


def test_an_instrument_written_against_the_old_attribute_names_still_works() -> None:
"""INVARIANT: a subclass declaring not_ready_message or check_dependencies keeps both behaviours.

Both names are part of the instrument protocol documented in `docs/introduction/configuration.md`,
so an out-of-tree instrument may define either. What breaks it is renaming them without the
forwarding in `BaseInstrument.__init_subclass__`: the bootstrapper would read the inherited empty
`not_configured_reason` and report no skip reason, and — far worse — it would call the inherited
`dependencies_installed`, which answers True, and construct an instrument whose optional package
is absent.
"""
with pytest.warns(DeprecationWarning, match="is deprecated") as record:

@dataclasses.dataclass(kw_only=True, slots=True)
class LegacyInstrument(BaseInstrument[BaseConfig]):
not_ready_message = "legacy reason"

@staticmethod
def check_dependencies() -> bool:
return False

assert LegacyInstrument.not_configured_reason == "legacy reason"
assert LegacyInstrument.dependencies_installed() is False
assert [str(warning.message) for warning in record] == [
(
"LegacyInstrument.not_ready_message is deprecated and will be removed in lite-bootstrap 2.0; "
"use not_configured_reason instead."
),
(
"LegacyInstrument.check_dependencies is deprecated and will be removed in lite-bootstrap 2.0; "
"use dependencies_installed instead."
),
]
assert {warning.filename for warning in record} == {__file__}


def test_a_subclass_declaring_both_names_keeps_the_current_one() -> None:
@dataclasses.dataclass(kw_only=True, slots=True)
class BothNamesInstrument(BaseInstrument[BaseConfig]):
not_ready_message = "legacy reason"
not_configured_reason = "current reason"

assert BothNamesInstrument.not_configured_reason == "current reason"
Loading
Loading