microbootstrap 1.5.4__tar.gz → 1.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/PKG-INFO +64 -6
  2. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/README.md +59 -1
  3. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/__init__.py +20 -0
  4. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/bootstrappers/fastapi.py +42 -0
  5. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/bootstrappers/faststream.py +11 -1
  6. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/bootstrappers/litestar.py +117 -0
  7. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/helpers.py +1 -1
  8. microbootstrap-1.6.0/microbootstrap/instruments/openapi_security_schemes.py +127 -0
  9. microbootstrap-1.6.0/microbootstrap/instruments/openapi_version_docs.py +86 -0
  10. microbootstrap-1.6.0/microbootstrap/instruments/swagger_instrument.py +121 -0
  11. microbootstrap-1.6.0/microbootstrap/middlewares/faststream.py +66 -0
  12. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/pyproject.toml +5 -5
  13. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/pyproject.toml.orig +5 -5
  14. microbootstrap-1.5.4/microbootstrap/instruments/swagger_instrument.py +0 -30
  15. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/bootstrappers/__init__.py +0 -0
  16. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/bootstrappers/base.py +0 -0
  17. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/bootstrappers/fastmcp.py +0 -0
  18. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/config/__init__.py +0 -0
  19. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/config/fastapi.py +0 -0
  20. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/config/fastmcp.py +0 -0
  21. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/config/faststream.py +0 -0
  22. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/config/litestar.py +0 -0
  23. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/console_writer.py +0 -0
  24. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/exceptions.py +0 -0
  25. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/granian_server.py +0 -0
  26. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/instruments/__init__.py +0 -0
  27. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/instruments/base.py +0 -0
  28. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/instruments/cors_instrument.py +0 -0
  29. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/instruments/health_checks_instrument.py +0 -0
  30. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/instruments/instrument_box.py +0 -0
  31. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/instruments/logging_instrument.py +0 -0
  32. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/instruments/opentelemetry_instrument.py +0 -0
  33. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/instruments/prometheus_instrument.py +0 -0
  34. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/instruments/pyroscope_instrument.py +0 -0
  35. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/instruments/sentry_instrument.py +0 -0
  36. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/instruments_setupper.py +0 -0
  37. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/middlewares/__init__.py +0 -0
  38. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/middlewares/fastapi.py +0 -0
  39. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/middlewares/fastmcp.py +0 -0
  40. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/middlewares/litestar.py +0 -0
  41. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/py.typed +0 -0
  42. {microbootstrap-1.5.4 → microbootstrap-1.6.0}/microbootstrap/settings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: microbootstrap
3
- Version: 1.5.4
3
+ Version: 1.6.0
4
4
  Summary: Package for bootstrapping new micro-services
5
5
  Keywords: python,microservice,bootstrap,opentelemetry,logging,error-tracing,litestar,fastapi,fastmcp,mcp
6
6
  Author: community-of-python
@@ -43,16 +43,16 @@ Requires-Dist: opentelemetry-instrumentation-sqlalchemy>=0.54b1
43
43
  Requires-Dist: opentelemetry-instrumentation-asyncio>=0.54b1
44
44
  Requires-Dist: opentelemetry-instrumentation-asgi>=0.46b0
45
45
  Requires-Dist: orjson>=3.10.18
46
- Requires-Dist: fastapi>=0.100 ; extra == 'fastapi'
46
+ Requires-Dist: fastapi>=0.110.1 ; extra == 'fastapi'
47
47
  Requires-Dist: fastapi-offline-docs>=1 ; extra == 'fastapi'
48
48
  Requires-Dist: opentelemetry-instrumentation-fastapi>=0.46b0 ; extra == 'fastapi'
49
- Requires-Dist: prometheus-fastapi-instrumentator>=6.1 ; extra == 'fastapi'
49
+ Requires-Dist: prometheus-fastapi-instrumentator>=7.1 ; extra == 'fastapi'
50
50
  Requires-Dist: fastmcp>=2,<4 ; extra == 'fastmcp'
51
51
  Requires-Dist: prometheus-client>=0.20 ; extra == 'fastmcp'
52
- Requires-Dist: faststream~=0.6.2 ; extra == 'faststream'
52
+ Requires-Dist: faststream>=0.6.7,<0.8 ; extra == 'faststream'
53
53
  Requires-Dist: prometheus-client>=0.20 ; extra == 'faststream'
54
54
  Requires-Dist: granian[reload]>=1 ; extra == 'granian'
55
- Requires-Dist: litestar>=2.9 ; extra == 'litestar'
55
+ Requires-Dist: litestar>=2.21.1 ; extra == 'litestar'
56
56
  Requires-Dist: litestar-offline-docs>=1 ; extra == 'litestar'
57
57
  Requires-Dist: prometheus-client>=0.20 ; extra == 'litestar'
58
58
  Requires-Python: >=3.10, <4
@@ -105,7 +105,7 @@ With <b>microbootstrap</b>, you receive an application with lightweight built-in
105
105
  - `opentelemetry`
106
106
  - `logging`
107
107
  - `cors`
108
- - `swagger` - with additional offline version support
108
+ - `swagger` - offline UI assets, OpenAPI security definitions, and optional Accept-version documentation
109
109
  - `health-checks`
110
110
 
111
111
  Those instruments can be bootstrapped for:
@@ -659,6 +659,64 @@ Parameter descriptions:
659
659
  - `swagger_offline_docs` - A boolean value that, when set to True, allows the Swagger JS bundles to be accessed offline. This is because the service starts to host via static.
660
660
  - `swagger_extra_params` - Additional parameters to pass into the OpenAPI configuration.
661
661
 
