lite-bootstrap 1.2.2__tar.gz → 1.3.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 (38) hide show
  1. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/PKG-INFO +16 -9
  2. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/README.md +7 -6
  3. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/bootstrappers/faststream_bootstrapper.py +8 -0
  4. lite_bootstrap-1.3.0/lite_bootstrap/import_checker.py +40 -0
  5. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/instruments/logging_factory.py +20 -5
  6. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/instruments/logging_instrument.py +3 -3
  7. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/instruments/opentelemetry_instrument.py +49 -14
  8. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/pyproject.toml +15 -5
  9. lite_bootstrap-1.2.2/lite_bootstrap/import_checker.py +0 -19
  10. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/__init__.py +0 -0
  11. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/bootstrappers/__init__.py +0 -0
  12. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/bootstrappers/base.py +0 -0
  13. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/bootstrappers/fastapi_bootstrapper.py +0 -0
  14. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/bootstrappers/fastmcp_bootstrapper.py +0 -0
  15. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/bootstrappers/free_bootstrapper.py +0 -0
  16. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/bootstrappers/litestar_bootstrapper.py +0 -0
  17. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/exceptions.py +0 -0
  18. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/helpers/__init__.py +0 -0
  19. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/helpers/fastapi_helpers.py +0 -0
  20. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/helpers/path.py +0 -0
  21. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/instruments/__init__.py +0 -0
  22. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/instruments/base.py +0 -0
  23. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/instruments/cors_instrument.py +0 -0
  24. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/instruments/healthchecks_instrument.py +0 -0
  25. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/instruments/prometheus_instrument.py +0 -0
  26. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/instruments/pyroscope_instrument.py +0 -0
  27. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/instruments/sentry_instrument.py +0 -0
  28. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/instruments/swagger_instrument.py +0 -0
  29. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/py.typed +0 -0
  30. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/static/fastapi_docs/redoc.standalone.js +0 -0
  31. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/static/fastapi_docs/swagger-ui-bundle.js +0 -0
  32. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/static/fastapi_docs/swagger-ui.css +0 -0
  33. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/static/litestar_docs/asyncapi-web-component.js +0 -0
  34. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/static/litestar_docs/default.min.css +0 -0
  35. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/static/litestar_docs/swagger-ui-bundle.js +0 -0
  36. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/static/litestar_docs/swagger-ui-standalone-preset.js +0 -0
  37. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/static/litestar_docs/swagger-ui.css +0 -0
  38. {lite_bootstrap-1.2.2 → lite_bootstrap-1.3.0}/lite_bootstrap/types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lite-bootstrap
3
- Version: 1.2.2
3
+ Version: 1.3.0
4
4
  Summary: Lightweight bootstrap for production-ready Python microservices
5
5
  Keywords: microservices,bootstrap,opentelemetry,sentry,error-tracing,fastapi,litestar,faststream,structlog,fastmcp,python
6
6
  Author: Artur Shiriev
@@ -15,7 +15,6 @@ Classifier: Programming Language :: Python :: 3.13
15
15
  Classifier: Programming Language :: Python :: 3.14
16
16
  Classifier: Typing :: Typed
17
17
  Classifier: Topic :: Software Development :: Libraries
18
- Requires-Dist: orjson
19
18
  Requires-Dist: fastapi ; extra == 'fastapi'
20
19
  Requires-Dist: lite-bootstrap[fastapi-sentry,fastapi-otl,fastapi-logging,fastapi-metrics,pyroscope] ; extra == 'fastapi-all'
21
20
  Requires-Dist: lite-bootstrap[fastapi,logging] ; extra == 'fastapi-logging'
@@ -46,10 +45,15 @@ Requires-Dist: lite-bootstrap[litestar,otl] ; extra == 'litestar-otl'
46
45
  Requires-Dist: opentelemetry-instrumentation-asgi>=0.46b0 ; extra == 'litestar-otl'
47
46
  Requires-Dist: lite-bootstrap[litestar,sentry] ; extra == 'litestar-sentry'
