From 8143f4097c800b6b1ddc4744351a701644eacbb0 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Wed, 4 Nov 2026 12:43:33 +0300 Subject: [PATCH 1/2] perf: lazily import public framework exports (#403) Keep exceptions eager and load framework and Pyroscope exports on first access. Preserve __all__ and TYPE_CHECKING imports. Subprocess isolation regressions failed before the change and pass afterward. Python 3.14.7, local all-extras environment, seven fresh processes per command with -X importtime, median elapsed import time measured inside each process: - import lite_bootstrap: 680.95 -> 12.91 ms - from lite_bootstrap import FastAPIBootstrapper: 677.70 -> 273.73 ms - from lite_bootstrap import ConfigurationError: 730.97 -> 13.02 ms - from lite_bootstrap import PyroscopeConfig: 681.73 -> 90.14 ms Validation: 617 tests passed, 100% coverage; lint and ty passed. Standards and spec reviews found no issues. --- lite_bootstrap/__init__.py | 44 +++++++++++++++++++--- tests/test_core_import_surface.py | 62 +++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 6 deletions(-) diff --git a/lite_bootstrap/__init__.py b/lite_bootstrap/__init__.py index 372f3ed..079adac 100644 --- a/lite_bootstrap/__init__.py +++ b/lite_bootstrap/__init__.py @@ -1,8 +1,6 @@ -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 -from lite_bootstrap.bootstrappers.free_bootstrapper import FreeBootstrapper, FreeConfig -from lite_bootstrap.bootstrappers.litestar_bootstrapper import LitestarBootstrapper, LitestarConfig +import typing +from importlib import import_module + from lite_bootstrap.exceptions import ( BootstrapperNotReadyError, ConfigurationError, @@ -10,7 +8,15 @@ LiteBootstrapError, TeardownError, ) -from lite_bootstrap.instruments.pyroscope_instrument import PyroscopeConfig, PyroscopeInstrument + + +if typing.TYPE_CHECKING: + 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 + from lite_bootstrap.bootstrappers.free_bootstrapper import FreeBootstrapper, FreeConfig + from lite_bootstrap.bootstrappers.litestar_bootstrapper import LitestarBootstrapper, LitestarConfig + from lite_bootstrap.instruments.pyroscope_instrument import PyroscopeConfig, PyroscopeInstrument __all__ = [ @@ -32,3 +38,29 @@ "PyroscopeInstrument", "TeardownError", ] + + +_LAZY_EXPORTS = { + "FastAPIBootstrapper": "bootstrappers.fastapi_bootstrapper", + "FastAPIConfig": "bootstrappers.fastapi_bootstrapper", + "FastMcpBootstrapper": "bootstrappers.fastmcp_bootstrapper", + "FastMcpConfig": "bootstrappers.fastmcp_bootstrapper", + "FastStreamBootstrapper": "bootstrappers.faststream_bootstrapper", + "FastStreamConfig": "bootstrappers.faststream_bootstrapper", + "FreeBootstrapper": "bootstrappers.free_bootstrapper", + "FreeConfig": "bootstrappers.free_bootstrapper", + "LitestarBootstrapper": "bootstrappers.litestar_bootstrapper", + "LitestarConfig": "bootstrappers.litestar_bootstrapper", + "PyroscopeConfig": "instruments.pyroscope_instrument", + "PyroscopeInstrument": "instruments.pyroscope_instrument", +} + + +def __getattr__(name: str) -> typing.Any: # noqa: ANN401 + module_name = _LAZY_EXPORTS.get(name) + if module_name is None: + msg = f"module {__name__!r} has no attribute {name!r}" + raise AttributeError(msg) + exported = getattr(import_module(f".{module_name}", __name__), name) + globals()[name] = exported + return exported diff --git a/tests/test_core_import_surface.py b/tests/test_core_import_surface.py index 14ccb56..657d3a5 100644 --- a/tests/test_core_import_surface.py +++ b/tests/test_core_import_surface.py @@ -4,6 +4,7 @@ import subprocess import sys +import pytest import typing_extensions import lite_bootstrap @@ -95,3 +96,64 @@ def test_import_with_bare_opentelemetry_namespace(tmp_path: pathlib.Path) -> Non ) assert result.returncode == 0, result.stderr + + +@pytest.mark.parametrize( + "statement", + [ + "import lite_bootstrap", + "from lite_bootstrap import ConfigurationError", + "from lite_bootstrap import PyroscopeConfig", + "from lite_bootstrap import FastAPIBootstrapper", + ], +) +def test_public_imports_do_not_load_unrelated_frameworks(statement: str) -> None: + result = subprocess.run( # noqa: S603 + [ + sys.executable, + "-c", + statement + + """ +import sys +assert not {"litestar", "faststream", "fastmcp"}.intersection(sys.modules) +""", + ], + capture_output=True, + text=True, + check=False, + ) + assert result.returncode == 0, result.stderr + + +def test_every_public_export_resolves_in_a_fresh_process() -> None: + result = subprocess.run( + [ + sys.executable, + "-c", + """ +import lite_bootstrap + +for name in lite_bootstrap.__all__: + exported = getattr(lite_bootstrap, name) + assert exported.__name__ == name, name + assert exported is getattr(lite_bootstrap, name), name + +try: + getattr(lite_bootstrap, "not_an_export") +except AttributeError as error: + assert "not_an_export" in str(error) +else: + raise AssertionError("unknown export resolved") +""", + ], + capture_output=True, + text=True, + check=False, + ) + assert result.returncode == 0, result.stderr + + +def test_unknown_public_export_raises_attribute_error() -> None: + name = "not_an_export" + with pytest.raises(AttributeError, match=name): + getattr(lite_bootstrap, name) From 1cee96c64e893430c817e1459c632ca515059366 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Wed, 4 Nov 2026 13:02:27 +0300 Subject: [PATCH 2/2] fix: preserve discovery of lazy public exports --- lite_bootstrap/__init__.py | 4 ++++ tests/test_core_import_surface.py | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/lite_bootstrap/__init__.py b/lite_bootstrap/__init__.py index 079adac..b2fc8f9 100644 --- a/lite_bootstrap/__init__.py +++ b/lite_bootstrap/__init__.py @@ -64,3 +64,7 @@ def __getattr__(name: str) -> typing.Any: # noqa: ANN401 exported = getattr(import_module(f".{module_name}", __name__), name) globals()[name] = exported return exported + + +def __dir__() -> list[str]: + return sorted(set(globals()) | set(__all__)) diff --git a/tests/test_core_import_surface.py b/tests/test_core_import_surface.py index 657d3a5..1fca672 100644 --- a/tests/test_core_import_surface.py +++ b/tests/test_core_import_surface.py @@ -157,3 +157,28 @@ def test_unknown_public_export_raises_attribute_error() -> None: name = "not_an_export" with pytest.raises(AttributeError, match=name): getattr(lite_bootstrap, name) + + +def test_public_exports_are_discoverable_without_loading_frameworks() -> None: + result = subprocess.run( + [ + sys.executable, + "-c", + """ +import sys +import lite_bootstrap + +before = set(sys.modules) +names = dir(lite_bootstrap) +assert set(lite_bootstrap.__all__).issubset(names) +assert "__name__" in names +assert set(sys.modules) == before +assert not {"fastapi", "litestar", "faststream", "fastmcp"}.intersection(sys.modules) +""", + ], + capture_output=True, + text=True, + check=False, + ) + assert result.returncode == 0, result.stderr + assert set(lite_bootstrap.__all__).issubset(dir(lite_bootstrap))