662
+ #### OpenAPI security schemes
663
+
664
+ Security schemes are disabled by default. They add reusable OpenAPI definitions under
665
+ `components.securitySchemes`; they do not authenticate requests or add global or operation-level security requirements.
666
+ Keep requirements and authentication in your application routes and dependencies.
667
+
668
+ ```python
669
+ from microbootstrap import (
670
+ LitestarSettings,
671
+ OpenApiHttpSecurityScheme,
672
+ )
673
+
674
+
675
+ class Settings(LitestarSettings):
676
+ security_schemes: dict[str, OpenApiHttpSecurityScheme] = {
677
+ "serviceAuth": OpenApiHttpSecurityScheme(scheme="bearer", bearer_format="JWT"),
678
+ }
679
+ ```
680
+
681
+ HTTP, API key, OAuth 2.0, and OpenID Connect definitions are supported by `SwaggerConfig`. Annotating a consumer
682
+ setting with a concrete scheme class intentionally rejects other kinds for that consumer. Python field names and
683
+ OpenAPI aliases are accepted; output uses canonical names such as `bearerFormat`, `in`, `tokenUrl`, and
684
+ `openIdConnectUrl`. A same-named definition must be identical to the service-owned definition or schema generation
685
+ raises `ValueError`.
686
+
687
+ The definitions are added to the framework's normal schema. Repeated schema reads remain stable; after correcting
688
+ a configuration conflict, rebuild the application.
689
+
690
+ #### API-version documentation
691
+
692
+ Version documentation is disabled by default. It describes supported versions in OpenAPI without implementing
693
+ runtime version negotiation.
694
+
695
+ ```python
696
+ from microbootstrap import LitestarSettings, OpenApiOperationVersionOverride, OpenApiVersionDocsConfig
697
+
698
+
699
+ class Settings(LitestarSettings):
700
+ openapi_version_docs: OpenApiVersionDocsConfig | None = OpenApiVersionDocsConfig(
701
+ vendor_media_type="application/vnd.example+json",
702
+ supported_versions=("1.0",),
703
+ operation_versions=(
704
+ OpenApiOperationVersionOverride(path="/widgets", method="post", supported_versions=("2.0",)),
705
+ OpenApiOperationVersionOverride(path="/internal/widgets", method="get", supported_versions=()),
706
+ ),
707
+ )
708
+ ```
709
+
710
+ Set `openapi_version_docs` to `None` to disable version documentation. A configured non-empty global
711
+ `supported_versions` list adds an `x-accept-versioning` extension and matching description text to each operation.
712
+ `operation_versions` replaces that list for one exact path and lower-case HTTP method; an explicit empty tuple skips
713
+ microbootstrap's additions for that operation without asserting that no service-owned version metadata exists. This does
714
+ not negotiate requests, add an `Accept` parameter, change response media types, or provide a Swagger UI version selector.
715
+
716
+ Repeated schema reads remain stable. A conflicting service-owned `x-accept-versioning` extension raises `ValueError`;
717
+ correct the configuration and rebuild the application. Litestar supports its standard `Operation` type for version
718
+ documentation and rejects other custom operation subclasses.
719
+
662
720
  #### FastStream AsyncAPI documentation
663
721
 
664
722
  AsyncAPI documentation is available by default under `/asyncapi` route. You can change that by setting `asyncapi_path`:
@@ -40,7 +40,7 @@ With <b>microbootstrap</b>, you receive an application with lightweight built-in
40
40
  - `opentelemetry`
41
41
  - `logging`
42
42
  - `cors`
43
- - `swagger` - with additional offline version support
43
+ - `swagger` - offline UI assets, OpenAPI security definitions, and optional Accept-version documentation
44
44
  - `health-checks`
45
45
 
46
46
  Those instruments can be bootstrapped for:
@@ -594,6 +594,64 @@ Parameter descriptions:
594
594
  - `swagger_offline_docs` - A boolean value that, when set to True, allows the Swagger JS bundles to be accessed offline. This is because the service starts to host via static.
595
595
  - `swagger_extra_params` - Additional parameters to pass into the OpenAPI configuration.
596
596
 
597
+ #### OpenAPI security schemes
598
+
599
+ Security schemes are disabled by default. They add reusable OpenAPI definitions under
600
+ `components.securitySchemes`; they do not authenticate requests or add global or operation-level security requirements.
601
+ Keep requirements and authentication in your application routes and dependencies.
602
+
603
+ ```python
604
+ from microbootstrap import (
605
+ LitestarSettings,
606
+ OpenApiHttpSecurityScheme,
607
+ )
608
+
609
+
610
+ class Settings(LitestarSettings):
611
+ security_schemes: dict[str, OpenApiHttpSecurityScheme] = {
612
+ "serviceAuth": OpenApiHttpSecurityScheme(scheme="bearer", bearer_format="JWT"),
613
+ }
614
+ ```
615
+
616
+ HTTP, API key, OAuth 2.0, and OpenID Connect definitions are supported by `SwaggerConfig`. Annotating a consumer
617
+ setting with a concrete scheme class intentionally rejects other kinds for that consumer. Python field names and
618
+ OpenAPI aliases are accepted; output uses canonical names such as `bearerFormat`, `in`, `tokenUrl`, and
619
+ `openIdConnectUrl`. A same-named definition must be identical to the service-owned definition or schema generation
620
+ raises `ValueError`.
621
+
622
+ The definitions are added to the framework's normal schema. Repeated schema reads remain stable; after correcting
623
+ a configuration conflict, rebuild the application.
624
+
625
+ #### API-version documentation
626
+
627
+ Version documentation is disabled by default. It describes supported versions in OpenAPI without implementing
628
+ runtime version negotiation.
629
+
630
+ ```python
631
+ from microbootstrap import LitestarSettings, OpenApiOperationVersionOverride, OpenApiVersionDocsConfig
632
+
633
+
634
+ class Settings(LitestarSettings):
635
+ openapi_version_docs: OpenApiVersionDocsConfig | None = OpenApiVersionDocsConfig(
636
+ vendor_media_type="application/vnd.example+json",
637
+ supported_versions=("1.0",),
638
+ operation_versions=(
639
+ OpenApiOperationVersionOverride(path="/widgets", method="post", supported_versions=("2.0",)),
640
+ OpenApiOperationVersionOverride(path="/internal/widgets", method="get", supported_versions=()),
641
+ ),
642
+ )
643
+ ```
644
+
645
+ Set `openapi_version_docs` to `None` to disable version documentation. A configured non-empty global
646
+ `supported_versions` list adds an `x-accept-versioning` extension and matching description text to each operation.
647
+ `operation_versions` replaces that list for one exact path and lower-case HTTP method; an explicit empty tuple skips
648
+ microbootstrap's additions for that operation without asserting that no service-owned version metadata exists. This does
649
+ not negotiate requests, add an `Accept` parameter, change response media types, or provide a Swagger UI version selector.
650
+
651
+ Repeated schema reads remain stable. A conflicting service-owned `x-accept-versioning` extension raises `ValueError`;
652
+ correct the configuration and rebuild the application. Litestar supports its standard `Operation` type for version
653
+ documentation and rejects other custom operation subclasses.
654
+
597
655
  #### FastStream AsyncAPI documentation
