provide-foundation 0.0.0.dev2__py3-none-any.whl → 0.0.0.dev3__py3-none-any.whl

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 (155) hide show
  1. provide/foundation/__init__.py +20 -20
  2. provide/foundation/archive/__init__.py +1 -1
  3. provide/foundation/archive/base.py +15 -14
  4. provide/foundation/archive/bzip2.py +40 -40
  5. provide/foundation/archive/gzip.py +42 -42
  6. provide/foundation/archive/operations.py +90 -91
  7. provide/foundation/archive/tar.py +33 -31
  8. provide/foundation/archive/zip.py +52 -50
  9. provide/foundation/asynctools/__init__.py +20 -0
  10. provide/foundation/asynctools/core.py +126 -0
  11. provide/foundation/cli/__init__.py +2 -2
  12. provide/foundation/cli/commands/deps.py +4 -4
  13. provide/foundation/cli/commands/logs/__init__.py +2 -2
  14. provide/foundation/cli/commands/logs/generate.py +2 -2
  15. provide/foundation/cli/commands/logs/query.py +3 -3
  16. provide/foundation/cli/commands/logs/send.py +2 -2
  17. provide/foundation/cli/commands/logs/tail.py +2 -2
  18. provide/foundation/cli/decorators.py +0 -1
  19. provide/foundation/cli/testing.py +0 -5
  20. provide/foundation/cli/utils.py +1 -2
  21. provide/foundation/config/__init__.py +19 -19
  22. provide/foundation/config/base.py +2 -2
  23. provide/foundation/config/converters.py +81 -83
  24. provide/foundation/config/defaults.py +1 -1
  25. provide/foundation/config/env.py +2 -1
  26. provide/foundation/config/loader.py +1 -1
  27. provide/foundation/config/sync.py +8 -6
  28. provide/foundation/config/types.py +5 -5
  29. provide/foundation/config/validators.py +4 -4
  30. provide/foundation/console/output.py +7 -7
  31. provide/foundation/context/core.py +19 -17
  32. provide/foundation/crypto/certificates/__init__.py +9 -5
  33. provide/foundation/crypto/certificates/base.py +2 -2
  34. provide/foundation/crypto/certificates/certificate.py +48 -19
  35. provide/foundation/crypto/certificates/factory.py +26 -18
  36. provide/foundation/crypto/certificates/generator.py +24 -23
  37. provide/foundation/crypto/certificates/loader.py +24 -16
  38. provide/foundation/crypto/certificates/operations.py +17 -10
  39. provide/foundation/crypto/certificates/trust.py +21 -21
  40. provide/foundation/env/__init__.py +28 -0
  41. provide/foundation/env/core.py +218 -0
  42. provide/foundation/errors/__init__.py +3 -2
  43. provide/foundation/errors/decorators.py +0 -3
  44. provide/foundation/errors/types.py +0 -1
  45. provide/foundation/eventsets/display.py +13 -14
  46. provide/foundation/eventsets/registry.py +61 -31
  47. provide/foundation/eventsets/resolver.py +50 -46
  48. provide/foundation/eventsets/sets/das.py +8 -8
  49. provide/foundation/eventsets/sets/database.py +14 -14
  50. provide/foundation/eventsets/sets/http.py +21 -21
  51. provide/foundation/eventsets/sets/llm.py +16 -16
  52. provide/foundation/eventsets/sets/task_queue.py +13 -13
  53. provide/foundation/eventsets/types.py +7 -7
  54. provide/foundation/file/directory.py +1 -1
  55. provide/foundation/file/lock.py +2 -3
  56. provide/foundation/hub/components.py +19 -21
  57. provide/foundation/hub/config.py +25 -19
  58. provide/foundation/hub/discovery.py +5 -4
  59. provide/foundation/hub/handlers.py +13 -5
  60. provide/foundation/hub/lifecycle.py +10 -9
  61. provide/foundation/hub/manager.py +3 -0
  62. provide/foundation/hub/processors.py +8 -3
  63. provide/foundation/integrations/__init__.py +1 -1
  64. provide/foundation/integrations/openobserve/client.py +2 -2
  65. provide/foundation/integrations/openobserve/commands.py +9 -9
  66. provide/foundation/integrations/openobserve/config.py +2 -2
  67. provide/foundation/integrations/openobserve/otlp.py +2 -2
  68. provide/foundation/integrations/openobserve/search.py +1 -2
  69. provide/foundation/integrations/openobserve/streaming.py +1 -1
  70. provide/foundation/logger/__init__.py +0 -1
  71. provide/foundation/logger/config/base.py +1 -1
  72. provide/foundation/logger/config/logging.py +19 -19
  73. provide/foundation/logger/config/telemetry.py +11 -13
  74. provide/foundation/logger/factories.py +2 -2
  75. provide/foundation/logger/processors/main.py +12 -10
  76. provide/foundation/logger/ratelimit/limiters.py +4 -4
  77. provide/foundation/logger/ratelimit/processor.py +1 -1
  78. provide/foundation/logger/setup/coordinator.py +38 -24
  79. provide/foundation/logger/setup/processors.py +3 -3
  80. provide/foundation/logger/setup/testing.py +14 -0
  81. provide/foundation/logger/trace.py +5 -5
  82. provide/foundation/metrics/__init__.py +1 -1
  83. provide/foundation/metrics/otel.py +3 -1
  84. provide/foundation/observability/__init__.py +1 -1
  85. provide/foundation/process/__init__.py +1 -1
  86. provide/foundation/process/exit.py +6 -5
  87. provide/foundation/process/lifecycle.py +41 -18
  88. provide/foundation/resilience/__init__.py +6 -5
  89. provide/foundation/resilience/circuit.py +32 -30
  90. provide/foundation/resilience/decorators.py +58 -42
  91. provide/foundation/resilience/fallback.py +55 -40
  92. provide/foundation/resilience/retry.py +67 -65
  93. provide/foundation/serialization/__init__.py +16 -0
  94. provide/foundation/serialization/core.py +70 -0
  95. provide/foundation/streams/config.py +8 -9
  96. provide/foundation/streams/console.py +3 -3
  97. provide/foundation/streams/core.py +2 -2
  98. provide/foundation/streams/file.py +1 -1
  99. provide/foundation/testing/__init__.py +22 -7
  100. provide/foundation/testing/archive/__init__.py +7 -7
  101. provide/foundation/testing/archive/fixtures.py +58 -54
  102. provide/foundation/testing/cli.py +3 -6
  103. provide/foundation/testing/common/__init__.py +13 -13
  104. provide/foundation/testing/common/fixtures.py +27 -30
  105. provide/foundation/testing/file/__init__.py +15 -15
  106. provide/foundation/testing/file/content_fixtures.py +65 -92
  107. provide/foundation/testing/file/directory_fixtures.py +19 -19
  108. provide/foundation/testing/file/fixtures.py +14 -17
  109. provide/foundation/testing/file/special_fixtures.py +34 -42
  110. provide/foundation/testing/logger.py +28 -23
  111. provide/foundation/testing/mocking/__init__.py +21 -21
  112. provide/foundation/testing/mocking/fixtures.py +80 -67
  113. provide/foundation/testing/process/__init__.py +23 -23
  114. provide/foundation/testing/process/async_fixtures.py +89 -80
  115. provide/foundation/testing/process/fixtures.py +11 -13
  116. provide/foundation/testing/process/subprocess_fixtures.py +41 -40
  117. provide/foundation/testing/threading/__init__.py +17 -17
  118. provide/foundation/testing/threading/basic_fixtures.py +21 -17
  119. provide/foundation/testing/threading/data_fixtures.py +18 -16
  120. provide/foundation/testing/threading/execution_fixtures.py +67 -52
  121. provide/foundation/testing/threading/fixtures.py +10 -14
  122. provide/foundation/testing/threading/sync_fixtures.py +21 -18
  123. provide/foundation/testing/time/__init__.py +11 -11
  124. provide/foundation/testing/time/fixtures.py +91 -79
  125. provide/foundation/testing/transport/__init__.py +9 -9
  126. provide/foundation/testing/transport/fixtures.py +54 -54
  127. provide/foundation/time/__init__.py +18 -0
  128. provide/foundation/time/core.py +63 -0
  129. provide/foundation/tools/__init__.py +2 -2
  130. provide/foundation/tools/base.py +68 -67
  131. provide/foundation/tools/cache.py +62 -69
  132. provide/foundation/tools/downloader.py +51 -56
  133. provide/foundation/tools/installer.py +51 -57
  134. provide/foundation/tools/registry.py +38 -45
  135. provide/foundation/tools/resolver.py +70 -68
  136. provide/foundation/tools/verifier.py +39 -50
  137. provide/foundation/tracer/spans.py +1 -13
  138. provide/foundation/transport/__init__.py +26 -33
  139. provide/foundation/transport/base.py +32 -30
  140. provide/foundation/transport/client.py +44 -49
  141. provide/foundation/transport/config.py +11 -13
  142. provide/foundation/transport/errors.py +13 -27
  143. provide/foundation/transport/http.py +69 -55
  144. provide/foundation/transport/middleware.py +86 -81
  145. provide/foundation/transport/registry.py +29 -27
  146. provide/foundation/transport/types.py +6 -6
  147. provide/foundation/utils/deps.py +3 -2
  148. provide/foundation/utils/parsing.py +7 -7
  149. {provide_foundation-0.0.0.dev2.dist-info → provide_foundation-0.0.0.dev3.dist-info}/METADATA +2 -2
  150. provide_foundation-0.0.0.dev3.dist-info/RECORD +233 -0
  151. provide_foundation-0.0.0.dev2.dist-info/RECORD +0 -225
  152. {provide_foundation-0.0.0.dev2.dist-info → provide_foundation-0.0.0.dev3.dist-info}/WHEEL +0 -0
  153. {provide_foundation-0.0.0.dev2.dist-info → provide_foundation-0.0.0.dev3.dist-info}/entry_points.txt +0 -0
  154. {provide_foundation-0.0.0.dev2.dist-info → provide_foundation-0.0.0.dev3.dist-info}/licenses/LICENSE +0 -0
  155. {provide_foundation-0.0.0.dev2.dist-info → provide_foundation-0.0.0.dev3.dist-info}/top_level.txt +0 -0
@@ -6,7 +6,6 @@ checksum algorithms and GPG/PGP signatures.
6
6
  """
7
7
 
8
8
  import hashlib
9
- import re
10
9
  from pathlib import Path
11
10
  from typing import Literal
12
11
 
@@ -18,7 +17,7 @@ log = get_logger(__name__)
18
17
 
19
18
  class VerificationError(FoundationError):
20
19
  """Raised when verification fails."""
21
-
20
+
22
21
  pass
23
22
 
24
23
 
@@ -28,125 +27,115 @@ HashAlgo = Literal["sha256", "sha512", "md5", "blake2b"]
28
27
  class ToolVerifier:
29
28
  """
30
29
  Verify tool artifacts using checksums and signatures.
31
-
30
+
32
31
  Supports multiple checksum algorithms and GPG/PGP signatures
33
32
  for ensuring artifact integrity and authenticity.
34
33
  """
35
-
34
+
36
35
  SUPPORTED_ALGORITHMS = ["sha256", "sha512", "md5", "blake2b"]
37
36
  CHUNK_SIZE = 8192 # Read files in 8KB chunks
38
-
37
+
39
38
  def verify_checksum(
40
- self,
41
- file_path: Path,
42
- expected: str,
43
- algo: HashAlgo = "sha256"
39
+ self, file_path: Path, expected: str, algo: HashAlgo = "sha256"
44
40
  ) -> bool:
45
41
  """
46
42
  Verify file checksum.
47
-
43
+
48
44
  Args:
49
45
  file_path: Path to file to verify.
50
46
  expected: Expected checksum (hex string).
51
47
  algo: Hash algorithm to use.
52
-
48
+
53
49
  Returns:
54
50
  True if checksum matches, False otherwise.
55
-
51
+
56
52
  Raises:
57
53
  ValueError: If algorithm is not supported.
58
54
  FileNotFoundError: If file doesn't exist.
59
55
  """
60
56
  if algo not in self.SUPPORTED_ALGORITHMS:
61
57
  raise ValueError(f"Unsupported hash algorithm: {algo}")
62
-
58
+
63
59
  if not file_path.exists():
64
60
  raise FileNotFoundError(f"File not found: {file_path}")
65
-
61
+
66
62
  log.debug(f"Verifying {algo} checksum for {file_path}")
67
-
63
+
68
64
  # Create hasher
69
65
  hasher = hashlib.new(algo)
70
-
66
+
71
67
  # Read file in chunks
72
68
  with file_path.open("rb") as f:
73
69
  while chunk := f.read(self.CHUNK_SIZE):
74
70
  hasher.update(chunk)
75
-
71
+
76
72
  actual = hasher.hexdigest()
77
73
  matches = actual == expected
78
-
74
+
79
75
  if not matches:
80
76
  log.warning(
81
77
  f"Checksum mismatch for {file_path.name}: "
82
78
  f"expected {expected}, got {actual}"
83
79
  )
84
-
80
+
85
81
  return matches
86
-
87
- def verify_shasums_file(
88
- self,
89
- shasums_file: Path,
90
- target_file: Path
91
- ) -> bool:
82
+
83
+ def verify_shasums_file(self, shasums_file: Path, target_file: Path) -> bool:
92
84
  """
