provide-foundation 0.0.0.dev0__py3-none-any.whl → 0.0.0.dev1__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 (92) hide show
  1. provide/foundation/__init__.py +12 -20
  2. provide/foundation/archive/__init__.py +23 -0
  3. provide/foundation/archive/base.py +70 -0
  4. provide/foundation/archive/bzip2.py +157 -0
  5. provide/foundation/archive/gzip.py +159 -0
  6. provide/foundation/archive/operations.py +336 -0
  7. provide/foundation/archive/tar.py +164 -0
  8. provide/foundation/archive/zip.py +203 -0
  9. provide/foundation/config/base.py +2 -2
  10. provide/foundation/config/sync.py +19 -4
  11. provide/foundation/core.py +1 -2
  12. provide/foundation/crypto/__init__.py +2 -0
  13. provide/foundation/crypto/certificates/__init__.py +34 -0
  14. provide/foundation/crypto/certificates/base.py +173 -0
  15. provide/foundation/crypto/certificates/certificate.py +290 -0
  16. provide/foundation/crypto/certificates/factory.py +213 -0
  17. provide/foundation/crypto/certificates/generator.py +138 -0
  18. provide/foundation/crypto/certificates/loader.py +130 -0
  19. provide/foundation/crypto/certificates/operations.py +198 -0
  20. provide/foundation/crypto/certificates/trust.py +107 -0
  21. provide/foundation/eventsets/__init__.py +0 -0
  22. provide/foundation/eventsets/display.py +84 -0
  23. provide/foundation/eventsets/registry.py +160 -0
  24. provide/foundation/eventsets/resolver.py +192 -0
  25. provide/foundation/eventsets/sets/das.py +128 -0
  26. provide/foundation/eventsets/sets/database.py +125 -0
  27. provide/foundation/eventsets/sets/http.py +153 -0
  28. provide/foundation/eventsets/sets/llm.py +139 -0
  29. provide/foundation/eventsets/sets/task_queue.py +107 -0
  30. provide/foundation/eventsets/types.py +70 -0
  31. provide/foundation/hub/components.py +7 -133
  32. provide/foundation/logger/__init__.py +3 -10
  33. provide/foundation/logger/config/logging.py +6 -6
  34. provide/foundation/logger/core.py +0 -2
  35. provide/foundation/logger/custom_processors.py +1 -0
  36. provide/foundation/logger/factories.py +11 -2
  37. provide/foundation/logger/processors/main.py +20 -84
  38. provide/foundation/logger/setup/__init__.py +5 -1
  39. provide/foundation/logger/setup/coordinator.py +75 -23
  40. provide/foundation/logger/setup/processors.py +2 -9
  41. provide/foundation/logger/trace.py +27 -0
  42. provide/foundation/metrics/otel.py +10 -10
  43. provide/foundation/process/lifecycle.py +82 -26
  44. provide/foundation/testing/__init__.py +77 -0
  45. provide/foundation/testing/archive/__init__.py +24 -0
  46. provide/foundation/testing/archive/fixtures.py +217 -0
  47. provide/foundation/testing/common/__init__.py +34 -0
  48. provide/foundation/testing/common/fixtures.py +263 -0
  49. provide/foundation/testing/file/__init__.py +40 -0
  50. provide/foundation/testing/file/fixtures.py +523 -0
  51. provide/foundation/testing/logger.py +41 -11
  52. provide/foundation/testing/mocking/__init__.py +46 -0
  53. provide/foundation/testing/mocking/fixtures.py +331 -0
  54. provide/foundation/testing/process/__init__.py +48 -0
  55. provide/foundation/testing/process/fixtures.py +577 -0
  56. provide/foundation/testing/threading/__init__.py +38 -0
  57. provide/foundation/testing/threading/fixtures.py +520 -0
  58. provide/foundation/testing/time/__init__.py +32 -0
  59. provide/foundation/testing/time/fixtures.py +409 -0
  60. provide/foundation/testing/transport/__init__.py +30 -0
  61. provide/foundation/testing/transport/fixtures.py +280 -0
  62. provide/foundation/tools/__init__.py +58 -0
  63. provide/foundation/tools/base.py +348 -0
  64. provide/foundation/tools/cache.py +266 -0
  65. provide/foundation/tools/downloader.py +213 -0
  66. provide/foundation/tools/installer.py +254 -0
  67. provide/foundation/tools/registry.py +223 -0
  68. provide/foundation/tools/resolver.py +321 -0
  69. provide/foundation/tools/verifier.py +186 -0
  70. provide/foundation/tracer/otel.py +7 -11
  71. provide/foundation/transport/__init__.py +155 -0
  72. provide/foundation/transport/base.py +171 -0
  73. provide/foundation/transport/client.py +266 -0
  74. provide/foundation/transport/config.py +209 -0
  75. provide/foundation/transport/errors.py +79 -0
  76. provide/foundation/transport/http.py +232 -0
  77. provide/foundation/transport/middleware.py +366 -0
  78. provide/foundation/transport/registry.py +167 -0
  79. provide/foundation/transport/types.py +45 -0
  80. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/METADATA +5 -28
  81. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/RECORD +85 -34
  82. provide/foundation/cli/commands/logs/generate_old.py +0 -569
  83. provide/foundation/crypto/certificates.py +0 -896
  84. provide/foundation/logger/emoji/__init__.py +0 -44
  85. provide/foundation/logger/emoji/matrix.py +0 -209
  86. provide/foundation/logger/emoji/sets.py +0 -458
  87. provide/foundation/logger/emoji/types.py +0 -56
  88. provide/foundation/logger/setup/emoji_resolver.py +0 -64
  89. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/WHEEL +0 -0
  90. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/entry_points.txt +0 -0
  91. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/licenses/LICENSE +0 -0
  92. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,209 @@