598
656
 
599
657
  AsyncAPI documentation is available by default under `/asyncapi` route. You can change that by setting `asyncapi_path`:
@@ -1,6 +1,18 @@
1
1
  from microbootstrap.instruments.cors_instrument import CorsConfig
2
2
  from microbootstrap.instruments.health_checks_instrument import HealthChecksConfig
3
3
  from microbootstrap.instruments.logging_instrument import LoggingConfig
4
+ from microbootstrap.instruments.openapi_security_schemes import (
5
+ OpenApiApiKeySecurityScheme,
6
+ OpenApiHttpSecurityScheme,
7
+ OpenApiOAuth2SecurityScheme,
8
+ OpenApiOAuthFlow,
9
+ OpenApiOAuthFlows,
10
+ OpenApiOpenIdConnectSecurityScheme,
11
+ )
12
+ from microbootstrap.instruments.openapi_version_docs import (
13
+ OpenApiOperationVersionOverride,
14
+ OpenApiVersionDocsConfig,
15
+ )
4
16
  from microbootstrap.instruments.opentelemetry_instrument import (
5
17
  FastStreamOpentelemetryConfig,
6
18
  FastStreamTelemetryMiddlewareProtocol,
@@ -42,6 +54,14 @@ __all__ = (
42
54
  "LitestarPrometheusConfig",
43
55
  "LitestarSettings",
44
56
  "LoggingConfig",
57
+ "OpenApiApiKeySecurityScheme",
58
+ "OpenApiHttpSecurityScheme",
59
+ "OpenApiOAuth2SecurityScheme",
60
+ "OpenApiOAuthFlow",
61
+ "OpenApiOAuthFlows",
62
+ "OpenApiOpenIdConnectSecurityScheme",
63
+ "OpenApiOperationVersionOverride",
64
+ "OpenApiVersionDocsConfig",
45
65
  "OpentelemetryConfig",
46
66
  "PyroscopeConfig",
47
67
  "SentryConfig",
@@ -12,6 +12,8 @@ from microbootstrap.config.fastapi import FastApiConfig
12
12
  from microbootstrap.instruments.cors_instrument import CorsInstrument
13
13
  from microbootstrap.instruments.health_checks_instrument import HealthChecksInstrument, HealthCheckTypedDict
14
14
  from microbootstrap.instruments.logging_instrument import LoggingInstrument
15
+ from microbootstrap.instruments.openapi_security_schemes import serialize_security_schemes
16
+ from microbootstrap.instruments.openapi_version_docs import SUPPORTED_HTTP_METHODS
15
17
  from microbootstrap.instruments.opentelemetry_instrument import OpentelemetryInstrument
16
18
  from microbootstrap.instruments.prometheus_instrument import FastApiPrometheusConfig, PrometheusInstrument
17
19
  from microbootstrap.instruments.pyroscope_instrument import PyroscopeInstrument
@@ -67,8 +69,48 @@ class FastApiSwaggerInstrument(SwaggerInstrument):
67
69
  def bootstrap_after(self, application: ApplicationT) -> ApplicationT:
68
70
  if self.instrument_config.swagger_offline_docs:
69
71
  enable_offline_docs(application, static_files_handler=self.instrument_config.service_static_path)
72
+ if self.instrument_config.openapi_version_docs is None and not self.instrument_config.security_schemes:
73
+ return application
74
+
75
+ original_openapi: typing.Final = application.openapi
76
+
77
+ def documented_openapi() -> dict[str, typing.Any]:
78
+ openapi_schema: typing.Final = original_openapi()
79
+ if self.instrument_config.security_schemes:
80
+ self._merge_security_schemes(openapi_schema)
81
+ if self.instrument_config.openapi_version_docs is not None:
82
+ self._document_operations(openapi_schema)
83
+ return openapi_schema
84
+
85
+ application.openapi = documented_openapi # type: ignore[method-assign] # FastAPI's public custom OpenAPI hook.
70
86
  return application
71
87
 
88
+ def _merge_security_schemes(self, openapi_schema: dict[str, typing.Any]) -> None:
89
+ expected_schemes: typing.Final = serialize_security_schemes(self.instrument_config.security_schemes)
90
+ components = openapi_schema.setdefault("components", {})
91
+ security_schemes = components.setdefault("securitySchemes", {})
92
+ self._validate_security_scheme_conflicts(security_schemes, expected_schemes)
93
+ security_schemes.update(
94
+ {name: scheme for name, scheme in expected_schemes.items() if name not in security_schemes}
95
+ )
96
+
97
+ def _document_operations(self, openapi_schema: dict[str, typing.Any]) -> None:
98
+ for path, path_item in openapi_schema.get("paths", {}).items():
99
+ if path.startswith("x-"):
100
+ continue
101
+ for method, operation in path_item.items():
102
+ if method not in SUPPORTED_HTTP_METHODS:
103
+ continue
104
+ documentation = self._build_version_documentation(
105
+ path,
106
+ method,
107
+ operation.get("description"),
108
+ operation.get("x-accept-versioning"),
109
+ has_existing_extension="x-accept-versioning" in operation,
110
+ )
111
+ if documentation is not None:
112
+ operation["x-accept-versioning"], operation["description"] = documentation
113
+
72
114
 
73
115
  @FastApiBootstrapper.use_instrument()
74
116
  class FastApiCorsInstrument(CorsInstrument):
@@ -24,6 +24,7 @@ from microbootstrap.instruments.opentelemetry_instrument import (
24
24
  from microbootstrap.instruments.prometheus_instrument import FastStreamPrometheusConfig, PrometheusInstrument
25
25
  from microbootstrap.instruments.pyroscope_instrument import PyroscopeInstrument
26
26
  from microbootstrap.instruments.sentry_instrument import SentryInstrument
27
+ from microbootstrap.middlewares.faststream import FastStreamOpenTelemetryBaggageMiddleware
27
28
  from microbootstrap.settings import FastStreamSettings
28
29
 
29
30
 
@@ -56,7 +57,11 @@ def _isolate_faststream_subscribers(application: AsgiFastStream) -> None:
56
57
  class KwargsAsgiFastStream(AsgiFastStream):
57
58
  def __init__(self, **kwargs: typing.Any) -> None: # noqa: ANN401
58
59
  # `broker` argument is positional-only
59
- super().__init__(kwargs.pop("broker", None), **kwargs)
60
+ broker = kwargs.pop("broker", None)
61
+ if broker is None:
62
+ super().__init__(**kwargs)
63
+ else:
64
+ super().__init__(broker, **kwargs)
60
65
 
61
66
 
62
67
  class FastStreamBootstrapper(ApplicationBootstrapper[FastStreamSettings, AsgiFastStream, FastStreamConfig]):
@@ -101,6 +106,11 @@ class FastStreamOpentelemetryInstrument(BaseOpentelemetryInstrument[FastStreamOp
101
106
  application.broker.add_middleware(
102
107
  self.instrument_config.opentelemetry_middleware_cls(tracer_provider=self.tracer_provider),
103
108
  )
109
+ application.broker.add_middleware(
110
+ FastStreamOpenTelemetryBaggageMiddleware(
111
+ baggage_span_attributes=self.instrument_config.opentelemetry_baggage_span_attributes,
112
+ ),
113
+ )
104
114
  return application
105
115
 
106
116
  @classmethod
@@ -1,4 +1,5 @@
1
1
  from __future__ import annotations
2
+ import dataclasses
2
3
  import typing
3
4
 
4
5
  import litestar
@@ -25,6 +26,15 @@ from microbootstrap.instruments.health_checks_instrument import (
25
26
  HealthCheckTypedDict,
26
27
  )
27
28
  from microbootstrap.instruments.logging_instrument import LoggingInstrument
29
+ from microbootstrap.instruments.openapi_security_schemes import (
30
+ OpenApiApiKeySecurityScheme,
31
+ OpenApiHttpSecurityScheme,
32
+ OpenApiOAuth2SecurityScheme,
33
+ OpenApiOpenIdConnectSecurityScheme,
34
+ _OpenApiSecurityScheme,
35
+ serialize_security_schemes,
36
+ )
37
+ from microbootstrap.instruments.openapi_version_docs import SUPPORTED_HTTP_METHODS
28
38
  from microbootstrap.instruments.opentelemetry_instrument import OpentelemetryInstrument
29
39
  from microbootstrap.instruments.prometheus_instrument import (
30
40
  LitestarPrometheusConfig,
@@ -37,6 +47,17 @@ from microbootstrap.middlewares.litestar import build_litestar_logging_middlewar
37
47
  from microbootstrap.settings import LitestarSettings
38
48
 
39
49
 
50
+ ApplicationT = typing.TypeVar("ApplicationT", bound=litestar.Litestar)
51
+
52
+
53
+ @dataclasses.dataclass
54
+ class AcceptVersionedOperation(openapi.spec.Operation):
55
+ accept_versioning: dict[str, str | list[str]] | None = dataclasses.field(
56
+ default=None,
57
+ metadata={"alias": "x-accept-versioning"},
58
+ )
59
+
60
+
40
61
  if typing.TYPE_CHECKING:
41
62
  from litestar.contrib.opentelemetry import OpenTelemetryConfig
42
63
  from litestar.types import ASGIApp, Scope
@@ -102,6 +123,102 @@ class LitestarSwaggerInstrument(SwaggerInstrument):
102
123
  ]
103
124
  return bootstrap_result
104
125
 
126
+ def bootstrap_after(self, application: ApplicationT) -> ApplicationT:
127
+ if (self.instrument_config.openapi_version_docs is None and not self.instrument_config.security_schemes) or (
128
+ application.openapi_config is None
129
+ ):
130
+ return application
131
+ if self.instrument_config.security_schemes:
132
+ self._merge_security_schemes(application.openapi_schema)
133
+ if self.instrument_config.openapi_version_docs is not None:
134
+ self._document_operations(application.openapi_schema)
135
+ return application
136
+
137
+ def _merge_security_schemes(self, openapi_schema: openapi.spec.OpenAPI) -> None:
138
+ expected_schemes: typing.Final = serialize_security_schemes(self.instrument_config.security_schemes)
139
+ security_schemes = openapi_schema.components.security_schemes
140
+ if security_schemes is not None:
141
+ canonical_schemes: typing.Final = {
142
+ name: scheme.to_schema() if isinstance(scheme, openapi.spec.SecurityScheme) else scheme
143
+ for name, scheme in security_schemes.items()
144
+ }
145
+ self._validate_security_scheme_conflicts(canonical_schemes, expected_schemes)
146
+ configured_schemes = {
147
+ scheme_name: self._build_litestar_security_scheme(security_scheme)
148
+ for scheme_name, security_scheme in self.instrument_config.security_schemes.items()
149
+ }
150
+ if security_schemes is None:
151
+ openapi_schema.components.security_schemes = typing.cast(
152
+ "dict[str, openapi.spec.SecurityScheme | openapi.spec.Reference]",
153
+ configured_schemes,
154
+ )
155
+ return
156
+ security_schemes.update(
157
+ {name: scheme for name, scheme in configured_schemes.items() if name not in security_schemes}
158
+ )
159
+
160
+ def _document_operations(self, openapi_schema: openapi.spec.OpenAPI) -> None:
161
+ if openapi_schema.paths is None:
162
+ return
163
+ for path, path_item in openapi_schema.paths.items():
164
+ for method in SUPPORTED_HTTP_METHODS:
165
+ operation = getattr(path_item, method)
166
+ if operation is None:
167
+ continue
168
+ existing_extension = (
169
+ operation.accept_versioning if isinstance(operation, AcceptVersionedOperation) else None
170
+ )
171
+ documentation = self._build_version_documentation(
172
+ path,
173
+ method,
174
+ operation.description,
175
+ existing_extension,
176
+ has_existing_extension=existing_extension is not None,
177
+ )
178
+ if documentation is None:
179
+ continue
180
+ extension, description = documentation
181
+ if type(operation) is openapi.spec.Operation:
182
+ init_fields = {
183
+ field.name: getattr(operation, field.name)
184
+ for field in dataclasses.fields(openapi.spec.Operation)
185
+ if field.init
186
+ }
187
+ operation = AcceptVersionedOperation(**init_fields, accept_versioning=extension)
188
+ setattr(path_item, method, operation)
189
+ elif type(operation) is not AcceptVersionedOperation:
190
+ message = (
191
+ f"OpenAPI operation {type(operation).__name__} is not supported "
192
+ "for Accept version documentation."
193
+ )
194
+ raise TypeError(message)
195
+ operation.accept_versioning = extension
196
+ operation.description = description
197
+
198
+ @classmethod
199
+ def _build_litestar_security_scheme(cls, security_scheme: _OpenApiSecurityScheme) -> openapi.spec.SecurityScheme:
200
+ if not isinstance(
201
+ security_scheme,
202
+ (
203
+ OpenApiHttpSecurityScheme,
204
+ OpenApiApiKeySecurityScheme,
205
+ OpenApiOAuth2SecurityScheme,
206
+ OpenApiOpenIdConnectSecurityScheme,
207
+ ),
208
+ ):
209
+ raise AssertionError("Unsupported OpenAPI security scheme.") # noqa: TRY004
210
+ scheme_data = security_scheme.model_dump(by_alias=False, exclude_none=True)
211
+ if "location" in scheme_data:
212
+ scheme_data["security_scheme_in"] = scheme_data.pop("location")
213
+ if "flows" in scheme_data:
214
+ flow_data = scheme_data["flows"]
215
+ if "resource_owner" in flow_data:
216
+ flow_data["password"] = flow_data.pop("resource_owner")
217
+ scheme_data["flows"] = openapi.spec.OAuthFlows(
218
+ **{flow_name: openapi.spec.OAuthFlow(**flow) for flow_name, flow in flow_data.items()}
219
+ )
220
+ return openapi.spec.SecurityScheme(**scheme_data)
221
+
105
222
 
106
223
  @LitestarBootstrapper.use_instrument()
107
224
  class LitestarCorsInstrument(CorsInstrument):
@@ -26,7 +26,7 @@ def dataclass_to_dict_no_defaults(dataclass_to_convert: "_DataclassT") -> dict[s
26
26
  if dataclass_field.default != value and isinstance(dataclass_field.default_factory, _MISSING_TYPE):
27
27
  conversion_result[dataclass_field.name] = value
28
28
  continue
29
- if value != dataclass_field.default and value != dataclass_field.default_factory(): # type: ignore[misc]
29
+ if value != dataclass_field.default and value != dataclass_field.default_factory(): # type: ignore[operator]
30
30
  conversion_result[dataclass_field.name] = value
31
31
 
32
32
  return conversion_result
@@ -0,0 +1,127 @@
1
+ from __future__ import annotations
2
+ import typing
3
+ import unicodedata
4
+
5
+ import pydantic
6
+
7
+
8
+ OpenApiApiKeyLocation: typing.TypeAlias = typing.Literal["header", "query", "cookie"]
9
+
10
+
11
+ class OpenApiSecuritySchemeModel(pydantic.BaseModel):
12
+ model_config = pydantic.ConfigDict(extra="forbid", populate_by_name=True)
13
+
14
+
15
+ class OpenApiHttpSecurityScheme(OpenApiSecuritySchemeModel):
16
+ type: typing.Literal["http"] = "http"
17
+ scheme: str
18
+ bearer_format: str | None = pydantic.Field(default=None, alias="bearerFormat")
19
+ description: str | None = None
20
+
21
+
22
+ class OpenApiApiKeySecurityScheme(OpenApiSecuritySchemeModel):
23
+ type: typing.Literal["apiKey"] = "apiKey"
24
+ name: str
25
+ location: OpenApiApiKeyLocation = pydantic.Field(alias="in")
26
+ description: str | None = None
27
+
28
+
29
+ class OpenApiOAuthFlow(OpenApiSecuritySchemeModel):
30
+ authorization_url: str | None = pydantic.Field(default=None, alias="authorizationUrl")
31
+ token_url: str | None = pydantic.Field(default=None, alias="tokenUrl")
32
+ refresh_url: str | None = pydantic.Field(default=None, alias="refreshUrl")
33
+ scopes: dict[str, str] = pydantic.Field(default_factory=dict)
34
+
35
+ @pydantic.field_validator("authorization_url", "token_url", "refresh_url")
36
+ @classmethod
37
+ def validate_url(cls, value: str | None) -> str | None:
38
+ return validate_openapi_url(value)
39
+
40
+
41
+ class OpenApiOAuthFlows(OpenApiSecuritySchemeModel):
42
+ implicit: OpenApiOAuthFlow | None = None
43
+ resource_owner: OpenApiOAuthFlow | None = pydantic.Field(default=None, alias="password")
44
+ client_credentials: OpenApiOAuthFlow | None = pydantic.Field(default=None, alias="clientCredentials")
45
+ authorization_code: OpenApiOAuthFlow | None = pydantic.Field(default=None, alias="authorizationCode")
46
+
47
+ @pydantic.model_validator(mode="after")
48
+ def validate_required_urls(self) -> OpenApiOAuthFlows:
49
+ if all(
50
+ flow is None
51
+ for flow in (self.implicit, self.resource_owner, self.client_credentials, self.authorization_code)
52
+ ):
53
+ message = "OAuth2 flows must configure at least one grant type."
54
+ raise ValueError(message)
55
+ self._validate_flow_urls("implicit", self.implicit, requires_authorization_url=True)
56
+ self._validate_flow_urls("password", self.resource_owner, requires_token_url=True)
57
+ self._validate_flow_urls("client credentials", self.client_credentials, requires_token_url=True)
58
+ self._validate_flow_urls(
59
+ "authorization code",
60
+ self.authorization_code,
61
+ requires_authorization_url=True,
62
+ requires_token_url=True,
63
+ )
64
+ return self
65
+
66
+ @staticmethod
67
+ def _validate_flow_urls(
68
+ flow_name: str,
69
+ flow: OpenApiOAuthFlow | None,
70
+ *,
71
+ requires_authorization_url: bool = False,
72
+ requires_token_url: bool = False,
73
+ ) -> None:
74
+ if flow is None:
75
+ return
76
+ if requires_authorization_url and flow.authorization_url is None:
77
+ message = f"OAuth2 {flow_name} flow requires authorizationUrl."
78
+ raise ValueError(message)
79
+ if requires_token_url and flow.token_url is None:
80
+ message = f"OAuth2 {flow_name} flow requires tokenUrl."
81
+ raise ValueError(message)
82
+
83
+
84
+ class OpenApiOAuth2SecurityScheme(OpenApiSecuritySchemeModel):
85
+ type: typing.Literal["oauth2"] = "oauth2"
86
+ flows: OpenApiOAuthFlows
87
+ description: str | None = None
88
+
89
+
90
+ class OpenApiOpenIdConnectSecurityScheme(OpenApiSecuritySchemeModel):
91
+ type: typing.Literal["openIdConnect"] = "openIdConnect"
92
+ open_id_connect_url: str = pydantic.Field(alias="openIdConnectUrl")
93
+ description: str | None = None
94
+
95
+ @pydantic.field_validator("open_id_connect_url")
96
+ @classmethod
97
+ def validate_url(cls, value: str) -> str:
98
+ validated_value = validate_openapi_url(value)
99
+ assert validated_value is not None # noqa: S101 - this field is required.
100
+ return validated_value
101
+
102
+
103
+ _OpenApiSecurityScheme: typing.TypeAlias = typing.Annotated[
104
+ OpenApiHttpSecurityScheme
105
+ | OpenApiApiKeySecurityScheme
106
+ | OpenApiOAuth2SecurityScheme
107
+ | OpenApiOpenIdConnectSecurityScheme,
108
+ pydantic.Field(discriminator="type"),
109
+ ]
110
+
111
+
112
+ def validate_openapi_url(value: str | None) -> str | None:
113
+ if value is None:
114
+ return value
115
+ if not value or any(character.isspace() or unicodedata.category(character) == "Cc" for character in value):
116
+ message = "OpenAPI URL values must be non-empty and contain no whitespace or control characters."
117
+ raise ValueError(message)
118
+ return value
119
+
120
+
121
+ def serialize_security_schemes(
122
+ security_schemes: typing.Mapping[str, _OpenApiSecurityScheme],
123
+ ) -> dict[str, dict[str, typing.Any]]:
124
+ return {
125
+ scheme_name: security_scheme.model_dump(by_alias=True, exclude_none=True)
126
+ for scheme_name, security_scheme in security_schemes.items()
127
+ }
@@ -0,0 +1,86 @@
1
+ from __future__ import annotations
2
+ import re
3
+ import typing
4
+
5
+ import pydantic
6
+
7
+
8
+ SUPPORTED_HTTP_METHODS: typing.Final = frozenset({"delete", "get", "head", "options", "patch", "post", "put", "trace"})
9
+ SAFE_MEDIA_TYPE_TOKEN: typing.Final[re.Pattern[str]] = re.compile(r"[!#$%&'*+\-.^_|~0-9A-Za-z]+\Z")
10
+ VENDOR_MEDIA_TYPE: typing.Final = re.compile(r"application/vnd\.([!#$%&'*+\-.^_|~0-9A-Za-z]+)\+json\Z")
11
+
12
+
13
+ class OpenApiOperationVersionOverride(pydantic.BaseModel):
14
+ model_config = pydantic.ConfigDict(extra="forbid")
15
+
16
+ path: str
17
+ method: str
18
+ supported_versions: tuple[str, ...]
19
+
20
+ @pydantic.field_validator("path")
21
+ @classmethod
22
+ def validate_path(cls, value: str) -> str:
23
+ if not value.startswith("/") or "?" in value or "#" in value:
24
+ message = "Operation path must be an absolute path without a query string or fragment."
25
+ raise ValueError(message)
26
+ return value
27
+
28
+ @pydantic.field_validator("method")
29
+ @classmethod
30
+ def validate_method(cls, value: str) -> str:
31
+ if value not in SUPPORTED_HTTP_METHODS:
32
+ message = f"Operation method must be one of: {', '.join(sorted(SUPPORTED_HTTP_METHODS))}."
33
+ raise ValueError(message)
34
+ return value
35
+
36
+ @pydantic.field_validator("supported_versions")
37
+ @classmethod
38
+ def validate_supported_versions(cls, value: tuple[str, ...]) -> tuple[str, ...]:
39
+ return validate_versions(value)
40
+
41
+
42
+ class OpenApiVersionDocsConfig(pydantic.BaseModel):
43
+ model_config = pydantic.ConfigDict(extra="forbid")
44
+
45
+ vendor_media_type: str
46
+ supported_versions: tuple[str, ...]
47
+ operation_versions: tuple[OpenApiOperationVersionOverride, ...] = ()
48
+
49
+ @pydantic.field_validator("vendor_media_type")
50
+ @classmethod
51
+ def validate_vendor_media_type(cls, value: str) -> str:
52
+ if VENDOR_MEDIA_TYPE.fullmatch(value) is None:
53
+ message = "Vendor media type must use the application/vnd.<name>+json form."
54
+ raise ValueError(message)
55
+ return value
56
+
57
+ @pydantic.field_validator("supported_versions")
58
+ @classmethod
59
+ def validate_supported_versions(cls, value: tuple[str, ...]) -> tuple[str, ...]:
60
+ validated_versions = validate_versions(value)
61
+ if not validated_versions:
62
+ message = "OpenAPI version documentation requires at least one supported API version."
63
+ raise ValueError(message)
64
+ return validated_versions
65
+
66
+ @pydantic.field_validator("operation_versions")
67
+ @classmethod
68
+ def validate_operation_versions(
69
+ cls,
70
+ value: tuple[OpenApiOperationVersionOverride, ...],
71
+ ) -> tuple[OpenApiOperationVersionOverride, ...]:
72
+ selectors = {(override.path, override.method) for override in value}
73
+ if len(value) != len(selectors):
74
+ message = "Operation version overrides must not contain duplicate path and method pairs."
75
+ raise ValueError(message)
76
+ return value
77
+
78
+
79
+ def validate_versions(value: tuple[str, ...]) -> tuple[str, ...]:
80
+ if len(value) != len(set(value)):
81
+ message = "Supported API versions must not contain duplicates."
82
+ raise ValueError(message)
83
+ if any(SAFE_MEDIA_TYPE_TOKEN.fullmatch(version) is None for version in value):
84
+ message = "Each supported API version must be a non-empty safe media-type token."
85
+ raise ValueError(message)
86
+ return value
@@ -0,0 +1,121 @@
1
+ from __future__ import annotations
2
+ import re
3
+ import typing
4
+
5
+ import pydantic
6
+
7
+ from microbootstrap.helpers import is_valid_path
8
+ from microbootstrap.instruments.base import BaseInstrumentConfig, Instrument
9
+ from microbootstrap.instruments.openapi_security_schemes import _OpenApiSecurityScheme # noqa: TC001
10
+ from microbootstrap.instruments.openapi_version_docs import (
11
+ SUPPORTED_HTTP_METHODS,
12
+ OpenApiVersionDocsConfig,
13
+ )
14
+
15
+
16
+ SECURITY_SCHEME_NAME_PATTERN: typing.Final = re.compile(r"^[a-zA-Z0-9._-]+$")
17
+
18
+
19
+ class SwaggerConfig(BaseInstrumentConfig):
20
+ service_name: str = "micro-service"
21
+ service_description: str = "Micro service description"
22
+ service_version: str = "1.0.0"
23
+
24
+ service_static_path: str = "/static"
25
+ swagger_path: str = "/docs"
26
+ swagger_offline_docs: bool = False
27
+ swagger_extra_params: dict[str, typing.Any] = pydantic.Field(default_factory=dict)
28
+ security_schemes: typing.Mapping[str, _OpenApiSecurityScheme] = pydantic.Field(default_factory=dict)
29
+ openapi_version_docs: OpenApiVersionDocsConfig | None = None
30
+
31
+ @pydantic.field_validator("security_schemes")
32
+ @classmethod
33
+ def validate_security_scheme_names(
34
+ cls,
35
+ security_schemes: typing.Mapping[str, _OpenApiSecurityScheme],
36
+ ) -> typing.Mapping[str, _OpenApiSecurityScheme]:
37
+ for scheme_name in security_schemes:
38
+ if SECURITY_SCHEME_NAME_PATTERN.fullmatch(scheme_name) is None:
39
+ message = "OpenAPI security scheme names must match ^[a-zA-Z0-9._-]+$."
40
+ raise ValueError(message)
41
+ return security_schemes
42
+
43
+
44
+ class SwaggerInstrument(Instrument[SwaggerConfig]):
45
+ instrument_name = "Swagger"
46
+ ready_condition = "Provide valid swagger_path"
47
+
48
+ def is_ready(self) -> bool:
49
+ return bool(self.instrument_config.swagger_path) and is_valid_path(self.instrument_config.swagger_path)
50
+
51
+ def _build_version_documentation(
52
+ self,
53
+ path: str,
54
+ method: str,
55
+ description: object,
56
+ existing_extension: object,
57
+ *,
58
+ has_existing_extension: bool,
59
+ ) -> tuple[dict[str, str | list[str]], str] | None:
60
+ configuration = self.instrument_config.openapi_version_docs
61
+ if configuration is None or method not in SUPPORTED_HTTP_METHODS:
62
+ return None
63
+
64
+ supported_versions = next(
65
+ (
66
+ override.supported_versions
67
+ for override in configuration.operation_versions
68
+ if override.path == path and override.method == method
69
+ ),
70
+ configuration.supported_versions,
71
+ )
72
+ if not supported_versions:
73
+ return None
74
+ if description is not None and not isinstance(description, str):
75
+ message = f"OpenAPI operation {method.upper()} {path} has a non-string description."
76
+ raise ValueError(message)
77
+
78
+ expected_extension: typing.Final[dict[str, str | list[str]]] = {
79
+ "header": "Accept",
80
+ "mediaType": configuration.vendor_media_type,
81
+ "parameter": "version",
82
+ "supportedVersions": list(supported_versions),
83
+ }
84
+ if has_existing_extension and existing_extension != expected_extension:
85
+ message = "OpenAPI operation x-accept-versioning conflicts with configured Accept version documentation."
86
+ raise ValueError(message)
87
+ return expected_extension, self._format_version_documentation(description, configuration, supported_versions)
88
+
89
+ @staticmethod
90
+ def _format_version_documentation(
91
+ description: str | None,
92
+ configuration: OpenApiVersionDocsConfig,
93
+ supported_versions: tuple[str, ...],
94
+ ) -> str:
95
+ media_types: typing.Final = tuple(
96
+ f"{configuration.vendor_media_type}; version={version}" for version in supported_versions
97
+ )
98
+ version_documentation: typing.Final = (
99
+ f"Supported API version: `{media_types[0]}`."
100
+ if len(media_types) == 1
101
+ else "Supported API versions:\n" + "\n".join(f"- `{media_type}`." for media_type in media_types)
102
+ )
103
+ if not description:
104
+ return version_documentation
105
+ if version_documentation in description:
106
+ return description
107
+ return f"{description}\n\n{version_documentation}"
108
+
109
+ @staticmethod
110
+ def _validate_security_scheme_conflicts(
111
+ existing_schemes: typing.Mapping[str, object],
112
+ expected_schemes: typing.Mapping[str, object],
113
+ ) -> None:
114
+ for scheme_name, expected_scheme in expected_schemes.items():
115
+ if scheme_name in existing_schemes and existing_schemes[scheme_name] != expected_scheme:
116
+ message = f"OpenAPI security scheme '{scheme_name}' conflicts with the configured security scheme."
117
+ raise ValueError(message)
118
+
119
+ @classmethod
120
+ def get_config_type(cls) -> type[SwaggerConfig]:
121
+ return SwaggerConfig
@@ -0,0 +1,66 @@
1
+ from __future__ import annotations
2
+ import typing
3
+
4
+ from faststream._internal.middlewares import BaseMiddleware
5
+ from opentelemetry import baggage, propagate
6
+
7
+ from microbootstrap.instruments.opentelemetry_instrument import opentelemetry_baggage_scope
8
+
9
+
10
+ if typing.TYPE_CHECKING:
11
+ from faststream._internal.basic_types import AsyncFuncAny
12
+ from faststream._internal.context import ContextRepo
13
+ from faststream.message import StreamMessage
14
+
15
+
16
+ class FastStreamOpenTelemetryBaggageMiddleware:
17
+ def __init__(self, *, baggage_span_attributes: typing.Mapping[str, str]) -> None:
18
+ self.baggage_span_attributes = baggage_span_attributes
19
+
20
+ def __call__(
21
+ self,
22
+ msg: typing.Any, # noqa: ANN401
23
+ /,
24
+ *,
25
+ context: ContextRepo,
26
+ ) -> _FastStreamOpenTelemetryBaggageMiddleware:
27
+ return _FastStreamOpenTelemetryBaggageMiddleware(
28
+ msg,
29
+ context=context,
30
+ baggage_span_attributes=self.baggage_span_attributes,
31
+ )
32
+
33
+
34
+ class _FastStreamOpenTelemetryBaggageMiddleware(BaseMiddleware[typing.Any, typing.Any]):
35
+ def __init__(
36
+ self,
37
+ msg: typing.Any, # noqa: ANN401
38
+ /,
39
+ *,
40
+ context: ContextRepo,
41
+ baggage_span_attributes: typing.Mapping[str, str],
42
+ ) -> None:
43
+ super().__init__(msg, context=context)
44
+ self.baggage_span_attributes = baggage_span_attributes
45
+
46
+ async def consume_scope(
47
+ self,
48
+ call_next: AsyncFuncAny,
49
+ msg: StreamMessage[typing.Any],
50
+ ) -> typing.Any: # noqa: ANN401
51
+ extracted_baggage: typing.Final = baggage.get_all(propagate.extract(msg.headers))
52
+ with opentelemetry_baggage_scope(
53
+ extracted_baggage,
54
+ current_span_attributes=self.baggage_span_attributes,
55
+ ):
56
+ return await call_next(msg)
57
+
58
+ async def publish_scope(
59
+ self,
60
+ call_next: typing.Callable[[typing.Any], typing.Awaitable[typing.Any]],
61
+ cmd: typing.Any, # noqa: ANN401
62
+ ) -> typing.Any: # noqa: ANN401
63
+ for field in propagate.get_global_textmap().fields:
64
+ cmd.headers.pop(field, None)
65
+ propagate.inject(cmd.headers)
66
+ return await call_next(cmd)
@@ -58,26 +58,26 @@ dependencies = [
58
58
  "opentelemetry-instrumentation-asgi>=0.46b0",
59
59
  "orjson>=3.10.18",
60
60
  ]
61
- version = "1.5.4"
61
+ version = "1.6.0"
62
62
 
63
63
  [[project.authors]]
64
64
  name = "community-of-python"
65
65
 
66
66
  [project.optional-dependencies]
67
67
  fastapi = [
68
- "fastapi>=0.100",
68
+ "fastapi>=0.110.1",
69
69
  "fastapi-offline-docs>=1",
70
70
  "opentelemetry-instrumentation-fastapi>=0.46b0",
71
- "prometheus-fastapi-instrumentator>=6.1",
71
+ "prometheus-fastapi-instrumentator>=7.1",
72
72
  ]
73
73
  litestar = [
74
- "litestar>=2.9",
74
+ "litestar>=2.21.1",
75
75
  "litestar-offline-docs>=1",
76
76
  "prometheus-client>=0.20",
77
77
  ]
78
78
  granian = ["granian[reload]>=1"]
79
79
  faststream = [
80
- "faststream~=0.6.2",
80
+ "faststream>=0.6.7,<0.8",
81
81
  "prometheus-client>=0.20",
82
82
  ]
83
83
  fastmcp = [
@@ -58,23 +58,23 @@ dependencies = [
58
58
  "opentelemetry-instrumentation-asgi>=0.46b0",
59
59
  "orjson>=3.10.18",
60
60
  ]
61
- version = "1.5.4"
61
+ version = "1.6.0"
62
62
  authors = [{ name = "community-of-python" }]
63
63
 
64
64
  [project.optional-dependencies]
65
65
  fastapi = [
66
- "fastapi>=0.100",
66
+ "fastapi>=0.110.1",
67
67
  "fastapi-offline-docs>=1",
68
68
  "opentelemetry-instrumentation-fastapi>=0.46b0",
69
- "prometheus-fastapi-instrumentator>=6.1",
69
+ "prometheus-fastapi-instrumentator>=7.1",
70
70
  ]
71
71
  litestar = [
72
- "litestar>=2.9",
72
+ "litestar>=2.21.1",
73
73
  "litestar-offline-docs>=1",
74
74
  "prometheus-client>=0.20",
75
75
  ]
76
76
  granian = ["granian[reload]>=1"]
77
- faststream = ["faststream~=0.6.2", "prometheus-client>=0.20"]
77
+ faststream = ["faststream>=0.6.7,<0.8", "prometheus-client>=0.20"]
78
78
  fastmcp = ["fastmcp>=2,<4", "prometheus-client>=0.20"]
79
79
 
80
80
  [dependency-groups]
@@ -1,30 +0,0 @@
1
- from __future__ import annotations
2
- import typing
3
-
4
- import pydantic
5
-
6
- from microbootstrap.helpers import is_valid_path
7
- from microbootstrap.instruments.base import BaseInstrumentConfig, Instrument
8
-
9
-
10
- class SwaggerConfig(BaseInstrumentConfig):
11
- service_name: str = "micro-service"
12
- service_description: str = "Micro service description"
13
- service_version: str = "1.0.0"
14
-
15
- service_static_path: str = "/static"
16
- swagger_path: str = "/docs"
17
- swagger_offline_docs: bool = False
18
- swagger_extra_params: dict[str, typing.Any] = pydantic.Field(default_factory=dict)
19
-
20
-
21
- class SwaggerInstrument(Instrument[SwaggerConfig]):
22
- instrument_name = "Swagger"
23
- ready_condition = "Provide valid swagger_path"
24
-
25
- def is_ready(self) -> bool:
26
- return bool(self.instrument_config.swagger_path) and is_valid_path(self.instrument_config.swagger_path)
27
-
28
- @classmethod
29
- def get_config_type(cls) -> type[SwaggerConfig]:
30
- return SwaggerConfig