93
85
  Verify using a shasums file (common for Go/Terraform).
94
-
86
+
95
87
  Args:
96
88
  shasums_file: Path to shasums file.
97
89
  target_file: Path to file to verify.
98
-
90
+
99
91
  Returns:
100
92
  True if file is listed and checksum matches, False otherwise.
101
93
  """
102
94
  log.debug(f"Verifying {target_file.name} using {shasums_file}")
103
-
95
+
104
96
  with shasums_file.open() as f:
105
97
  for line in f:
106
98
  line = line.strip()
107
99
  if not line:
108
100
  continue
109
-
101
+
110
102
  # Parse line: "checksum filename" or "checksum *filename"
111
103
  parts = line.split(None, 1)
112
104
  if len(parts) != 2:
113
105
  continue
114
-
106
+
115
107
  checksum, filename = parts
116
108
  # Remove asterisk prefix if present (binary mode indicator)
117
109
  filename = filename.lstrip("*")
118
-
110
+
119
111
  # Check if this is our file
120
112
  if filename == target_file.name:
121
113
  return self.verify_checksum(target_file, checksum)
122
-
114
+
123
115
  # File not found in shasums
124
116
  log.warning(f"{target_file.name} not found in {shasums_file}")
125
117
  return False
126
-
118
+
127
119
  def verify_signature(
128
- self,
129
- file_path: Path,
130
- signature: str,
131
- public_key: str | None = None
120
+ self, file_path: Path, signature: str, public_key: str | None = None
132
121
  ) -> bool:
133
122
  """
134
123
  Verify GPG/PGP signature.
135
-
124
+
136
125
  Args:
137
126
  file_path: Path to file to verify.
138
127
  signature: Signature data.
139
128
  public_key: Optional public key for verification.
140
-
129
+
141
130
  Returns:
142
131
  True if signature is valid, False otherwise.
143
132
  """
144
133
  log.debug(f"Verifying signature for {file_path}")
145
-
134
+
146
135
  try:
147
136
  # Use foundation's crypto module
148
137
  from provide.foundation.crypto import verify_signature
149
-
138
+
150
139
  return verify_signature(file_path, signature, public_key)
151
140
  except ImportError:
152
141
  log.warning("Crypto module not available, skipping signature verification")
@@ -154,33 +143,33 @@ class ToolVerifier:
154
143
  except Exception as e:
155
144
  log.error(f"Signature verification failed: {e}")
156
145
  return False
157
-
146
+
158
147
  def extract_checksum(self, checksum_string: str) -> str:
159
148
  """
160
149
  Extract checksum from various string formats.
161
-
150
+
162
151
  Handles formats like:
163
152
  - "abc123"
164
153
  - "abc123 filename.tar.gz"
165
154
  - "sha256:abc123"
166
155
  - "SHA256:def456"
167
-
156
+
168
157
  Args:
169
158
  checksum_string: String containing checksum.
170
-
159
+
171
160
  Returns:
172
161
  Extracted checksum hex string.
173
162
  """
174
163
  checksum_string = checksum_string.strip()
175
-
164
+
176
165
  # Remove algorithm prefix if present
177
166
  if ":" in checksum_string:
178
167
  checksum_string = checksum_string.split(":", 1)[1]
179
-
168
+
180
169
  # Take first word (checksum is before any whitespace)
181
170
  checksum = checksum_string.split()[0]
182
-
171
+
183
172
  # Remove any asterisk prefix (binary mode indicator)
184
173
  checksum = checksum.lstrip("*")
185
-
186
- return checksum
174
+
175
+ return checksum
@@ -47,9 +47,7 @@ class Span:
47
47
  error: str | None = None
48
48
 
49
49
  # Internal OpenTelemetry span (when available)
50
- _otel_span: "otel_trace.Span | None" = field(
51
- default=None, init=False, repr=False
52
- )
50
+ _otel_span: "otel_trace.Span | None" = field(default=None, init=False, repr=False)
53
51
  _active: bool = field(default=True, init=False, repr=False)
54
52
 
55
53
  def __post_init__(self) -> None:
@@ -162,13 +160,3 @@ class Span:
162
160
  "status": self.status,
163
161
  "error": self.error,
164
162
  }
165
-
166
- def __enter__(self):
167
- """Context manager entry."""
168
- return self
169
-
170
- def __exit__(self, exc_type, exc_val, exc_tb):
171
- """Context manager exit."""
172
- if exc_type is not None:
173
- self.set_error(f"{exc_type.__name__}: {exc_val}")
174
- self.finish()
@@ -14,28 +14,28 @@ Key Features:
14
14
 
15
15
  Example Usage:
16
16
  >>> from provide.foundation.transport import get, post
17
- >>>
17
+ >>>
18
18
  >>> # Simple requests
19
19
  >>> response = await get("https://api.example.com/users")
20
20
  >>> data = response.json()
21
- >>>
21
+ >>>
22
22
  >>> # POST with JSON body
23
23
  >>> response = await post(
24
24
  ... "https://api.example.com/users",
25
25
  ... body={"name": "John", "email": "john@example.com"}
26
26
  ... )
27
- >>>
27
+ >>>
28
28
  >>> # Using client for multiple requests
29
29
  >>> from provide.foundation.transport import UniversalClient
30
- >>>
30
+ >>>
31
31
  >>> async with UniversalClient() as client:
32
32
  ... users = await client.get("https://api.example.com/users")
33
33
  ... posts = await client.get("https://api.example.com/posts")
34
- >>>
34
+ >>>
35
35
  >>> # Custom transport registration
36
36
  >>> from provide.foundation.transport import register_transport
37
37
  >>> from provide.foundation.transport.types import TransportType
38
- >>>
38
+ >>>
39
39
  >>> register_transport(TransportType("custom"), MyCustomTransport)
40
40
 
41
41
  Environment Configuration:
@@ -43,7 +43,7 @@ Environment Configuration:
43
43
  TRANSPORT_MAX_RETRIES=3
44
44
  TRANSPORT_RETRY_BACKOFF_FACTOR=0.5
45
45
  TRANSPORT_VERIFY_SSL=true
46
-
46
+
47
47
  HTTP_POOL_CONNECTIONS=10
48
48
  HTTP_POOL_MAXSIZE=100
49
49
  HTTP_FOLLOW_REDIRECTS=true
@@ -54,9 +54,23 @@ Environment Configuration:
54
54
  # Core transport abstractions
55
55
  from provide.foundation.transport.base import Request, Response
56
56
 
57
+ # High-level client API
58
+ from provide.foundation.transport.client import (
59
+ UniversalClient,
60
+ delete,
61
+ get,
62
+ get_default_client,
63
+ head,
64
+ options,
65
+ patch,
66
+ post,
67
+ put,
68
+ request,
69
+ stream,
70
+ )
71
+
57
72
  # Transport types and configuration
58
73
  from provide.foundation.transport.config import HTTPConfig, TransportConfig
59
- from provide.foundation.transport.types import HTTPMethod, TransportType
60
74
 
61
75
  # Error types
62
76
  from provide.foundation.transport.errors import (
@@ -87,45 +101,26 @@ from provide.foundation.transport.registry import (
87
101
  list_registered_transports,
88
102
  register_transport,
89
103
  )
90
-
91
- # High-level client API
92
- from provide.foundation.transport.client import (
93
- UniversalClient,
94
- delete,
95
- get,
96
- get_default_client,
97
- head,
98
- options,
99
- patch,
100
- post,
101
- put,
102
- request,
103
- stream,
104
- )
104
+ from provide.foundation.transport.types import HTTPMethod, TransportType
105
105
 
106
106
  __all__ = [
107
107
  # Core abstractions
108
108
  "Request",
109
109
  "Response",
110
-
111
110
  # Configuration
112
111
  "TransportConfig",
113
112
  "HTTPConfig",
114
-
115
113
  # Types
116
114
  "TransportType",
117
115
  "HTTPMethod",
118
-
119
116
  # Errors
120
117
  "TransportError",
121
118
  "TransportConnectionError",
122
- "TransportTimeoutError",
119
+ "TransportTimeoutError",
123
120
  "HTTPResponseError",
124
121
  "TransportNotFoundError",
125
-
126
122
  # Transport implementations
127
123
  "HTTPTransport",
128
-
129
124
  # Middleware
130
125
  "Middleware",
131
126
  "MiddlewarePipeline",
@@ -133,13 +128,11 @@ __all__ = [
133
128
  "RetryMiddleware",
134
129
  "MetricsMiddleware",
135
130
  "create_default_pipeline",
136
-
137
131
  # Registry
138
132
  "register_transport",
139
133
  "get_transport",
140
134
  "get_transport_info",
141
135
  "list_registered_transports",
142
-
143
136
  # Client API
144
137
  "UniversalClient",
145
138
  "get_default_client",
@@ -147,9 +140,9 @@ __all__ = [
147
140
  "get",
148
141
  "post",
149
142
  "put",
150
- "patch",
143
+ "patch",
151
144
  "delete",
152
145
  "head",
153
146
  "options",
154
147
  "stream",
155
- ]
148
+ ]
@@ -2,7 +2,6 @@
2
2
  Core transport abstractions.
3
3
  """
