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

Full refactoring. #1

Merged
merged 46 commits into from
Aug 2, 2024
Merged

Full refactoring. #1

merged 46 commits into from
Aug 2, 2024

Conversation

insani7y
Copy link
Member

Tests are broken yet.

@insani7y insani7y changed the title Refactor bootstrapper base class and add sentry instrument. Full refactoring. Jul 12, 2024
@vrslev vrslev self-requested a review July 14, 2024 10:03
Copy link
Contributor

@vrslev vrslev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The design is much better now 🔥
I don’t have critical objections, but may add more suggestions once the whole thing gets done.

microbootstrap/bootstrappers/base.py Outdated Show resolved Hide resolved
microbootstrap/instruments/base.py Outdated Show resolved Hide resolved
microbootstrap/settings/base.py Outdated Show resolved Hide resolved
@vrslev
Copy link
Contributor

vrslev commented Jul 19, 2024

Perhaps, it's a good idea to change abstractions a bit to something like this:

from contextlib import ExitStack, contextmanager
from functools import partial
from typing import (
    TYPE_CHECKING,
    Any,
    Callable,
    ContextManager,
    Generator,
    Protocol,
    TypedDict,
    TypeVar,
)

if TYPE_CHECKING:

    def merge_dict_configs(a: Any, b: Any) -> Any: ...
    def merge_pydantic_configs(a: Any, b: Any) -> Any: ...

    class Litestar: ...

    class OpenTelemetryInstrumentationMiddleware: ...

    class LitestarOpentelemetryConfig: ...


InstrumentConfig = TypeVar("InstrumentConfig")
InstrumentResult = TypeVar("InstrumentResult")


class Instrument(ContextManager, Protocol[InstrumentConfig, InstrumentResult]):
    def __enter__(self, config: InstrumentConfig) -> InstrumentResult: ...


class OpenTelemetryConfig: ...


class OpenTelemetryInstrumentResult(TypedDict):
    tracer_provider: Any
    exclude_urls: list[str]


@contextmanager
def instrument_opentelemetry(
    config: OpenTelemetryConfig,
) -> Generator[OpenTelemetryInstrumentResult, None, None]:
    yield {"tracer_provider": ..., "exclude_urls": []}


ApplicationT = TypeVar("ApplicationT")
ApplicationConfigT = TypeVar("ApplicationConfigT")


class Configurator(Protocol[ApplicationT, ApplicationConfigT]):
    def make_application_from_config(
        self, config: ApplicationConfigT
    ) -> ApplicationT: ...
    def configure_opentelemetry(
        self, result: OpenTelemetryInstrumentResult
    ) -> None: ...
    def configure_teardown(self, teardown: Callable[[], None]) -> None: ...


class Settings: ...


def bootstrap(
    configurator: Configurator[ApplicationT, ApplicationConfigT],
    settings: Settings,
    *,
    application_config: ApplicationConfigT | None = None,
    opentelemetry_config: OpenTelemetryConfig | None = None,
) -> ApplicationT:
    exit_stack = ExitStack()
    full_application_config = application_config

    for instrument_config, instrument, framework_adapter in [
        (
            opentelemetry_config,
            instrument_opentelemetry,
            configurator.configure_opentelemetry,
        )
    ]:
        config_piece = framework_adapter(
            instrument(merge_pydantic_configs(settings, instrument_config))
        )
        full_application_config = merge_dict_configs(
            full_application_config, exit_stack.enter_context(config=config_piece)
        )

    full_application_config = merge_dict_configs(
        full_application_config, configurator.configure_teardown(exit_stack.close)
    )
    return configurator.make_application_from_config(full_application_config)


class LitestarConfigurator(Configurator[Litestar, dict[str, Any]]):
    def make_application_from_config(self, config: dict[str, Any]) -> Any:
        return Litestar(**config)

    def configure_opentelemetry(self, result: OpenTelemetryInstrumentResult) -> None:
        return {
            "middleware": OpenTelemetryInstrumentationMiddleware(
                LitestarOpentelemetryConfig(
                    tracer_provider=result["tracer_provider"],
                    exclude=result["exclude_urls"],
                ),
            ),
        }

    def configure_teardown(self, teardown: Callable[[], None]) -> None:
        return {"on_shutdown": [teardown]}


bootstrap_litestar = partial(bootstrap, configurator=LitestarConfigurator())


def main() -> None:
    application = bootstrap_litestar(Settings())
  1. Instrument is a context managers that returns some result, that can be adapted for any framework.
  2. Configurator is an adapter from instruments to application config.
  3. There are no bootstrappers, just a function that gathers it all together.

@insani7y insani7y self-assigned this Jul 20, 2024
@insani7y
Copy link
Member Author

Tests are working! But there is much to be done still

Copy link

codecov bot commented Jul 24, 2024

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

ℹ️ You can also turn on project coverage checks and project coverage reporting on Pull Request comment

Thanks for integrating Codecov - We've got you covered ☂️

@Mernus Mernus self-requested a review July 29, 2024 06:24
@Mernus
Copy link

Mernus commented Jul 29, 2024

First at all, thank you for your work. Package are really getting better.
I like current structure and package usage.

@Mernus
Copy link

Mernus commented Jul 29, 2024

However, I have some suggestions and questions, and I am hopeful that they will only make better the package.

  1. bootstrap_before and bootstrap_after. Names of this methods dont tell me about ist usages. It provides some config data to app, but i can know this only if i read usage from docs, code or docstrings of this methods. I dont know how to name it better by now, but if any thoughts come to me - i will write them down.
  2. typing.Final - most code in package typed with Final, but not all.

microbootstrap/instruments/opentelemery_instrument.py Outdated Show resolved Hide resolved
microbootstrap/granian_server.py Outdated Show resolved Hide resolved
microbootstrap/granian_server.py Show resolved Hide resolved
microbootstrap/granian_server.py Show resolved Hide resolved
microbootstrap/exceptions.py Outdated Show resolved Hide resolved
microbootstrap/instruments/logging_instrument.py Outdated Show resolved Hide resolved
microbootstrap/instruments/opentelemery_instrument.py Outdated Show resolved Hide resolved
microbootstrap/instruments/opentelemery_instrument.py Outdated Show resolved Hide resolved
microbootstrap/instruments/opentelemery_instrument.py Outdated Show resolved Hide resolved
microbootstrap/helpers.py Outdated Show resolved Hide resolved
Copy link

@Mernus Mernus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All good for now I think, thanks!

@insani7y
Copy link
Member Author

insani7y commented Aug 2, 2024

LGTM!

@insani7y insani7y merged commit a0c6e41 into main Aug 2, 2024
14 checks passed
@insani7y insani7y deleted the feature/full-refactor branch August 2, 2024 20:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants