dory-processor-sdk 0.0.1__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 (86) hide show
  1. dory/__init__.py +101 -0
  2. dory/auth/__init__.py +10 -0
  3. dory/auth/oauth2.py +153 -0
  4. dory/auto_instrument.py +142 -0
  5. dory/cli/__init__.py +5 -0
  6. dory/cli/main.py +137 -0
  7. dory/cli/templates.py +123 -0
  8. dory/config/__init__.py +23 -0
  9. dory/config/defaults.py +24 -0
  10. dory/config/loader.py +430 -0
  11. dory/config/presets.py +73 -0
  12. dory/config/schema.py +84 -0
  13. dory/core/__init__.py +27 -0
  14. dory/core/app.py +434 -0
  15. dory/core/context.py +209 -0
  16. dory/core/lifecycle.py +214 -0
  17. dory/core/meta.py +121 -0
  18. dory/core/modes.py +479 -0
  19. dory/core/processor.py +564 -0
  20. dory/core/signals.py +122 -0
  21. dory/decorators.py +142 -0
  22. dory/edge/__init__.py +88 -0
  23. dory/edge/adaptive.py +644 -0
  24. dory/edge/detector.py +546 -0
  25. dory/edge/fencing.py +488 -0
  26. dory/edge/heartbeat.py +598 -0
  27. dory/edge/role.py +419 -0
  28. dory/errors/__init__.py +139 -0
  29. dory/errors/classification.py +362 -0
  30. dory/errors/codes.py +498 -0
  31. dory/geo/__init__.py +40 -0
  32. dory/geo/geolocalizer.py +1034 -0
  33. dory/health/__init__.py +12 -0
  34. dory/health/probes.py +210 -0
  35. dory/health/server.py +635 -0
  36. dory/k8s/__init__.py +80 -0
  37. dory/k8s/annotation_watcher.py +184 -0
  38. dory/k8s/client.py +251 -0
  39. dory/k8s/labels.py +505 -0
  40. dory/k8s/pod_metadata.py +182 -0
  41. dory/logging/__init__.py +9 -0
  42. dory/logging/logger.py +148 -0
  43. dory/metrics/__init__.py +7 -0
  44. dory/metrics/collector.py +301 -0
  45. dory/middleware/__init__.py +46 -0
  46. dory/middleware/connection_tracker.py +608 -0
  47. dory/middleware/request_id.py +325 -0
  48. dory/middleware/request_tracker.py +511 -0
  49. dory/migration/__init__.py +33 -0
  50. dory/migration/configmap.py +232 -0
  51. dory/migration/s3_store.py +594 -0
  52. dory/migration/serialization.py +135 -0
  53. dory/migration/state_manager.py +286 -0
  54. dory/migration/transfer.py +382 -0
  55. dory/monitoring/__init__.py +29 -0
  56. dory/monitoring/opentelemetry.py +489 -0
  57. dory/output/__init__.py +31 -0
  58. dory/output/envelope.py +137 -0
  59. dory/output/formatter.py +113 -0
  60. dory/output/rabbitmq.py +632 -0
  61. dory/output/routing.py +318 -0
  62. dory/output/validator.py +199 -0
  63. dory/py.typed +2 -0
  64. dory/recovery/__init__.py +60 -0
  65. dory/recovery/golden_image.py +487 -0
  66. dory/recovery/golden_snapshot.py +713 -0
  67. dory/recovery/golden_validator.py +518 -0
  68. dory/recovery/partial_recovery.py +482 -0
  69. dory/recovery/recovery_decision.py +242 -0
  70. dory/recovery/restart_detector.py +142 -0
  71. dory/recovery/state_validator.py +183 -0
  72. dory/resilience/__init__.py +45 -0
  73. dory/resilience/circuit_breaker.py +457 -0
  74. dory/resilience/retry.py +389 -0
  75. dory/simple.py +342 -0
  76. dory/types.py +68 -0
  77. dory/utils/__init__.py +31 -0
  78. dory/utils/errors.py +59 -0
  79. dory/utils/retry.py +115 -0
  80. dory/utils/timeout.py +80 -0
  81. dory_processor_sdk-0.0.1.dist-info/METADATA +424 -0
  82. dory_processor_sdk-0.0.1.dist-info/RECORD +86 -0
  83. dory_processor_sdk-0.0.1.dist-info/WHEEL +5 -0
  84. dory_processor_sdk-0.0.1.dist-info/entry_points.txt +2 -0
  85. dory_processor_sdk-0.0.1.dist-info/licenses/LICENSE +201 -0
  86. dory_processor_sdk-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,489 @@