48
47
  Requires-Dist: structlog ; extra == 'logging'
48
+ Requires-Dist: orjson ; extra == 'orjson'
49
49
  Requires-Dist: opentelemetry-api ; extra == 'otl'
50
50
  Requires-Dist: opentelemetry-sdk ; extra == 'otl'
51
- Requires-Dist: opentelemetry-exporter-otlp ; extra == 'otl'
51
+ Requires-Dist: opentelemetry-exporter-otlp-proto-grpc ; extra == 'otl'
52
52
  Requires-Dist: opentelemetry-instrumentation ; extra == 'otl'
53
+ Requires-Dist: opentelemetry-api ; extra == 'otl-http'
54
+ Requires-Dist: opentelemetry-sdk ; extra == 'otl-http'
55
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http ; extra == 'otl-http'
56
+ Requires-Dist: opentelemetry-instrumentation ; extra == 'otl-http'
53
57
  Requires-Dist: pyroscope-io ; extra == 'pyroscope'
54
58
  Requires-Dist: sentry-sdk ; extra == 'sentry'
55
59
  Requires-Python: >=3.10, <4
@@ -81,7 +85,9 @@ Provides-Extra: litestar-metrics
81
85
  Provides-Extra: litestar-otl
82
86
  Provides-Extra: litestar-sentry
83
87
  Provides-Extra: logging
88
+ Provides-Extra: orjson
84
89
  Provides-Extra: otl
90
+ Provides-Extra: otl-http
85
91
  Provides-Extra: pyroscope
86
92
  Provides-Extra: sentry
87
93
  Description-Content-Type: text/markdown
@@ -109,6 +115,7 @@ Description-Content-Type: text/markdown
109
115
  `lite-bootstrap` wires production observability — OpenTelemetry, Prometheus, Sentry, and structlog — into FastAPI, Litestar, and FastStream services in a few lines, with no boilerplate.
110
116
 
111
117
  With `lite-bootstrap`, you receive an application with lightweight built-in support for:
118
+
112
119
  - `sentry`
113
120
  - `prometheus`
114
121
  - `opentelemetry`
@@ -120,11 +127,11 @@ With `lite-bootstrap`, you receive an application with lightweight built-in supp
120
127
 
121
128
  Those instruments can be bootstrapped for:
122
129
 