4
4
 
5
- import time
6
5
  from abc import ABC, abstractmethod
7
6
  from collections.abc import AsyncIterator
8
7
  from typing import Any, Protocol, runtime_checkable
@@ -18,7 +17,7 @@ log = get_logger(__name__)
18
17
  @define
19
18
  class Request:
20
19
  """Protocol-agnostic request."""
21
-
20
+
22
21
  uri: str
23
22
  method: str = "GET"
24
23
  headers: Headers = field(factory=dict)
@@ -26,7 +25,7 @@ class Request:
26
25
  body: Data = None
27
26
  timeout: float | None = None
28
27
  metadata: dict[str, Any] = field(factory=dict)
29
-
28
+
30
29
  @property
31
30
  def transport_type(self) -> TransportType:
32
31
  """Infer transport type from URI scheme."""
@@ -36,7 +35,7 @@ class Request:
36
35
  except ValueError:
37
36
  log.trace(f"Unknown scheme '{scheme}', defaulting to HTTP")
38
37
  return TransportType.HTTP
39
-
38
+
40
39
  @property
41
40
  def base_url(self) -> str:
42
41
  """Extract base URL from URI."""
@@ -49,43 +48,44 @@ class Request:
49
48
  @define
50
49
  class Response:
51
50
  """Protocol-agnostic response."""
52
-
51
+
53
52
  status: int
54
53
  headers: Headers = field(factory=dict)
55
54
  body: bytes | str | None = None
56
55
  metadata: dict[str, Any] = field(factory=dict)
57
56
  elapsed_ms: float = 0
58
57
  request: Request | None = None
59
-
58
+
60
59
  def is_success(self) -> bool:
61
60
  """Check if response indicates success."""
62
61
  return 200 <= self.status < 300
63
-
62
+
64
63
  def json(self) -> Any:
