httpware 0.8.3__tar.gz → 0.8.4__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 (23) hide show
  1. {httpware-0.8.3 → httpware-0.8.4}/PKG-INFO +1 -1
  2. {httpware-0.8.3 → httpware-0.8.4}/pyproject.toml +1 -1
  3. httpware-0.8.4/src/httpware/_internal/import_checker.py +26 -0
  4. {httpware-0.8.3 → httpware-0.8.4}/src/httpware/_internal/observability.py +9 -3
  5. httpware-0.8.3/src/httpware/_internal/import_checker.py +0 -8
  6. {httpware-0.8.3 → httpware-0.8.4}/README.md +0 -0
  7. {httpware-0.8.3 → httpware-0.8.4}/src/httpware/__init__.py +0 -0
  8. {httpware-0.8.3 → httpware-0.8.4}/src/httpware/_internal/__init__.py +0 -0
  9. {httpware-0.8.3 → httpware-0.8.4}/src/httpware/_internal/exception_mapping.py +0 -0
  10. {httpware-0.8.3 → httpware-0.8.4}/src/httpware/_internal/status.py +0 -0
  11. {httpware-0.8.3 → httpware-0.8.4}/src/httpware/client.py +0 -0
  12. {httpware-0.8.3 → httpware-0.8.4}/src/httpware/decoders/__init__.py +0 -0
  13. {httpware-0.8.3 → httpware-0.8.4}/src/httpware/decoders/msgspec.py +0 -0
  14. {httpware-0.8.3 → httpware-0.8.4}/src/httpware/decoders/pydantic.py +0 -0
  15. {httpware-0.8.3 → httpware-0.8.4}/src/httpware/errors.py +0 -0
  16. {httpware-0.8.3 → httpware-0.8.4}/src/httpware/middleware/__init__.py +0 -0
  17. {httpware-0.8.3 → httpware-0.8.4}/src/httpware/middleware/chain.py +0 -0
  18. {httpware-0.8.3 → httpware-0.8.4}/src/httpware/middleware/resilience/__init__.py +0 -0
  19. {httpware-0.8.3 → httpware-0.8.4}/src/httpware/middleware/resilience/_backoff.py +0 -0
  20. {httpware-0.8.3 → httpware-0.8.4}/src/httpware/middleware/resilience/budget.py +0 -0
  21. {httpware-0.8.3 → httpware-0.8.4}/src/httpware/middleware/resilience/bulkhead.py +0 -0
  22. {httpware-0.8.3 → httpware-0.8.4}/src/httpware/middleware/resilience/retry.py +0 -0
  23. {httpware-0.8.3 → httpware-0.8.4}/src/httpware/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: httpware
3
- Version: 0.8.3
3
+ Version: 0.8.4
4
4
  Summary: Resilience-first async HTTP client framework for Python
5
5
  Keywords: http,async,client,resilience,retry,circuit-breaker,middleware,httpx,pydantic
6
6
  Author: Artur Shiriev
@@ -26,7 +26,7 @@ classifiers = [
26
26
  "Topic :: Internet :: WWW/HTTP",
27
27
  "Framework :: AsyncIO",
28
28
  ]
29
- version = "0.8.3"
29
+ version = "0.8.4"
30
30
  dependencies = [
31
31
  "httpx2>=2.0.0,<3.0",
32
32
  ]
@@ -0,0 +1,26 @@
1
+ """Detect optional extras without importing them. Used by adapter modules to gate hard imports."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, distribution
4
+ from importlib.util import find_spec
5
+
6
+
7
+ def _is_distribution_installed(name: str) -> bool:
8
+ """Probe the package registry for a distribution by name. No sys.modules side effects."""
9
+ try:
10
+ distribution(name)
11
+ except PackageNotFoundError:
12
+ return False
13
+ return True
14
+
15
+
16
+ is_msgspec_installed = find_spec("msgspec") is not None
17
+ is_pydantic_installed = find_spec("pydantic") is not None
18
+ # opentelemetry/ is a PEP 420 namespace package — instrumentation packages create the
19
+ # directory even without opentelemetry-api. find_spec("opentelemetry") therefore returns
20
+ # non-None regardless of whether the api package is present, and
21
+ # find_spec("opentelemetry.trace") populates sys.modules with the namespace parent as a
22
+ # CPython side-effect, breaking the isolation guarantee.
23
+ # importlib.metadata.distribution probes the package registry instead: it returns the
24
+ # distribution when opentelemetry-api is installed and raises PackageNotFoundError when
25
+ # it is absent, with no sys.modules side effects.
26
+ is_otel_installed = _is_distribution_installed("opentelemetry-api")
@@ -29,7 +29,9 @@ def _emit_event(
29
29
  ``trace.get_current_span().add_event(event_name, attributes=attributes)``.
30
30
  When no tracer is active, ``get_current_span()`` returns a ``NonRecordingSpan``
31
31
  whose ``add_event`` is a documented no-op — so the call is unconditional
32
- behind the install gate.
32
+ behind the install gate. If the install gate is wrong (the namespace exists
33
+ but the api package is missing or broken), the lazy import raises
34
+ ``ImportError``; we degrade silently to log-only emission.
33
35
 
34
36
  The lazy ``from opentelemetry import trace`` inside the if-block preserves
35
37
  the optional-extras isolation invariant: ``import httpware`` must not pull
@@ -37,6 +39,10 @@ def _emit_event(
37
39
  """
38
40
  logger.log(level, message, extra=attributes)
39
41
  if import_checker.is_otel_installed:
40
- from opentelemetry import trace # noqa: PLC0415 — lazy by design (optional-extras isolation)
41
-
42
+ try:
43
+ from opentelemetry import trace # noqa: PLC0415 — lazy by design (optional-extras isolation)
44
+ except ImportError:
45
+ # opentelemetry namespace exists but the api package is broken or missing —
46
+ # degrade to log-only emission. The structured log record above has already fired.
47
+ return
42
48
  trace.get_current_span().add_event(event_name, attributes=attributes)
@@ -1,8 +0,0 @@
1
- """Detect optional extras without importing them. Used by adapter modules to gate hard imports."""
2
-
3
- from importlib.util import find_spec
4
-
5
-
6
- is_msgspec_installed = find_spec("msgspec") is not None
7
- is_pydantic_installed = find_spec("pydantic") is not None
8
- is_otel_installed = find_spec("opentelemetry") is not None
File without changes
File without changes