123
- - [LiteStar](https://lite-bootstrap.modern-python.org/integrations/litestar)
124
- - [FastStream](https://lite-bootstrap.modern-python.org/integrations/faststream)
125
- - [FastAPI](https://lite-bootstrap.modern-python.org/integrations/fastapi)
126
- - [FastMCP](https://lite-bootstrap.modern-python.org/integrations/fastmcp)
127
- - [services and scripts without frameworks](https://lite-bootstrap.modern-python.org/integrations/free)
130
+ - [LiteStar](https://lite-bootstrap.modern-python.org/integrations/litestar/)
131
+ - [FastStream](https://lite-bootstrap.modern-python.org/integrations/faststream/)
132
+ - [FastAPI](https://lite-bootstrap.modern-python.org/integrations/fastapi/)
133
+ - [FastMCP](https://lite-bootstrap.modern-python.org/integrations/fastmcp/)
134
+ - [services and scripts without frameworks](https://lite-bootstrap.modern-python.org/integrations/free/)
128
135
 
129
136
  ## Lifecycle constraints
130
137
 
@@ -155,7 +162,7 @@ The following ideas were borrowed:
155
162
  The following intentionally differ:
156
163
 
157
164
  - **Configuration**: `lite-bootstrap` uses frozen `dataclass` configs (no `pydantic` / `pydantic-settings` runtime dependency), which is what makes it "lite". `microbootstrap` configures everything through `pydantic-settings` models.
158
- - **Granular extras**: `lite-bootstrap` ships only `orjson` as a runtime dependency; every instrument (`sentry`, `otl`, `logging`, `pyroscope`) and every framework (`fastapi`, `litestar`, `faststream`) is its own extra, with per-pair combos (`fastapi-sentry`, `litestar-otl`, `faststream-metrics`, …) and `*-all` rollups. You install only what you actually use. `microbootstrap` bundles the full observability stack (opentelemetry, sentry-sdk, structlog, pyroscope-io, rich, pydantic-settings, …) as base dependencies and only splits framework packages into extras.
165
+ - **Granular extras**: `lite-bootstrap` has **no** mandatory runtime dependencies; every instrument (`sentry`, `otl`, `logging`, `pyroscope`) and every framework (`fastapi`, `litestar`, `faststream`) is its own extra, plus an opt-in `orjson` speedup for logging, per-pair combos (`fastapi-sentry`, `litestar-otl`, `faststream-metrics`, …) and `*-all` rollups. You install only what you actually use. It also runs on free-threaded CPython (3.13t/3.14t) — see [`architecture/free-threading.md`](architecture/free-threading.md). `microbootstrap` bundles the full observability stack (opentelemetry, sentry-sdk, structlog, pyroscope-io, rich, pydantic-settings, …) as base dependencies and only splits framework packages into extras.
159
166
  - **Scope**: `lite-bootstrap` is deliberately narrow — only instrument wiring. It does not include a Granian server runner or a console writer.
160
167
 
161
168
  ## 📚 [Documentation](https://lite-bootstrap.modern-python.org)
@@ -21,6 +21,7 @@
21
21
  `lite-bootstrap` wires production observability — OpenTelemetry, Prometheus, Sentry, and structlog — into FastAPI, Litestar, and FastStream services in a few lines, with no boilerplate.
22
22
 
23
23
  With `lite-bootstrap`, you receive an application with lightweight built-in support for:
24
+
24
25
  - `sentry`
25
26
  - `prometheus`
26
27
  - `opentelemetry`
@@ -32,11 +33,11 @@ With `lite-bootstrap`, you receive an application with lightweight built-in supp
32
33
 
33
34
  Those instruments can be bootstrapped for:
34
35
 
35
- - [LiteStar](https://lite-bootstrap.modern-python.org/integrations/litestar)
36
- - [FastStream](https://lite-bootstrap.modern-python.org/integrations/faststream)
37
- - [FastAPI](https://lite-bootstrap.modern-python.org/integrations/fastapi)
38
- - [FastMCP](https://lite-bootstrap.modern-python.org/integrations/fastmcp)
39
- - [services and scripts without frameworks](https://lite-bootstrap.modern-python.org/integrations/free)
36
+ - [LiteStar](https://lite-bootstrap.modern-python.org/integrations/litestar/)
37
+ - [FastStream](https://lite-bootstrap.modern-python.org/integrations/faststream/)
38
+ - [FastAPI](https://lite-bootstrap.modern-python.org/integrations/fastapi/)
39
+ - [FastMCP](https://lite-bootstrap.modern-python.org/integrations/fastmcp/)
40
+ - [services and scripts without frameworks](https://lite-bootstrap.modern-python.org/integrations/free/)
40
41
 
41
42
  ## Lifecycle constraints
42
43
 
@@ -67,7 +68,7 @@ The following ideas were borrowed:
67
68
  The following intentionally differ:
68
69
 
69
70
  - **Configuration**: `lite-bootstrap` uses frozen `dataclass` configs (no `pydantic` / `pydantic-settings` runtime dependency), which is what makes it "lite". `microbootstrap` configures everything through `pydantic-settings` models.
70
- - **Granular extras**: `lite-bootstrap` ships only `orjson` as a runtime dependency; every instrument (`sentry`, `otl`, `logging`, `pyroscope`) and every framework (`fastapi`, `litestar`, `faststream`) is its own extra, with per-pair combos (`fastapi-sentry`, `litestar-otl`, `faststream-metrics`, …) and `*-all` rollups. You install only what you actually use. `microbootstrap` bundles the full observability stack (opentelemetry, sentry-sdk, structlog, pyroscope-io, rich, pydantic-settings, …) as base dependencies and only splits framework packages into extras.
71
+ - **Granular extras**: `lite-bootstrap` has **no** mandatory runtime dependencies; every instrument (`sentry`, `otl`, `logging`, `pyroscope`) and every framework (`fastapi`, `litestar`, `faststream`) is its own extra, plus an opt-in `orjson` speedup for logging, per-pair combos (`fastapi-sentry`, `litestar-otl`, `faststream-metrics`, …) and `*-all` rollups. You install only what you actually use. It also runs on free-threaded CPython (3.13t/3.14t) — see [`architecture/free-threading.md`](architecture/free-threading.md). `microbootstrap` bundles the full observability stack (opentelemetry, sentry-sdk, structlog, pyroscope-io, rich, pydantic-settings, …) as base dependencies and only splits framework packages into extras.
71
72
  - **Scope**: `lite-bootstrap` is deliberately narrow — only instrument wiring. It does not include a Granian server runner or a console writer.
72
73
 
73
74
  ## 📚 [Documentation](https://lite-bootstrap.modern-python.org)
@@ -43,6 +43,11 @@ class FastStreamTelemetryMiddlewareProtocol(typing.Protocol):
43
43
  include_messages_counters: bool = True,
44
44
  ) -> None: ...
45
45
 
46
+ # A constructed instance is passed to broker.add_middleware, which expects a
47
+ # faststream BrokerMiddleware — a builder callable. Declared with Any to keep
48
+ # lite-bootstrap decoupled from faststream's internal middleware types.
49
+ def __call__(self, msg: typing.Any, /, *, context: typing.Any) -> typing.Any: ... # noqa: ANN401
50
+
46
51
 
47
52
  @typing.runtime_checkable
48
53
  class FastStreamPrometheusMiddlewareProtocol(typing.Protocol):
@@ -55,6 +60,9 @@ class FastStreamPrometheusMiddlewareProtocol(typing.Protocol):
55
60
  received_messages_size_buckets: typing.Sequence[float] | None = None,
56
61
  ) -> None: ...
57
62
 
63
+ # See FastStreamTelemetryMiddlewareProtocol.__call__.
64
+ def __call__(self, msg: typing.Any, /, *, context: typing.Any) -> typing.Any: ... # noqa: ANN401
65
+
58
66
 
59
67
  def _make_asgi_faststream() -> "AsgiFastStream":
60
68
  return AsgiFastStream()
@@ -0,0 +1,40 @@
1
+ from importlib.util import find_spec
2
+
3
+
4
+ def _safe_find_spec(module_name: str) -> bool:
5
+ """Return whether a dotted module is importable, treating a missing parent as absent.
6
+
7
+ find_spec imports the dotted name's parent first; a present-but-incomplete
8
+ namespace (e.g. opentelemetry-api installed without opentelemetry-instrumentation)
9
+ otherwise raises ModuleNotFoundError, crashing `import lite_bootstrap`.
10
+ """
11
+ try:
12
+ return find_spec(module_name) is not None
13
+ except ModuleNotFoundError:
14
+ return False
15
+
16
+
17
+ is_opentelemetry_installed = find_spec("opentelemetry") is not None
18
+ # opentelemetry-api provides the `opentelemetry` namespace (trace, metrics) without the
19
+ # sdk. The OTel instrument imports opentelemetry.sdk.* and needs this stricter check;
20
+ # api-only consumers (logging trace-injection, framework get_tracer_provider) use the
21
+ # flag above. See architecture/instruments.md.
22
+ is_opentelemetry_sdk_installed = _safe_find_spec("opentelemetry.sdk")
23
+ is_sentry_installed = find_spec("sentry_sdk") is not None
24
+ is_structlog_installed = find_spec("structlog") is not None
25
+ is_prometheus_client_installed = find_spec("prometheus_client") is not None
26
+ is_fastapi_installed = find_spec("fastapi") is not None
27
+ is_litestar_installed = find_spec("litestar") is not None
28
+ is_faststream_installed = find_spec("faststream") is not None
29
+ is_prometheus_fastapi_instrumentator_installed = find_spec("prometheus_fastapi_instrumentator") is not None
30
+ is_fastapi_opentelemetry_installed = is_opentelemetry_installed and _safe_find_spec(
31
+ "opentelemetry.instrumentation.fastapi"
32
+ )
33
+ is_litestar_opentelemetry_installed = (
34
+ is_opentelemetry_installed and is_litestar_installed and _safe_find_spec("opentelemetry.instrumentation.asgi")
35
+ )
36
+ is_otlp_grpc_exporter_installed = _safe_find_spec("opentelemetry.exporter.otlp.proto.grpc.trace_exporter")
37
+ is_otlp_http_exporter_installed = _safe_find_spec("opentelemetry.exporter.otlp.proto.http.trace_exporter")
38
+ is_pyroscope_installed = find_spec("pyroscope") is not None
39
+ is_fastmcp_installed = find_spec("fastmcp") is not None
40
+ is_orjson_installed = find_spec("orjson") is not None
@@ -1,14 +1,17 @@
1
1
  import dataclasses
2
+ import json
2
3
  import logging
3
4
  import logging.handlers
4
5
  import sys
5
6
  import typing
6
7
 
7
- import orjson
8
-
9
8
  from lite_bootstrap import import_checker
10
9
 
11
10
 
11
+ if import_checker.is_orjson_installed:
12
+ import orjson
13
+
14
+
12
15
  ScopeType = typing.MutableMapping[str, typing.Any]
13
16
 
14
17
 
@@ -31,10 +34,22 @@ class _MemoryLoggerFactoryConfig:
31
34
  log_stream: typing.Any = sys.stdout
32
35
 
33
36
 
34
- def _serialize_log_with_orjson_to_string(value: typing.Any, **kwargs: typing.Any) -> str: # noqa: ANN401
37
+ def _dumps_orjson(value: typing.Any, **kwargs: typing.Any) -> str: # noqa: ANN401
35
38
  return orjson.dumps(value, **kwargs).decode()
36
39
 
37
40
 
41
+ def _dumps_stdlib(value: typing.Any, **kwargs: typing.Any) -> str: # noqa: ANN401
42
+ # Match orjson's compact, UTF-8 output shape so log lines stay byte-identical.
43
+ return json.dumps(value, separators=(",", ":"), ensure_ascii=False, **kwargs)
44
+
45
+
46
+ # orjson has no free-threaded wheels and refuses to build on ft; fall back to the
47
+ # stdlib json accelerator (always ft-native) when it is absent.
48
+ # See architecture/free-threading.md.
49
+ _serialize_log_to_string = _dumps_orjson if import_checker.is_orjson_installed else _dumps_stdlib
50
+ _json_loads = orjson.loads if import_checker.is_orjson_installed else json.loads
51
+
52
+
38
53
  # Meta-keys the producer's structlog processor chain emits at the top level of every
39
54
  # rendered line (see lite_bootstrap/instruments/logging_instrument.py). They are not
40
55
  # user-supplied extra and are stripped from StructuredLogPayload.extra. If you add a
@@ -68,8 +83,8 @@ class StructuredLogPayload:
68
83
  if not formatted.startswith("{"):
69
84
  return None
70
85
  try:
71
- loaded = orjson.loads(formatted)
72
- except orjson.JSONDecodeError:
86
+ loaded = _json_loads(formatted)
87
+ except json.JSONDecodeError: # orjson.JSONDecodeError subclasses this
73
88
  return None
74
89
  if not isinstance(loaded, dict): # pragma: no cover - JSON starting with "{" is always an object
75
90
  return None
@@ -11,7 +11,7 @@ from lite_bootstrap.instruments.logging_factory import (
11
11
  RequestProtocol,
12
12
  ScopeType,
13
13
  _MemoryLoggerFactoryConfig,
14
- _serialize_log_with_orjson_to_string,
14
+ _serialize_log_to_string,
15
15
  )
16
16
 
17
17
 
@@ -117,7 +117,7 @@ class LoggingInstrument(BaseInstrument[LoggingConfig]):
117
117
  structlog.stdlib.filter_by_level,
118
118
  *self.structlog_pre_chain_processors,
119
119
  *self.bootstrap_config.logging_extra_processors,
120
- structlog.processors.JSONRenderer(serializer=_serialize_log_with_orjson_to_string),
120
+ structlog.processors.JSONRenderer(serializer=_serialize_log_to_string),
121
121
  ]
122
122
 
123
123
  @property
@@ -152,7 +152,7 @@ class LoggingInstrument(BaseInstrument[LoggingConfig]):
152
152
  processors=[
153
153
  structlog.stdlib.ProcessorFormatter.remove_processors_meta,
154
154
  *self.bootstrap_config.logging_extra_processors,
155
- structlog.processors.JSONRenderer(serializer=_serialize_log_with_orjson_to_string),
155
+ structlog.processors.JSONRenderer(serializer=_serialize_log_to_string),
156
156
  ],
157
157
  logger=root_logger,
158
158
  )
@@ -6,21 +6,29 @@ import urllib.parse
6
6
  import warnings
7
7
 
8
8
  from lite_bootstrap import import_checker
9
- from lite_bootstrap.exceptions import TeardownError
9
+ from lite_bootstrap.exceptions import InstrumentDependencyMissingWarning, TeardownError
10
10
  from lite_bootstrap.instruments.base import BaseConfig, BaseInstrument
11
11
 
12
12
 
13
13
  if typing.TYPE_CHECKING:
14
14
  from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
15
15
 
16
- if import_checker.is_opentelemetry_installed:
16
+ if import_checker.is_opentelemetry_sdk_installed:
17
17
  from opentelemetry.context import Context
18
- from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
19
18
  from opentelemetry.sdk import resources
20
19
  from opentelemetry.sdk.trace import ReadableSpan, SpanProcessor, TracerProvider
21
20
  from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter, SimpleSpanProcessor
22
21
  from opentelemetry.trace import Span, format_span_id, set_tracer_provider
23
22
 
23
+ if import_checker.is_otlp_grpc_exporter_installed:
24
+ # opentelemetry-api can be present without the grpc otlp exporter package (e.g.
25
+ # lite-bootstrap[fastmcp] pulls bare opentelemetry-api transitively); this must
26
+ # stay a separate guard from is_opentelemetry_installed above.
27
+ from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter as OTLPGrpcSpanExporter
28
+
29
+ if import_checker.is_otlp_http_exporter_installed:
30
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter as OTLPHttpSpanExporter
31
+
24
32
  if import_checker.is_pyroscope_installed:
25
33
  import pyroscope
26
34
 
@@ -51,6 +59,7 @@ class OpenTelemetryConfig(OpenTelemetryServiceFieldsConfig):
51
59
  )
52
60
  opentelemetry_endpoint: str | None = None
53
61
  opentelemetry_insecure: bool = True
62
+ opentelemetry_exporter_protocol: typing.Literal["grpc", "http"] = "grpc"
54
63
  opentelemetry_instrumentors: list[typing.Union[InstrumentorWithParams, "BaseInstrumentor"]] = dataclasses.field(
55
64
  default_factory=list
56
65
  )
@@ -70,6 +79,8 @@ class OpenTelemetryConfig(OpenTelemetryServiceFieldsConfig):
70
79
 
71
80
  def _parse_remote_insecure_host(self) -> str | None:
72
81
  """Return the host name if the endpoint is insecure AND non-local; else None."""
82
+ if self.opentelemetry_exporter_protocol != "grpc":
83
+ return None
73
84
  if not self.opentelemetry_endpoint or not self.opentelemetry_insecure:
74
85
  return None
75
86
  if self.opentelemetry_endpoint.startswith("unix://"):
@@ -86,7 +97,7 @@ class OpenTelemetryConfig(OpenTelemetryServiceFieldsConfig):
86
97
  return host
87
98
 
88
99
 
89
- if import_checker.is_opentelemetry_installed and import_checker.is_pyroscope_installed:
100
+ if import_checker.is_opentelemetry_sdk_installed and import_checker.is_pyroscope_installed:
90
101
  _OTEL_PROFILE_ID_KEY: typing.Final = "pyroscope.profile.id"
91
102
  _PYROSCOPE_SPAN_ID_KEY: typing.Final = "span_id"
92
103
  _PYROSCOPE_SPAN_NAME_KEY: typing.Final = "span_name"
@@ -126,7 +137,7 @@ class OpenTelemetryInstrument(BaseInstrument[OpenTelemetryConfig]):
126
137
  """
127
138
 
128
139
  not_ready_message = "opentelemetry_endpoint is empty and opentelemetry_log_traces is False"
129
- missing_dependency_message = "opentelemetry is not installed"
140
+ missing_dependency_message = "opentelemetry-sdk is not installed"
130
141
  _tracer_provider: "TracerProvider | None" = dataclasses.field(
131
142
  default_factory=lambda: None, init=False, repr=False, compare=False
132
143
  )
@@ -140,7 +151,9 @@ class OpenTelemetryInstrument(BaseInstrument[OpenTelemetryConfig]):
140
151
 
141
152
  @staticmethod
142
153
  def check_dependencies() -> bool:
143
- return import_checker.is_opentelemetry_installed
154
+ # The instrument imports from both the api (opentelemetry.trace/.context) and
155
+ # the sdk (opentelemetry.sdk.*), so it needs both distributions present.
156
+ return import_checker.is_opentelemetry_installed and import_checker.is_opentelemetry_sdk_installed
144
157
 
145
158
  def _build_excluded_urls(self) -> set[str]:
146
159
  excluded_urls: set[str] = set(self.bootstrap_config.opentelemetry_excluded_urls)
@@ -176,15 +189,37 @@ class OpenTelemetryInstrument(BaseInstrument[OpenTelemetryConfig]):
176
189
  tracer_provider.add_span_processor(PyroscopeSpanProcessor())
177
190
  if self.bootstrap_config.opentelemetry_log_traces:
178
191
  tracer_provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter(formatter=_format_span)))
179
- if self.bootstrap_config.opentelemetry_endpoint: # pragma: no cover
180
- tracer_provider.add_span_processor(
181
- BatchSpanProcessor(
182
- OTLPSpanExporter(
183
- endpoint=self.bootstrap_config.opentelemetry_endpoint,
184
- insecure=self.bootstrap_config.opentelemetry_insecure,
192
+ if self.bootstrap_config.opentelemetry_endpoint:
193
+ if self.bootstrap_config.opentelemetry_exporter_protocol == "grpc":
194
+ if import_checker.is_otlp_grpc_exporter_installed:
195
+ tracer_provider.add_span_processor(
196
+ BatchSpanProcessor(
197
+ OTLPGrpcSpanExporter(
198
+ endpoint=self.bootstrap_config.opentelemetry_endpoint,
199
+ insecure=self.bootstrap_config.opentelemetry_insecure,
200
+ ),
201
+ ),
202
+ )
203
+ else:
204
+ warnings.warn(
205
+ "opentelemetry_endpoint is set but the gRPC OTLP exporter is not installed; "
206
+ "spans will not be exported. Install lite-bootstrap[otl].",
207
+ category=InstrumentDependencyMissingWarning,
208
+ stacklevel=2,
209
+ )
210
+ elif import_checker.is_otlp_http_exporter_installed:
211
+ tracer_provider.add_span_processor(
212
+ BatchSpanProcessor(
213
+ OTLPHttpSpanExporter(endpoint=self.bootstrap_config.opentelemetry_endpoint),
185
214
  ),
186
- ),
187
- )
215
+ )
216
+ else:
217
+ warnings.warn(
218
+ "opentelemetry_endpoint is set but the HTTP OTLP exporter is not installed; "
219
+ "spans will not be exported. Install lite-bootstrap[otl-http].",
220
+ category=InstrumentDependencyMissingWarning,
221
+ stacklevel=2,
222
+ )
188
223
  for one_instrumentor in self.bootstrap_config.opentelemetry_instrumentors:
189
224
  if isinstance(one_instrumentor, InstrumentorWithParams):
190
225
  one_instrumentor.instrumentor.instrument(
@@ -19,10 +19,8 @@ classifiers = [
19
19
  "Typing :: Typed",
20
20
  "Topic :: Software Development :: Libraries",
21
21
  ]
22
- version = "1.2.2"
23
- dependencies = [
24
- "orjson",
25
- ]
22
+ version = "1.3.0"
23
+ dependencies = []
26
24
 
27
25
  [project.urls]
28
26
  Homepage = "https://lite-bootstrap.modern-python.org"
@@ -32,6 +30,9 @@ Issues = "https://github.com/modern-python/lite-bootstrap/issues"
32
30
  Changelog = "https://github.com/modern-python/lite-bootstrap/releases"
33
31
 
34
32
  [project.optional-dependencies]
33
+ orjson = [
34
+ "orjson",
35
+ ]
35
36
  sentry = [
36
37
  "sentry-sdk",
37
38
  ]
@@ -41,7 +42,13 @@ pyroscope = [
41
42
  otl = [
42
43
  "opentelemetry-api",
43
44
  "opentelemetry-sdk",
44
- "opentelemetry-exporter-otlp",
45
+ "opentelemetry-exporter-otlp-proto-grpc",
46
+ "opentelemetry-instrumentation",
47
+ ]
48
+ otl-http = [
49
+ "opentelemetry-api",
50
+ "opentelemetry-sdk",
51
+ "opentelemetry-exporter-otlp-proto-http",
45
52
  "opentelemetry-instrumentation",
46
53
  ]
47
54
  logging = [
@@ -170,6 +177,9 @@ ignore = [
170
177
  isort.lines-after-imports = 2
171
178
  isort.no-lines-before = ["standard-library", "local-folder"]
172
179
 
180
+ [tool.ruff.lint.per-file-ignores]
181
+ "scripts/*.py" = ["INP001"] # standalone scripts, not an importable package
182
+
173
183
  [tool.pytest.ini_options]
174
184
  addopts = "--cov=. --cov-report term-missing --cov-fail-under=100"
175
185
  asyncio_mode = "auto"
@@ -1,19 +0,0 @@
1
- from importlib.util import find_spec
2
-
3
-
4
- is_opentelemetry_installed = find_spec("opentelemetry") is not None
5
- is_sentry_installed = find_spec("sentry_sdk") is not None
6
- is_structlog_installed = find_spec("structlog") is not None
7
- is_prometheus_client_installed = find_spec("prometheus_client") is not None
8
- is_fastapi_installed = find_spec("fastapi") is not None
9
- is_litestar_installed = find_spec("litestar") is not None
10
- is_faststream_installed = find_spec("faststream") is not None
11
- is_prometheus_fastapi_instrumentator_installed = find_spec("prometheus_fastapi_instrumentator") is not None
12
- is_fastapi_opentelemetry_installed = (
13
- is_opentelemetry_installed and find_spec("opentelemetry.instrumentation.fastapi") is not None
14
- )
15
- is_litestar_opentelemetry_installed = (
16
- is_opentelemetry_installed and is_litestar_installed and find_spec("opentelemetry.instrumentation.asgi") is not None
17
- )
18
- is_pyroscope_installed = find_spec("pyroscope") is not None
19
- is_fastmcp_installed = find_spec("fastmcp") is not None