1
+ """
2
+ Transport configuration with Foundation config integration.
3
+ """
4
+
5
+ import os
6
+
7
+ from attrs import define
8
+
9
+ from provide.foundation.config import BaseConfig, field
10
+ from provide.foundation.config.loader import RuntimeConfigLoader
11
+ from provide.foundation.config.manager import register_config
12
+ from provide.foundation.config.types import ConfigSource
13
+ from provide.foundation.logger import get_logger
14
+
15
+ log = get_logger(__name__)
16
+
17
+
18
+ @define(slots=True, repr=False)
19
+ class TransportConfig(BaseConfig):
20
+ """Base configuration for all transports."""
21
+
22
+ timeout: float = field(
23
+ default=30.0,
24
+ env_var="PROVIDE_TRANSPORT_TIMEOUT",
25
+ description="Request timeout in seconds",
26
+ )
27
+ max_retries: int = field(
28
+ default=3,
29
+ env_var="PROVIDE_TRANSPORT_MAX_RETRIES",
30
+ description="Maximum number of retry attempts",
31
+ )
32
+ retry_backoff_factor: float = field(
33
+ default=0.5,
34
+ env_var="PROVIDE_TRANSPORT_RETRY_BACKOFF_FACTOR",
35
+ description="Backoff multiplier for retries",
36
+ )
37
+ verify_ssl: bool = field(
38
+ default=True,
39
+ env_var="PROVIDE_TRANSPORT_VERIFY_SSL",
40
+ description="Whether to verify SSL certificates",
41
+ )
42
+
43
+ @classmethod
44
+ def from_env(cls, strict: bool = True) -> "TransportConfig":
45
+ """Load configuration from environment variables."""
46
+ config_dict = {}
47
+
48
+ if timeout := os.getenv("PROVIDE_TRANSPORT_TIMEOUT"):
49
+ try:
50
+ config_dict["timeout"] = float(timeout)
51
+ except ValueError:
52
+ if strict:
53
+ log.warning(
54
+ "Invalid transport timeout value, using field default",
55
+ invalid_value=timeout,
56
+ )
57
+
58
+ if max_retries := os.getenv("PROVIDE_TRANSPORT_MAX_RETRIES"):
59
+ try:
60
+ config_dict["max_retries"] = int(max_retries)
61
+ except ValueError:
62
+ if strict:
63
+ log.warning(
64
+ "Invalid max retries value, using field default",
65
+ invalid_value=max_retries,
66
+ )
67
+
68
+ if backoff := os.getenv("PROVIDE_TRANSPORT_RETRY_BACKOFF_FACTOR"):
69
+ try:
70
+ config_dict["retry_backoff_factor"] = float(backoff)
71
+ except ValueError:
72
+ if strict:
73
+ log.warning(
74
+ "Invalid backoff factor value, using field default",
75
+ invalid_value=backoff,
76
+ )
77
+
78
+ if verify_ssl := os.getenv("PROVIDE_TRANSPORT_VERIFY_SSL"):
79
+ config_dict["verify_ssl"] = verify_ssl.lower() == "true"
80
+
81
+ config = cls.from_dict(config_dict, source=ConfigSource.ENV)
82
+ log.trace("Loaded transport configuration from environment", config_dict=config_dict)
83
+ return config
84
+
85
+
86
+ @define(slots=True, repr=False)
87
+ class HTTPConfig(TransportConfig):
88
+ """HTTP-specific configuration."""
89
+
90
+ pool_connections: int = field(
91
+ default=10,
92
+ env_var="PROVIDE_HTTP_POOL_CONNECTIONS",
93
+ description="Number of connection pools to cache",
94
+ )
95
+ pool_maxsize: int = field(
96
+ default=100,
97
+ env_var="PROVIDE_HTTP_POOL_MAXSIZE",
98
+ description="Maximum number of connections per pool",
99
+ )
100
+ follow_redirects: bool = field(
101
+ default=True,
102
+ env_var="PROVIDE_HTTP_FOLLOW_REDIRECTS",
103
+ description="Whether to automatically follow redirects",
104
+ )
105
+ http2: bool = field(
106
+ default=False,
107
+ env_var="PROVIDE_HTTP_USE_HTTP2",
108
+ description="Enable HTTP/2 support",
109
+ )
110
+ max_redirects: int = field(
111
+ default=5,
112
+ env_var="PROVIDE_HTTP_MAX_REDIRECTS",
113
+ description="Maximum number of redirects to follow",
114
+ )
115
+
116
+ @classmethod
117
+ def from_env(cls, strict: bool = True) -> "HTTPConfig":
118
+ """Load HTTP configuration from environment variables."""
119
+ # Start with base transport config
120
+ base_config = TransportConfig.from_env(strict=strict)
121
+ config_dict = base_config.to_dict(include_sensitive=True)
122
+
123
+ # Add HTTP-specific settings
124
+ if pool_connections := os.getenv("PROVIDE_HTTP_POOL_CONNECTIONS"):
125
+ try:
126
+ config_dict["pool_connections"] = int(pool_connections)
127
+ except ValueError:
128
+ if strict:
129
+ log.warning(
130
+ "Invalid pool connections value, using field default",
131
+ invalid_value=pool_connections,
132
+ )
133
+
134
+ if pool_maxsize := os.getenv("PROVIDE_HTTP_POOL_MAXSIZE"):
135
+ try:
136
+ config_dict["pool_maxsize"] = int(pool_maxsize)
137
+ except ValueError:
138
+ if strict:
139
+ log.warning(
140
+ "Invalid pool maxsize value, using field default",
141
+ invalid_value=pool_maxsize,
142
+ )
143
+
144
+ if follow_redirects := os.getenv("PROVIDE_HTTP_FOLLOW_REDIRECTS"):
145
+ config_dict["follow_redirects"] = follow_redirects.lower() == "true"
146
+
147
+ if http2 := os.getenv("PROVIDE_HTTP_USE_HTTP2"):
148
+ config_dict["http2"] = http2.lower() == "true"
149
+
150
+ if max_redirects := os.getenv("PROVIDE_HTTP_MAX_REDIRECTS"):
151
+ try:
152
+ config_dict["max_redirects"] = int(max_redirects)
153
+ except ValueError:
154
+ if strict:
155
+ log.warning(
156
+ "Invalid max redirects value, using field default",
157
+ invalid_value=max_redirects,
158
+ )
159
+
160
+ config = cls.from_dict(config_dict, source=ConfigSource.ENV)
161
+ log.trace("Loaded HTTP configuration from environment", config_dict=config_dict)
162
+ return config
163
+
164
+
165
+ async def register_transport_configs() -> None:
166
+ """Register transport configurations with the global ConfigManager."""
167
+ try:
168
+ # Register TransportConfig
169
+ await register_config(
170
+ name="transport",
171
+ config=None, # Will be loaded on demand
172
+ loader=RuntimeConfigLoader(prefix="PROVIDE_TRANSPORT"),
173
+ defaults={
174
+ "timeout": 30.0,
175
+ "max_retries": 3,
176
+ "retry_backoff_factor": 0.5,
177
+ "verify_ssl": True,
178
+ }
179
+ )
180
+
181
+ # Register HTTPConfig
182
+ await register_config(
183
+ name="transport.http",
184
+ config=None, # Will be loaded on demand
185
+ loader=RuntimeConfigLoader(prefix="PROVIDE_HTTP"),
186
+ defaults={
187
+ "timeout": 30.0,
188
+ "max_retries": 3,
189
+ "retry_backoff_factor": 0.5,
190
+ "verify_ssl": True,
191
+ "pool_connections": 10,
192
+ "pool_maxsize": 100,
193
+ "follow_redirects": True,
194
+ "http2": False,
195
+ "max_redirects": 5,
196
+ }
197
+ )
198
+
199
+ log.trace("Successfully registered transport configurations with ConfigManager")
200
+
201
+ except Exception as e:
202
+ log.warning("Failed to register transport configurations", error=str(e))
203
+
204
+
205
+ __all__ = [
206
+ "TransportConfig",
207
+ "HTTPConfig",
208
+ "register_transport_configs",
209
+ ]
@@ -0,0 +1,79 @@
1
+ """
2
+ Transport-specific error types.
3
+ """
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from provide.foundation.errors.base import FoundationError
8
+
9
+ if TYPE_CHECKING:
10
+ from provide.foundation.transport.base import Request, Response
11
+
12
+
13
+ class TransportError(FoundationError):
14
+ """Base transport error."""
15
+
16
+ def __init__(
17
+ self,
18
+ message: str,
19
+ *,
20
+ request: "Request | None" = None,
21
+ **kwargs
22
+ ):
23
+ super().__init__(message, **kwargs)
24
+ self.request = request
25
+
26
+
27
+ class TransportConnectionError(TransportError):
28
+ """Transport connection failed."""
29
+ pass
30
+
31
+
32
+ class TransportTimeoutError(TransportError):
33
+ """Transport request timed out."""
34
+ pass
35
+
36
+
37
+ class HTTPResponseError(TransportError):
38
+ """HTTP response error (4xx/5xx status codes)."""
39
+
40
+ def __init__(
41
+ self,
42
+ message: str,
43
+ *,
44
+ status_code: int,
45
+ response: "Response",
46
+ **kwargs
47
+ ):
48
+ super().__init__(message, **kwargs)
49
+ self.status_code = status_code
50
+ self.response = response
51
+
52
+
53
+ class TransportConfigurationError(TransportError):
54
+ """Transport configuration error."""
55
+ pass
56
+
57
+
58
+ class TransportNotFoundError(TransportError):
59
+ """No transport found for the given URI scheme."""
60
+
61
+ def __init__(
62
+ self,
63
+ message: str,
64
+ *,
65
+ scheme: str,
66
+ **kwargs
67
+ ):
68
+ super().__init__(message, **kwargs)
69
+ self.scheme = scheme
70
+
71
+
72
+ __all__ = [
73
+ "TransportError",
74
+ "TransportConnectionError",
75
+ "TransportTimeoutError",
76
+ "HTTPResponseError",
77
+ "TransportConfigurationError",
78
+ "TransportNotFoundError",
79
+ ]
@@ -0,0 +1,232 @@
1
+ """
2
+ HTTP/HTTPS transport implementation using httpx.
3
+ """
4
+
5
+ import time
6
+ from collections.abc import AsyncIterator
7
+ from typing import Any
8
+
9
+ import httpx
10
+ from attrs import define, field
11
+
12
+ from provide.foundation.logger import get_logger
13
+ from provide.foundation.transport.base import Request, Response, TransportBase
14
+ from provide.foundation.transport.config import HTTPConfig
15
+ from provide.foundation.transport.errors import (
16
+ HTTPResponseError,
17
+ TransportConnectionError,
18
+ TransportTimeoutError,
19
+ )
20
+ from provide.foundation.transport.types import TransportType
21
+
22
+ log = get_logger(__name__)
23
+
24
+
25
+ @define
26
+ class HTTPTransport(TransportBase):
27
+ """HTTP/HTTPS transport using httpx backend."""
28
+
29
+ SCHEMES = ["http", "https"]
30
+
31
+ config: HTTPConfig = field(factory=HTTPConfig.from_env)
32
+ _client: httpx.AsyncClient | None = field(default=None, init=False)
33
+
34
+ def supports(self, transport_type: TransportType) -> bool:
35
+ """Check if this transport supports the given type."""
36
+ return transport_type.value in self.SCHEMES
37
+
38
+ async def connect(self) -> None:
39
+ """Initialize httpx client with configuration."""
40
+ if self._client is not None:
41
+ return
42
+
43
+ limits = httpx.Limits(
44
+ max_connections=self.config.pool_connections,
45
+ max_keepalive_connections=self.config.pool_maxsize,
46
+ )
47
+
48
+ timeout = httpx.Timeout(self.config.timeout)
49
+
50
+ self._client = httpx.AsyncClient(
51
+ limits=limits,
52
+ timeout=timeout,
53
+ verify=self.config.verify_ssl,
54
+ follow_redirects=self.config.follow_redirects,
55
+ max_redirects=self.config.max_redirects,
56
+ http2=self.config.http2,
57
+ )
58
+
59
+ log.trace("HTTP transport connected",
60
+ pool_connections=self.config.pool_connections,
61
+ http2=self.config.http2)
62
+
63
+ async def disconnect(self) -> None:
64
+ """Close httpx client."""
65
+ if self._client is not None:
66
+ await self._client.aclose()
67
+ self._client = None
68
+ log.trace("HTTP transport disconnected")
69
+
70
+ async def execute(self, request: Request) -> Response:
71
+ """Execute HTTP request."""
72
+ await self.connect()
73
+
74
+ if self._client is None:
75
+ raise TransportConnectionError("HTTP client not connected")
76
+
77
+ # Log request with emoji
78
+ log.info(f"🚀 {request.method} {request.uri}")
79
+
80
+ start_time = time.perf_counter()
81
+
82
+ try:
83
+ # Determine request body format
84
+ json_data = None
85
+ data = None
86
+
87
+ if request.body is not None:
88
+ if isinstance(request.body, dict):
89
+ json_data = request.body
90
+ elif isinstance(request.body, (str, bytes)):
91
+ data = request.body
92
+ else:
93
+ # Try to serialize as JSON
94
+ import json
95
+ json_data = request.body
96
+
97
+ # Make the request
98
+ httpx_response = await self._client.request(
99
+ method=request.method,
100
+ url=request.uri,
101
+ headers=request.headers,
102
+ params=request.params,
103
+ json=json_data,
104
+ data=data,
105
+ timeout=request.timeout or self.config.timeout,
106
+ )
107
+
108
+ elapsed_ms = (time.perf_counter() - start_time) * 1000
109
+
110
+ # Log response with status emoji
111
+ status_emoji = self._get_status_emoji(httpx_response.status_code)
112
+ log.info(f"{status_emoji} {httpx_response.status_code} ({elapsed_ms:.0f}ms)")
113
+
114
+ # Create response object
115
+ response = Response(
116
+ status=httpx_response.status_code,
117
+ headers=dict(httpx_response.headers),
118
+ body=httpx_response.content,
119
+ metadata={
120
+ "http_version": str(httpx_response.http_version),
121
+ "reason_phrase": httpx_response.reason_phrase,
122
+ "encoding": httpx_response.encoding,
123
+ "is_redirect": httpx_response.is_redirect,
124
+ "url": str(httpx_response.url),
125
+ },
126
+ elapsed_ms=elapsed_ms,
127
+ request=request,
128
+ )
129
+
130
+ return response
131
+
132
+ except httpx.ConnectError as e:
133
+ log.error(f"❌ Connection failed: {e}")
134
+ raise TransportConnectionError(f"Failed to connect: {e}", request=request) from e
135
+
136
+ except httpx.TimeoutException as e:
137
+ elapsed_ms = (time.perf_counter() - start_time) * 1000
138
+ log.error(f"⏱️ Request timed out ({elapsed_ms:.0f}ms)")
139
+ raise TransportTimeoutError(f"Request timed out: {e}", request=request) from e
140
+
141
+ except httpx.RequestError as e:
142
+ log.error(f"❌ Request failed: {e}")
143
+ raise TransportConnectionError(f"Request failed: {e}", request=request) from e
144
+
145
+ except Exception as e:
146
+ log.error(f"❌ Unexpected error: {e}", exc_info=True)
147
+ raise TransportConnectionError(f"Unexpected error: {e}", request=request) from e
148
+
149
+ async def stream(self, request: Request) -> AsyncIterator[bytes]:
150
+ """Stream HTTP response."""
151
+ await self.connect()
152
+
153
+ if self._client is None:
154
+ raise TransportConnectionError("HTTP client not connected")
155
+
156
+ log.info(f"🌊 Streaming {request.method} {request.uri}")
157
+
158
+ try:
159
+ async with self._client.stream(
160
+ method=request.method,
161
+ url=request.uri,
162
+ headers=request.headers,
163
+ params=request.params,
164
+ timeout=request.timeout or self.config.timeout,
165
+ ) as response:
166
+
167
+ # Log response start
168
+ status_emoji = self._get_status_emoji(response.status_code)
169
+ log.info(f"{status_emoji} {response.status_code} (streaming)")
170
+
171
+ # Stream the response
172
+ async for chunk in response.aiter_bytes():
173
+ yield chunk
174
+
175
+ except httpx.ConnectError as e:
176
+ raise TransportConnectionError(f"Failed to connect: {e}", request=request) from e
177
+
178
+ except httpx.TimeoutException as e:
179
+ raise TransportTimeoutError(f"Stream timed out: {e}", request=request) from e
180
+
181
+ except httpx.RequestError as e:
182
+ raise TransportConnectionError(f"Stream failed: {e}", request=request) from e
183
+
184
+ def _get_status_emoji(self, status_code: int) -> str:
185
+ """Get emoji for HTTP status code."""
186
+ if 200 <= status_code < 300:
187
+ return "✅" # Success
188
+ elif 300 <= status_code < 400:
189
+ return "↩️" # Redirect
190
+ elif 400 <= status_code < 500:
191
+ return "⚠️" # Client error
192
+ elif 500 <= status_code < 600:
193
+ return "❌" # Server error
194
+ else:
195
+ return "❓" # Unknown
196
+
197
+
198
+ # Auto-register HTTP transport
199
+ def _register_http_transport():
200
+ """Register HTTP transport with the Hub."""
201
+ try:
202
+ from provide.foundation.transport.registry import register_transport
203
+
204
+ register_transport(
205
+ TransportType.HTTP,
206
+ HTTPTransport,
207
+ schemes=HTTPTransport.SCHEMES,
208
+ description="HTTP/HTTPS transport using httpx",
209
+ version="1.0.0",
210
+ )
211
+
212
+ # Also register HTTPS explicitly
213
+ register_transport(
214
+ TransportType.HTTPS,
215
+ HTTPTransport,
216
+ schemes=HTTPTransport.SCHEMES,
217
+ description="HTTP/HTTPS transport using httpx",
218
+ version="1.0.0",
219
+ )
220
+
221
+ except ImportError:
222
+ # Registry not available yet, will be registered later
223
+ pass
224
+
225
+
226
+ # Register when module is imported
227
+ _register_http_transport()
228
+
229
+
230
+ __all__ = [
231
+ "HTTPTransport",
232
+ ]