1
+ """
2
+ OpenTelemetry Integration
3
+
4
+ Provides full OpenTelemetry support for distributed tracing and metrics.
5
+
6
+ Features:
7
+ - Automatic span creation
8
+ - Trace propagation
9
+ - Metrics collection
10
+ - Context management
11
+ - Integration with request IDs
12
+
13
+ Note: Requires opentelemetry packages:
14
+ pip install opentelemetry-api opentelemetry-sdk
15
+ pip install opentelemetry-exporter-otlp
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import logging
21
+ from contextlib import contextmanager
22
+ from functools import wraps
23
+ from typing import Any, Dict, Optional, Callable
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+ # Try to import OpenTelemetry (optional dependency)
28
+ try:
29
+ from opentelemetry import trace
30
+ from opentelemetry.sdk.trace import TracerProvider
31
+ from opentelemetry.sdk.trace.export import (
32
+ BatchSpanProcessor,
33
+ ConsoleSpanExporter,
34
+ )
35
+ from opentelemetry.sdk.resources import Resource, SERVICE_NAME
36
+ from opentelemetry.trace import Status, StatusCode, SpanKind
37
+ from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
38
+
39
+ OTEL_AVAILABLE = True
40
+ except ImportError:
41
+ OTEL_AVAILABLE = False
42
+ # Create stub types for when OpenTelemetry is not available
43
+ # This allows the module to be imported without OpenTelemetry installed
44
+ from enum import Enum
45
+
46
+ class SpanKind(Enum):
47
+ """Stub SpanKind enum when OpenTelemetry is not installed."""
48
+ INTERNAL = 0
49
+ SERVER = 1
50
+ CLIENT = 2
51
+ PRODUCER = 3
52
+ CONSUMER = 4
53
+
54
+ trace = None
55
+ TracerProvider = None
56
+
57
+ logger.warning(
58
+ "OpenTelemetry not available. Install with: "
59
+ "pip install opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp"
60
+ )
61
+
62
+
63
+ class OpenTelemetryManager:
64
+ """
65
+ Manages OpenTelemetry tracing and metrics.
66
+
67
+ Features:
68
+ - Automatic tracer setup
69
+ - Span creation and management
70
+ - Context propagation
71
+ - Metrics collection
72
+ - Export to OTLP endpoints
73
+
74
+ Usage:
75
+ # Initialize
76
+ otel = OpenTelemetryManager(service_name="my-service")
77
+ otel.initialize()
78
+
79
+ # Use decorator
80
+ @otel.trace("process_item")
81
+ async def process_item(item):
82
+ # Automatically traced
83
+ pass
84
+
85
+ # Or context manager
86
+ with otel.create_span("operation"):
87
+ # Operations here are traced
88
+ pass
89
+ """
90
+
91
+ def __init__(
92
+ self,
93
+ service_name: str = "dory-processor",
94
+ service_version: str = "1.0.0",
95
+ environment: str = "production",
96
+ otlp_endpoint: Optional[str] = None,
97
+ console_export: bool = False,
98
+ enable_metrics: bool = True,
99
+ ):
100
+ """
101
+ Initialize OpenTelemetry manager.
102
+
103
+ Args:
104
+ service_name: Name of the service
105
+ service_version: Version of the service
106
+ environment: Environment (production, staging, dev)
107
+ otlp_endpoint: Optional OTLP endpoint URL
108
+ console_export: Export spans to console (for debugging)
109
+ enable_metrics: Enable metrics collection
110
+ """
111
+ if not OTEL_AVAILABLE:
112
+ raise ImportError(
113
+ "OpenTelemetry not available. Install with: "
114
+ "pip install opentelemetry-api opentelemetry-sdk"
115
+ )
116
+
117
+ self.service_name = service_name
118
+ self.service_version = service_version
119
+ self.environment = environment
120
+ self.otlp_endpoint = otlp_endpoint
121
+ self.console_export = console_export
122
+ self.enable_metrics = enable_metrics
123
+
124
+ self._tracer_provider: Optional[TracerProvider] = None
125
+ self._tracer: Optional[trace.Tracer] = None
126
+ self._initialized = False
127
+
128
+ logger.info(
129
+ f"OpenTelemetryManager created: service={service_name}, "
130
+ f"version={service_version}, env={environment}"
131
+ )
132
+
133
+ def initialize(self) -> None:
134
+ """
135
+ Initialize OpenTelemetry.
136
+
137
+ Sets up tracer provider, exporters, and processors.
138
+ """
139
+ if self._initialized:
140
+ logger.warning("OpenTelemetry already initialized")
141
+ return
142
+
143
+ # Create resource
144
+ resource = Resource.create({
145
+ SERVICE_NAME: self.service_name,
146
+ "service.version": self.service_version,
147
+ "deployment.environment": self.environment,
148
+ })
149
+
150
+ # Create tracer provider
151
+ self._tracer_provider = TracerProvider(resource=resource)
152
+
153
+ # Add console exporter if enabled
154
+ if self.console_export:
155
+ console_exporter = ConsoleSpanExporter()
156
+ console_processor = BatchSpanProcessor(console_exporter)
157
+ self._tracer_provider.add_span_processor(console_processor)
158
+ logger.info("Console span exporter added")
159
+
160
+ # Add OTLP exporter if endpoint provided
161
+ if self.otlp_endpoint:
162
+ try:
163
+ from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
164
+
165
+ otlp_exporter = OTLPSpanExporter(endpoint=self.otlp_endpoint)
166
+ otlp_processor = BatchSpanProcessor(otlp_exporter)
167
+ self._tracer_provider.add_span_processor(otlp_processor)
168
+ logger.info(f"OTLP span exporter added: {self.otlp_endpoint}")
169
+ except ImportError:
170
+ logger.warning(
171
+ "OTLP exporter not available. Install with: "
172
+ "pip install opentelemetry-exporter-otlp"
173
+ )
174
+
175
+ # Set global tracer provider
176
+ trace.set_tracer_provider(self._tracer_provider)
177
+
178
+ # Get tracer
179
+ self._tracer = trace.get_tracer(
180
+ instrumenting_module_name=__name__,
181
+ instrumenting_library_version=self.service_version,
182
+ )
183
+
184
+ self._initialized = True
185
+ logger.info("OpenTelemetry initialized successfully")
186
+
187
+ def get_tracer(self) -> trace.Tracer:
188
+ """
189
+ Get the tracer instance.
190
+
191
+ Returns:
192
+ OpenTelemetry tracer
193
+ """
194
+ if not self._initialized:
195
+ self.initialize()
196
+ return self._tracer
197
+
198
+ @contextmanager
199
+ def create_span(
200
+ self,
201
+ name: str,
202
+ attributes: Optional[Dict[str, Any]] = None,
203
+ kind: SpanKind = SpanKind.INTERNAL,
204
+ ):
205
+ """
206
+ Create a span context manager.
207
+
208
+ Args:
209
+ name: Span name
210
+ attributes: Optional span attributes
211
+ kind: Span kind (INTERNAL, SERVER, CLIENT, etc.)
212
+
213
+ Example:
214
+ with otel.create_span("database_query", {"query": "SELECT ...")):
215
+ result = await db.execute(query)
216
+ """
217
+ if not self._initialized:
218
+ # If not initialized, just yield without tracing
219
+ yield None
220
+ return
221
+
222
+ tracer = self.get_tracer()
223
+
224
+ with tracer.start_as_current_span(name, kind=kind) as span:
225
+ # Add attributes
226
+ if attributes:
227
+ for key, value in attributes.items():
228
+ span.set_attribute(key, value)
229
+
230
+ try:
231
+ yield span
232
+ except Exception as e:
233
+ # Record exception
234
+ span.record_exception(e)
235
+ span.set_status(Status(StatusCode.ERROR, str(e)))
236
+ raise
237
+ else:
238
+ span.set_status(Status(StatusCode.OK))
239
+
240
+ def trace(
241
+ self,
242
+ name: Optional[str] = None,
243
+ attributes: Optional[Dict[str, Any]] = None,
244
+ kind: SpanKind = SpanKind.INTERNAL,
245
+ ):
246
+ """
247
+ Decorator to automatically trace a function.
248
+
249
+ Args:
250
+ name: Span name (uses function name if None)
251
+ attributes: Optional span attributes
252
+ kind: Span kind
253
+
254
+ Example:
255
+ @otel.trace("process_item")
256
+ async def process_item(item):
257
+ # Automatically traced
258
+ pass
259
+ """
260
+ def decorator(func):
261
+ span_name = name or func.__name__
262
+
263
+ @wraps(func)
264
+ async def async_wrapper(*args, **kwargs):
265
+ with self.create_span(span_name, attributes, kind):
266
+ return await func(*args, **kwargs)
267
+
268
+ @wraps(func)
269
+ def sync_wrapper(*args, **kwargs):
270
+ with self.create_span(span_name, attributes, kind):
271
+ return func(*args, **kwargs)
272
+
273
+ # Return appropriate wrapper
274
+ import asyncio
275
+ if asyncio.iscoroutinefunction(func):
276
+ return async_wrapper
277
+ else:
278
+ return sync_wrapper
279
+
280
+ return decorator
281
+
282
+ def add_span_attributes(self, attributes: Dict[str, Any]) -> None:
283
+ """
284
+ Add attributes to current span.
285
+
286
+ Args:
287
+ attributes: Attributes to add
288
+ """
289
+ if not self._initialized:
290
+ return
291
+
292
+ span = trace.get_current_span()
293
+ if span:
294
+ for key, value in attributes.items():
295
+ span.set_attribute(key, value)
296
+
297
+ def record_exception(self, exception: Exception) -> None:
298
+ """
299
+ Record an exception in current span.
300
+
301
+ Args:
302
+ exception: Exception to record
303
+ """
304
+ if not self._initialized:
305
+ return
306
+
307
+ span = trace.get_current_span()
308
+ if span:
309
+ span.record_exception(exception)
310
+ span.set_status(Status(StatusCode.ERROR, str(exception)))
311
+
312
+ def inject_context(self, headers: Dict[str, str]) -> Dict[str, str]:
313
+ """
314
+ Inject trace context into headers for propagation.
315
+
316
+ Args:
317
+ headers: HTTP headers or similar dict
318
+
319
+ Returns:
320
+ Headers with trace context injected
321
+ """
322
+ if not self._initialized:
323
+ return headers
324
+
325
+ propagator = TraceContextTextMapPropagator()
326
+ headers = dict(headers)
327
+ propagator.inject(headers)
328
+ return headers
329
+
330
+ def extract_context(self, headers: Dict[str, str]) -> None:
331
+ """
332
+ Extract trace context from headers.
333
+
334
+ Args:
335
+ headers: HTTP headers or similar dict
336
+ """
337
+ if not self._initialized:
338
+ return
339
+
340
+ propagator = TraceContextTextMapPropagator()
341
+ context = propagator.extract(headers)
342
+ # Context is automatically set as current
343
+
344
+ def shutdown(self) -> None:
345
+ """Shutdown OpenTelemetry and flush spans."""
346
+ if self._tracer_provider:
347
+ self._tracer_provider.shutdown()
348
+ logger.info("OpenTelemetry shut down")
349
+
350
+
351
+ # Global instance
352
+ _global_otel: Optional[OpenTelemetryManager] = None
353
+
354
+
355
+ def get_global_otel() -> Optional[OpenTelemetryManager]:
356
+ """Get global OpenTelemetry manager."""
357
+ return _global_otel
358
+
359
+
360
+ def initialize_otel(
361
+ service_name: str = "dory-processor",
362
+ **kwargs
363
+ ) -> OpenTelemetryManager:
364
+ """
365
+ Initialize global OpenTelemetry manager.
366
+
367
+ Args:
368
+ service_name: Service name
369
+ **kwargs: Additional arguments for OpenTelemetryManager
370
+
371
+ Returns:
372
+ Initialized OpenTelemetryManager
373
+ """
374
+ global _global_otel
375
+
376
+ if _global_otel:
377
+ logger.warning("Global OpenTelemetry already initialized")
378
+ return _global_otel
379
+
380
+ _global_otel = OpenTelemetryManager(service_name=service_name, **kwargs)
381
+ _global_otel.initialize()
382
+
383
+ logger.info(f"Global OpenTelemetry initialized for service: {service_name}")
384
+ return _global_otel
385
+
386
+
387
+ # Convenience functions using global instance
388
+
389
+ def get_tracer() -> Optional[trace.Tracer]:
390
+ """Get tracer from global instance."""
391
+ otel = get_global_otel()
392
+ return otel.get_tracer() if otel else None
393
+
394
+
395
+ def create_span(
396
+ name: str,
397
+ attributes: Optional[Dict[str, Any]] = None,
398
+ kind: Optional[SpanKind] = None,
399
+ ):
400
+ """Create span using global instance."""
401
+ otel = get_global_otel()
402
+ if otel:
403
+ # Default to INTERNAL kind when OpenTelemetry is available
404
+ if kind is None and OTEL_AVAILABLE:
405
+ kind = SpanKind.INTERNAL
406
+ return otel.create_span(name, attributes, kind)
407
+ else:
408
+ # Return no-op context manager
409
+ @contextmanager
410
+ def noop():
411
+ yield None
412
+ return noop()
413
+
414
+
415
+ def trace_function(
416
+ name: Optional[str] = None,
417
+ attributes: Optional[Dict[str, Any]] = None,
418
+ kind: Optional[SpanKind] = None,
419
+ ):
420
+ """Trace function using global instance."""
421
+ otel = get_global_otel()
422
+ if otel:
423
+ # Default to INTERNAL kind when OpenTelemetry is available
424
+ if kind is None and OTEL_AVAILABLE:
425
+ kind = SpanKind.INTERNAL
426
+ return otel.trace(name, attributes, kind)
427
+ else:
428
+ # Return no-op decorator
429
+ def decorator(func):
430
+ return func
431
+ return decorator
432
+
433
+
434
+ def add_span_attributes(attributes: Dict[str, Any]) -> None:
435
+ """Add attributes to current span using global instance."""
436
+ otel = get_global_otel()
437
+ if otel:
438
+ otel.add_span_attributes(attributes)
439
+
440
+
441
+ def record_exception(exception: Exception) -> None:
442
+ """Record exception in current span using global instance."""
443
+ otel = get_global_otel()
444
+ if otel:
445
+ otel.record_exception(exception)
446
+
447
+
448
+ # Integration with request IDs
449
+
450
+ def integrate_with_request_id():
451
+ """
452
+ Integrate OpenTelemetry with request ID middleware.
453
+
454
+ Automatically adds request IDs as span attributes.
455
+ """
456
+ try:
457
+ from dory.middleware.request_id import get_current_request_id
458
+
459
+ otel = get_global_otel()
460
+ if not otel:
461
+ logger.warning("OpenTelemetry not initialized")
462
+ return
463
+
464
+ # Monkey patch create_span to add request ID
465
+ original_create_span = otel.create_span
466
+
467
+ @contextmanager
468
+ def create_span_with_request_id(name, attributes=None, kind=None):
469
+ # Get current request ID
470
+ request_id = get_current_request_id()
471
+
472
+ # Default to INTERNAL kind
473
+ if kind is None:
474
+ kind = SpanKind.INTERNAL
475
+
476
+ # Add to attributes
477
+ attrs = attributes or {}
478
+ if request_id:
479
+ attrs["request.id"] = request_id
480
+
481
+ with original_create_span(name, attrs, kind) as span:
482
+ yield span
483
+
484
+ otel.create_span = create_span_with_request_id
485
+
486
+ logger.info("OpenTelemetry integrated with request ID middleware")
487
+
488
+ except ImportError:
489
+ logger.warning("Request ID middleware not available for integration")
@@ -0,0 +1,31 @@
1
+ """Output formatting and publishing for processor results."""
2
+
3
+ from dory.output.formatter import OutputFormatter, JSONFormatter
4
+ from dory.output.routing import build_routing_key, get_geohash
5
+ from dory.output.envelope import (
6
+ MessageEnvelope,
7
+ EnvelopeFormatter,
8
+ ENVELOPE_SCHEMA_VERSION,
9
+ )
10
+ from dory.output.validator import (
11
+ EnvelopeValidator,
12
+ UnsupportedVersionError,
13
+ )
14
+
15
+ # RabbitMQ requires optional dependency
16
+ try:
17
+ from dory.output.rabbitmq import RabbitMQPublisher, PublisherConfig, PublishError
18
+ except ImportError:
19
+ pass
20
+
21
+ __all__ = [
22
+ "OutputFormatter",
23
+ "JSONFormatter",
24
+ "MessageEnvelope",
25
+ "EnvelopeFormatter",
26
+ "EnvelopeValidator",
27
+ "UnsupportedVersionError",
28
+ "ENVELOPE_SCHEMA_VERSION",
29
+ "build_routing_key",
30
+ "get_geohash",
31
+ ]
@@ -0,0 +1,137 @@
1
+ """
2
+ MessageEnvelope for unified publisher/subscriber contract.
3
+
4
+ Provides a versioned envelope that wraps payload data with metadata,
5
+ enabling schema evolution between publisher and subscriber SDKs.
6
+
7
+ The envelope contains:
8
+ - schema_version: MAJOR.MINOR version string
9
+ - message_id: Unique UUID for each message
10
+ - timestamp: ISO 8601 creation timestamp
11
+ - payload: The actual business data
12
+
13
+ Schema versioning rules:
14
+ - MINOR bump (0.1 -> 0.2): Additive change (new optional fields only)
15
+ - MAJOR bump (0.1 -> 1.0): Breaking change (fields removed, types changed)
16
+ - Subscribers should use major version matching for backward compatibility
17
+ """
18
+
19
+ import logging
20
+ from dataclasses import dataclass, field, asdict
21
+ from datetime import datetime, timezone
22
+ from typing import Any
23
+ from uuid import uuid4
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+ # Current envelope schema version
28
+ ENVELOPE_SCHEMA_VERSION = "0.1"
29
+
30
+
31
+ # =========================================================================
32
+ # MessageEnvelope
33
+ # =========================================================================
34
+
35
+ @dataclass
36
+ class MessageEnvelope:
37
+ """Versioned envelope wrapping payload data.
38
+
39
+ Attributes:
40
+ schema_version: Envelope schema version (MAJOR.MINOR).
41
+ message_id: Unique message identifier.
42
+ timestamp: ISO 8601 timestamp of message creation.
43
+ payload: The actual message data.
44
+ """
45
+
46
+ schema_version: str = ENVELOPE_SCHEMA_VERSION
47
+ message_id: str = field(default_factory=lambda: str(uuid4()))
48
+ timestamp: str = field(
49
+ default_factory=lambda: datetime.now(timezone.utc).isoformat()
50
+ )
51
+ payload: Any = field(default_factory=dict)
52
+
53
+ def to_dict(self) -> dict[str, Any]:
54
+ """Convert envelope to a plain dictionary."""
55
+ return asdict(self)
56
+
57
+ @classmethod
58
+ def from_dict(cls, data: dict[str, Any]) -> "MessageEnvelope":
59
+ """Reconstruct an envelope from a dictionary.
60
+
61
+ Args:
62
+ data: Dictionary with envelope fields.
63
+
64
+ Returns:
65
+ MessageEnvelope instance.
66
+ """
67
+ return cls(
68
+ schema_version=data.get("schema_version", ENVELOPE_SCHEMA_VERSION),
69
+ message_id=data.get("message_id", str(uuid4())),
70
+ timestamp=data.get("timestamp", ""),
71
+ payload=data.get("payload", {}),
72
+ )
73
+
74
+
75
+ # =========================================================================
76
+ # EnvelopeFormatter
77
+ # =========================================================================
78
+
79
+ class EnvelopeFormatter:
80
+ """Wraps payload data in a MessageEnvelope before formatting.
81
+
82
+ Composes with an existing OutputFormatter: the EnvelopeFormatter builds
83
+ the envelope dict, then delegates serialization to the inner formatter.
84
+
85
+ Args:
86
+ formatter: Inner OutputFormatter for serialization (defaults to JSONFormatter).
87
+ schema_version: Envelope schema version to use.
88
+ """
89
+
90
+ def __init__(
91
+ self,
92
+ formatter: Any = None,
93
+ schema_version: str = ENVELOPE_SCHEMA_VERSION,
94
+ **kwargs: Any,
95
+ ):
96
+ from dory.output.formatter import JSONFormatter, OutputFormatter
97
+
98
+ self._formatter: OutputFormatter = formatter or JSONFormatter()
99
+ self._schema_version = schema_version
100
+
101
+ @property
102
+ def content_type(self) -> str:
103
+ return self._formatter.content_type
104
+
105
+ def wrap(
106
+ self,
107
+ data: Any,
108
+ **kwargs: Any,
109
+ ) -> MessageEnvelope:
110
+ """Wrap data in a MessageEnvelope.
111
+
112
+ Args:
113
+ data: Payload data.
114
+
115
+ Returns:
116
+ MessageEnvelope with payload.
117
+ """
118
+ return MessageEnvelope(
119
+ schema_version=self._schema_version,
120
+ payload=data,
121
+ )
122
+
123
+ def format(
124
+ self,
125
+ data: Any,
126
+ **kwargs: Any,
127
+ ) -> bytes:
128
+ """Wrap data in an envelope and serialize to bytes.
129
+
130
+ Args:
131
+ data: Payload data.
132
+
133
+ Returns:
134
+ Serialized envelope as bytes.
135
+ """
136
+ envelope = self.wrap(data)
137
+ return self._formatter.format(envelope.to_dict())