From a6bf51aef3cc7c34f103f64e98dd25657e5ca10e Mon Sep 17 00:00:00 2001 From: reqww Date: Tue, 21 Oct 2025 12:26:27 +0300 Subject: [PATCH 1/4] Support faststream 0.6 --- .github/workflows/workflow.yml | 2 +- docs/.vuepress/config.js | 28 ------------- docs/README.md | 33 ---------------- docs/get-started.md | 46 ---------------------- microbootstrap/bootstrappers/faststream.py | 19 +++++---- 5 files changed, 13 insertions(+), 115 deletions(-) delete mode 100644 docs/.vuepress/config.js delete mode 100644 docs/README.md delete mode 100644 docs/get-started.md diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 88ae1c1..0c62682 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -15,5 +15,5 @@ jobs: ci: uses: community-of-python/community-workflow/.github/workflows/preset.yml@main with: - python-version: '["3.10","3.11","3.12","3.13"]' + python-version: '["3.10","3.11","3.12","3.13", "3.14"]' secrets: inherit diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js deleted file mode 100644 index 354e52d..0000000 --- a/docs/.vuepress/config.js +++ /dev/null @@ -1,28 +0,0 @@ -import {defaultTheme} from "@vuepress/theme-default"; -import {defineUserConfig} from "vuepress/cli"; -import {viteBundler} from "@vuepress/bundler-vite"; - -export default defineUserConfig({ - lang: "en-US", - - title: "VuePress", - description: "My first VuePress Site", - base: "/microbootstrap/", - - theme: defaultTheme({ - repo: "community-of-python/microbootstrap", - repoLabel: "GitHub", - repoDisplay: true, - hostname: "https://community-of-python.github.io/", - - logo: "https://vuejs.press/images/hero.png", - - navbar: ["/", "/get-started"], - }), - - bundler: viteBundler({ - viteOptions: { - base: "https://community-of-python.github.io/assets/microbootstrap/", - }, - }), -}); diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 4080e4b..0000000 --- a/docs/README.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -home: true -title: Home -heroImage: https://vuejs.press/images/hero.png -actions: - - text: Get Started - link: /getting-started.html - type: primary - - - text: Introduction - link: https://vuejs.press/guide/introduction.html - type: secondary - -features: - - title: Simplicity First - details: Minimal setup with markdown-centered project structure helps you focus on writing. - - title: Vue-Powered - details: Enjoy the dev experience of Vue, use Vue components in markdown, and develop custom themes with Vue. - - title: Performant - details: VuePress generates pre-rendered static HTML for each page, and runs as an SPA once a page is loaded. - - title: Themes - details: Providing a default theme out of the box. You can also choose a community theme or create your own one. - - title: Plugins - details: Flexible plugin API, allowing plugins to provide lots of plug-and-play features for your site. - - title: Bundlers - details: Default bundler is Vite, while Webpack is also supported. Choose the one you like! - -footer: MIT Licensed | Copyright © 2018-present VuePress Community ---- - -This is the content of home page. Check [Home Page Docs][default-theme-home] for more details. - -[default-theme-home]: https://vuejs.press/reference/default-theme/frontmatter.html#home-page diff --git a/docs/get-started.md b/docs/get-started.md deleted file mode 100644 index 0b7b031..0000000 --- a/docs/get-started.md +++ /dev/null @@ -1,46 +0,0 @@ -# Get Started - -This is a normal page, which contains VuePress basics. - -## Pages - -You can add markdown files in your vuepress directory, every markdown file will be converted to a page in your site. - -See [routing][] for more details. - -## Content - -Every markdown file [will be rendered to HTML, then converted to a Vue SFC][content]. - -VuePress support basic markdown syntax and [some extensions][synatex-extensions], you can also [use Vue features][vue-feature] in it. - -## Configuration - -VuePress use a `.vuepress/config.js`(or .ts) file as [site configuration][config], you can use it to config your site. - -For [client side configuration][client-config], you can create `.vuepress/client.js`(or .ts). - -Meanwhile, you can also add configuration per page with [frontmatter][]. - -## Layouts and customization - -Here are common configuration controlling layout of `@vuepress/theme-default`: - -- [navbar][] -- [sidebar][] - -Check [default theme docs][default-theme] for full reference. - -You can [add extra style][style] with `.vuepress/styles/index.scss` file. - -[routing]: https://vuejs.press/guide/page.html#routing -[content]: https://vuejs.press/guide/page.html#content -[synatex-extensions]: https://vuejs.press/guide/markdown.html#syntax-extensions -[vue-feature]: https://vuejs.press/guide/markdown.html#using-vue-in-markdown -[config]: https://vuejs.press/guide/configuration.html#client-config-file -[client-config]: https://vuejs.press/guide/configuration.html#client-config-file -[frontmatter]: https://vuejs.press/guide/page.html#frontmatter -[navbar]: https://vuejs.press/reference/default-theme/config.html#navbar -[sidebar]: https://vuejs.press/reference/default-theme/config.html#sidebar -[default-theme]: https://vuejs.press/reference/default-theme/ -[style]: https://vuejs.press/reference/default-theme/styles.html#style-file diff --git a/microbootstrap/bootstrappers/faststream.py b/microbootstrap/bootstrappers/faststream.py index 37cc01e..23e3d5d 100644 --- a/microbootstrap/bootstrappers/faststream.py +++ b/microbootstrap/bootstrappers/faststream.py @@ -7,6 +7,7 @@ import typing_extensions from faststream.asgi import AsgiFastStream, AsgiResponse from faststream.asgi import get as handle_get +from faststream.specification import AsyncAPI from microbootstrap.bootstrappers.base import ApplicationBootstrapper from microbootstrap.config.faststream import FastStreamConfig @@ -34,9 +35,11 @@ class FastStreamBootstrapper(ApplicationBootstrapper[FastStreamSettings, AsgiFas def bootstrap_before(self: typing_extensions.Self) -> dict[str, typing.Any]: return { - "title": self.settings.service_name, - "version": self.settings.service_version, - "description": self.settings.service_description, + "specification": AsyncAPI( + title=self.settings.service_name, + version=self.settings.service_version, + description=self.settings.service_description, + ), "on_shutdown": [self.teardown], "on_startup": [self.console_writer.print_bootstrap_table], "asyncapi_path": self.settings.asyncapi_path, @@ -55,7 +58,7 @@ def is_ready(self) -> bool: def bootstrap_after(self, application: AsgiFastStream) -> AsgiFastStream: # type: ignore[override] if self.instrument_config.opentelemetry_middleware_cls and application.broker: application.broker.add_middleware( - self.instrument_config.opentelemetry_middleware_cls(tracer_provider=self.tracer_provider) + self.instrument_config.opentelemetry_middleware_cls(tracer_provider=self.tracer_provider), ) return application @@ -82,13 +85,13 @@ def bootstrap_before(self) -> dict[str, typing.Any]: self.instrument_config.prometheus_metrics_path, prometheus_client.make_asgi_app(prometheus_client.REGISTRY), ), - ) + ), } def bootstrap_after(self, application: AsgiFastStream) -> AsgiFastStream: # type: ignore[override] if self.instrument_config.prometheus_middleware_cls and application.broker: application.broker.add_middleware( - self.instrument_config.prometheus_middleware_cls(registry=prometheus_client.REGISTRY) + self.instrument_config.prometheus_middleware_cls(registry=prometheus_client.REGISTRY), ) return application @@ -105,7 +108,9 @@ def bootstrap_before(self) -> dict[str, typing.Any]: async def check_health(scope: typing.Any) -> AsgiResponse: # noqa: ANN401, ARG001 return ( AsgiResponse( - json.dumps(self.render_health_check_data()).encode(), 200, headers={"content-type": "text/plain"} + json.dumps(self.render_health_check_data()).encode(), + 200, + headers={"content-type": "text/plain"}, ) if await self.define_health_status() else AsgiResponse(b"Service is unhealthy", 500, headers={"content-type": "application/json"}) From 97f935a9a86ed1269546dcf476813644d62a54ac Mon Sep 17 00:00:00 2001 From: reqww Date: Tue, 21 Oct 2025 12:43:43 +0300 Subject: [PATCH 2/4] Support faststream 0.6 --- microbootstrap/bootstrappers/fastapi.py | 4 ++- microbootstrap/config/faststream.py | 34 +++++++++++-------- microbootstrap/exceptions.py | 4 +++ .../instruments/opentelemetry_instrument.py | 7 ++-- .../instruments/prometheus_instrument.py | 5 ++- 5 files changed, 31 insertions(+), 23 deletions(-) diff --git a/microbootstrap/bootstrappers/fastapi.py b/microbootstrap/bootstrappers/fastapi.py index b567bf4..ff4c121 100644 --- a/microbootstrap/bootstrappers/fastapi.py +++ b/microbootstrap/bootstrappers/fastapi.py @@ -7,6 +7,7 @@ from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor from prometheus_fastapi_instrumentator import Instrumentator +from microbootstrap import exceptions from microbootstrap.bootstrappers.base import ApplicationBootstrapper from microbootstrap.config.fastapi import FastApiConfig from microbootstrap.instruments.cors_instrument import CorsInstrument @@ -40,7 +41,8 @@ async def _lifespan_manager(self, _: fastapi.FastAPI) -> typing.AsyncIterator[No @contextlib.asynccontextmanager async def _wrapped_lifespan_manager(self, app: fastapi.FastAPI) -> typing.AsyncIterator[None]: - assert self.application_config.lifespan # noqa: S101 + if not self.application_config.lifespan: + raise exceptions.BootstrapperConfigurationError("FastAPI Lifespan is missing.") async with self._lifespan_manager(app), self.application_config.lifespan(app): yield None diff --git a/microbootstrap/config/faststream.py b/microbootstrap/config/faststream.py index 6e790bd..99ce2a4 100644 --- a/microbootstrap/config/faststream.py +++ b/microbootstrap/config/faststream.py @@ -4,24 +4,28 @@ if typing.TYPE_CHECKING: - import faststream.asyncapi.schema as asyncapi - from faststream.asgi.types import ASGIApp - from faststream.broker.core.usecase import BrokerUsecase - from faststream.types import AnyDict, AnyHttpUrl, Lifespan + from fast_depends import Provider + from fast_depends.library.serializer import SerializerProto + from faststream._internal.basic_types import ( + AnyCallable, + Lifespan, + LoggerProto, + ) + from faststream._internal.broker import BrokerUsecase + from faststream._internal.context import ContextRepo + from faststream.specification.base import SpecificationFactory @dataclasses.dataclass class FastStreamConfig: broker: BrokerUsecase[typing.Any, typing.Any] | None = None - asgi_routes: typing.Sequence[tuple[str, ASGIApp]] = () + logger: LoggerProto | None = None + provider: Provider | None = None + serializer: SerializerProto | None = None + context: ContextRepo | None = None lifespan: Lifespan | None = None - terms_of_service: AnyHttpUrl | None = None - license: asyncapi.License | asyncapi.LicenseDict | AnyDict | None = None - contact: asyncapi.Contact | asyncapi.ContactDict | AnyDict | None = None - tags: typing.Sequence[asyncapi.Tag | asyncapi.TagDict | AnyDict] | None = None - external_docs: asyncapi.ExternalDocs | asyncapi.ExternalDocsDict | AnyDict | None = None - identifier: str | None = None - on_startup: typing.Sequence[typing.Callable[..., typing.Any]] = () - after_startup: typing.Sequence[typing.Callable[..., typing.Any]] = () - on_shutdown: typing.Sequence[typing.Callable[..., typing.Any]] = () - after_shutdown: typing.Sequence[typing.Callable[..., typing.Any]] = () + on_startup: typing.Sequence[AnyCallable] = () + after_startup: typing.Sequence[AnyCallable] = () + on_shutdown: typing.Sequence[AnyCallable] = () + after_shutdown: typing.Sequence[AnyCallable] = () + specification: SpecificationFactory | None = None diff --git a/microbootstrap/exceptions.py b/microbootstrap/exceptions.py index 8f19d19..8f6b4df 100644 --- a/microbootstrap/exceptions.py +++ b/microbootstrap/exceptions.py @@ -8,3 +8,7 @@ class ConfigMergeError(MicroBootstrapBaseError): class MissingInstrumentError(MicroBootstrapBaseError): """Raises when attempting to configure instrument, that is not supported yet.""" + + +class BootstrapperConfigurationError(MicroBootstrapBaseError): + """Raises when there is something wrong with bootstrapper configuration.""" diff --git a/microbootstrap/instruments/opentelemetry_instrument.py b/microbootstrap/instruments/opentelemetry_instrument.py index 15802ee..1d40301 100644 --- a/microbootstrap/instruments/opentelemetry_instrument.py +++ b/microbootstrap/instruments/opentelemetry_instrument.py @@ -6,6 +6,7 @@ import pydantic import structlog +from faststream._internal.types import BrokerMiddleware from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter from opentelemetry.instrumentation.dependencies import DependencyConflictError from opentelemetry.instrumentation.environment_variables import OTEL_PYTHON_DISABLED_INSTRUMENTATIONS @@ -31,7 +32,6 @@ if typing.TYPE_CHECKING: - import faststream from opentelemetry.context import Context from opentelemetry.metrics import Meter, MeterProvider from opentelemetry.trace import TracerProvider @@ -64,13 +64,13 @@ class OpentelemetryConfig(BaseInstrumentConfig): default=[ one_package_to_exclude.strip() for one_package_to_exclude in os.environ.get(OTEL_PYTHON_DISABLED_INSTRUMENTATIONS, "").split(",") - ] + ], ) opentelemetry_log_traces: bool = False @typing.runtime_checkable -class FastStreamTelemetryMiddlewareProtocol(typing.Protocol): +class FastStreamTelemetryMiddlewareProtocol(BrokerMiddleware, typing.Protocol): def __init__( self, *, @@ -78,7 +78,6 @@ def __init__( meter_provider: MeterProvider | None = None, meter: Meter | None = None, ) -> None: ... - def __call__(self, msg: typing.Any | None) -> faststream.BaseMiddleware: ... # noqa: ANN401 class FastStreamOpentelemetryConfig(OpentelemetryConfig): diff --git a/microbootstrap/instruments/prometheus_instrument.py b/microbootstrap/instruments/prometheus_instrument.py index b7737cf..dc84795 100644 --- a/microbootstrap/instruments/prometheus_instrument.py +++ b/microbootstrap/instruments/prometheus_instrument.py @@ -2,13 +2,13 @@ import typing import pydantic +from faststream._internal.types import BrokerMiddleware from microbootstrap.helpers import is_valid_path from microbootstrap.instruments.base import BaseInstrumentConfig, Instrument if typing.TYPE_CHECKING: - import faststream import prometheus_client @@ -33,7 +33,7 @@ class FastApiPrometheusConfig(BasePrometheusConfig): @typing.runtime_checkable -class FastStreamPrometheusMiddlewareProtocol(typing.Protocol): +class FastStreamPrometheusMiddlewareProtocol(BrokerMiddleware, typing.Protocol): def __init__( self, *, @@ -42,7 +42,6 @@ def __init__( metrics_prefix: str = "faststream", received_messages_size_buckets: typing.Sequence[float] | None = None, ) -> None: ... - def __call__(self, msg: typing.Any | None) -> faststream.BaseMiddleware: ... # noqa: ANN401 class FastStreamPrometheusConfig(BasePrometheusConfig): From 3770bd7472858bf96d67a1e9e007932ea1336301 Mon Sep 17 00:00:00 2001 From: reqww Date: Tue, 21 Oct 2025 12:44:15 +0300 Subject: [PATCH 3/4] Support faststream 0.6 --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 02b5193..0a61aa1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ] dependencies = [ "eval-type-backport>=0.2", From a55b14a461a54d4a70c91c0789ffd9c05d71f97b Mon Sep 17 00:00:00 2001 From: reqww Date: Tue, 21 Oct 2025 12:52:11 +0300 Subject: [PATCH 4/4] Support faststream 0.6 --- microbootstrap/bootstrappers/fastapi.py | 4 +--- microbootstrap/exceptions.py | 4 ---- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/microbootstrap/bootstrappers/fastapi.py b/microbootstrap/bootstrappers/fastapi.py index ff4c121..b567bf4 100644 --- a/microbootstrap/bootstrappers/fastapi.py +++ b/microbootstrap/bootstrappers/fastapi.py @@ -7,7 +7,6 @@ from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor from prometheus_fastapi_instrumentator import Instrumentator -from microbootstrap import exceptions from microbootstrap.bootstrappers.base import ApplicationBootstrapper from microbootstrap.config.fastapi import FastApiConfig from microbootstrap.instruments.cors_instrument import CorsInstrument @@ -41,8 +40,7 @@ async def _lifespan_manager(self, _: fastapi.FastAPI) -> typing.AsyncIterator[No @contextlib.asynccontextmanager async def _wrapped_lifespan_manager(self, app: fastapi.FastAPI) -> typing.AsyncIterator[None]: - if not self.application_config.lifespan: - raise exceptions.BootstrapperConfigurationError("FastAPI Lifespan is missing.") + assert self.application_config.lifespan # noqa: S101 async with self._lifespan_manager(app), self.application_config.lifespan(app): yield None diff --git a/microbootstrap/exceptions.py b/microbootstrap/exceptions.py index 8f6b4df..8f19d19 100644 --- a/microbootstrap/exceptions.py +++ b/microbootstrap/exceptions.py @@ -8,7 +8,3 @@ class ConfigMergeError(MicroBootstrapBaseError): class MissingInstrumentError(MicroBootstrapBaseError): """Raises when attempting to configure instrument, that is not supported yet.""" - - -class BootstrapperConfigurationError(MicroBootstrapBaseError): - """Raises when there is something wrong with bootstrapper configuration."""