65
64
  """Parse response body as JSON."""
66
65
  import json
67
-
66
+
68
67
  if isinstance(self.body, bytes):
69
- return json.loads(self.body.decode('utf-8'))
68
+ return json.loads(self.body.decode("utf-8"))
70
69
  elif isinstance(self.body, str):
71
70
  return json.loads(self.body)
72
71
  else:
73
72
  raise ValueError("Response body is not JSON-parseable")
74
-
73
+
75
74
  @property
76
75
  def text(self) -> str:
77
76
  """Get response body as text."""
78
77
  if isinstance(self.body, bytes):
79
- return self.body.decode('utf-8')
78
+ return self.body.decode("utf-8")
80
79
  elif isinstance(self.body, str):
81
80
  return self.body
82
81
  else:
83
82
  return str(self.body or "")
84
-
83
+
85
84
  def raise_for_status(self) -> None:
86
85
  """Raise error if response status indicates failure."""
87
86
  if not self.is_success():
88
87
  from provide.foundation.transport.errors import HTTPResponseError
88
+
89
89
  raise HTTPResponseError(
90
90
  f"Request failed with status {self.status}",
91
91
  status_code=self.status,
@@ -96,32 +96,32 @@ class Response:
96
96
  @runtime_checkable
97
97
  class Transport(Protocol):
98
98
  """Abstract transport protocol."""
99
-
99
+
100
100
  async def execute(self, request: Request) -> Response:
101
101
  """Execute a request and return response."""
102
102
  ...
103
-
103
+
104
104
  async def stream(self, request: Request) -> AsyncIterator[bytes]:
105
105
  """Stream response data."""
106
106
  ...
107
-
107
+
108
108
  async def connect(self) -> None:
109
109
  """Establish connection if needed."""
110
110
  ...
111
-
111
+
112
112
  async def disconnect(self) -> None:
113
113
  """Close connection if needed."""
114
114
  ...
115
-
115
+
116
116
  def supports(self, transport_type: TransportType) -> bool:
117
117
  """Check if this transport handles the given type."""
118
118
  ...
119
-
119
+
120
120
  async def __aenter__(self) -> "Transport":
121
121
  """Context manager entry."""
122
122
  await self.connect()
123
123
  return self
124
-
124
+
125
125
  async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
126
126
  """Context manager exit."""
127
127
  await self.disconnect()
@@ -129,43 +129,45 @@ class Transport(Protocol):
129
129
 
130
130
  class TransportBase(ABC):
131
131
  """Base class for transport implementations."""
132
-
132
+
133
133
  def __init__(self):
134
134
  self._logger = get_logger(self.__class__.__name__)
135
-
135
+
136
136
  @abstractmethod
137
137
  async def execute(self, request: Request) -> Response:
138
138
  """Execute a request and return response."""
139
139
  pass
140
-
140
+
141
141
  @abstractmethod
142
142
  def supports(self, transport_type: TransportType) -> bool:
143
143
  """Check if this transport handles the given type."""
144
144
  pass
145
-
145
+
146
146
  async def connect(self) -> None:
147
147
  """Default connect implementation."""
148
148
  self._logger.trace("Transport connecting")
149
-
149
+
150
150
  async def disconnect(self) -> None:
151
151
  """Default disconnect implementation."""
152
152
  self._logger.trace("Transport disconnecting")
153
-
153
+
154
154
  async def stream(self, request: Request) -> AsyncIterator[bytes]:
155
155
  """Default streaming implementation (not supported)."""
156
- raise NotImplementedError(f"{self.__class__.__name__} does not support streaming")
157
-
156
+ raise NotImplementedError(
157
+ f"{self.__class__.__name__} does not support streaming"
158
+ )
159
+
158
160
  async def __aenter__(self) -> "TransportBase":
159
161
  await self.connect()
160
162
  return self
161
-
163
+
162
164
  async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
163
165
  await self.disconnect()
164
166
 
165
167
 
166
168
  __all__ = [
167
169
  "Request",
168
- "Response",
170
+ "Response",
169
171
  "Transport",
170
172
  "TransportBase",
171
- ]
173
+ ]