proxilion 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.
- proxilion/__init__.py +136 -0
- proxilion/audit/__init__.py +133 -0
- proxilion/audit/base_exporters.py +527 -0
- proxilion/audit/compliance/__init__.py +130 -0
- proxilion/audit/compliance/base.py +457 -0
- proxilion/audit/compliance/eu_ai_act.py +603 -0
- proxilion/audit/compliance/iso27001.py +544 -0
- proxilion/audit/compliance/soc2.py +491 -0
- proxilion/audit/events.py +493 -0
- proxilion/audit/explainability.py +1173 -0
- proxilion/audit/exporters/__init__.py +58 -0
- proxilion/audit/exporters/aws_s3.py +636 -0
- proxilion/audit/exporters/azure_storage.py +608 -0
- proxilion/audit/exporters/cloud_base.py +468 -0
- proxilion/audit/exporters/gcp_storage.py +570 -0
- proxilion/audit/exporters/multi_exporter.py +498 -0
- proxilion/audit/hash_chain.py +652 -0
- proxilion/audit/logger.py +543 -0
- proxilion/caching/__init__.py +49 -0
- proxilion/caching/tool_cache.py +633 -0
- proxilion/context/__init__.py +73 -0
- proxilion/context/context_window.py +556 -0
- proxilion/context/message_history.py +505 -0
- proxilion/context/session.py +735 -0
- proxilion/contrib/__init__.py +51 -0
- proxilion/contrib/anthropic.py +609 -0
- proxilion/contrib/google.py +1012 -0
- proxilion/contrib/langchain.py +641 -0
- proxilion/contrib/mcp.py +893 -0
- proxilion/contrib/openai.py +646 -0
- proxilion/core.py +3058 -0
- proxilion/decorators.py +966 -0
- proxilion/engines/__init__.py +287 -0
- proxilion/engines/base.py +266 -0
- proxilion/engines/casbin_engine.py +412 -0
- proxilion/engines/opa_engine.py +493 -0
- proxilion/engines/simple.py +437 -0
- proxilion/exceptions.py +887 -0
- proxilion/guards/__init__.py +54 -0
- proxilion/guards/input_guard.py +522 -0
- proxilion/guards/output_guard.py +634 -0
- proxilion/observability/__init__.py +198 -0
- proxilion/observability/cost_tracker.py +866 -0
- proxilion/observability/hooks.py +683 -0
- proxilion/observability/metrics.py +798 -0
- proxilion/observability/session_cost_tracker.py +1063 -0
- proxilion/policies/__init__.py +67 -0
- proxilion/policies/base.py +304 -0
- proxilion/policies/builtin.py +486 -0
- proxilion/policies/registry.py +376 -0
- proxilion/providers/__init__.py +201 -0
- proxilion/providers/adapter.py +468 -0
- proxilion/providers/anthropic_adapter.py +330 -0
- proxilion/providers/gemini_adapter.py +391 -0
- proxilion/providers/openai_adapter.py +294 -0
- proxilion/py.typed +0 -0
- proxilion/resilience/__init__.py +81 -0
- proxilion/resilience/degradation.py +615 -0
- proxilion/resilience/fallback.py +555 -0
- proxilion/resilience/retry.py +554 -0
- proxilion/scheduling/__init__.py +57 -0
- proxilion/scheduling/priority_queue.py +419 -0
- proxilion/scheduling/scheduler.py +459 -0
- proxilion/security/__init__.py +244 -0
- proxilion/security/agent_trust.py +968 -0
- proxilion/security/behavioral_drift.py +794 -0
- proxilion/security/cascade_protection.py +869 -0
- proxilion/security/circuit_breaker.py +428 -0
- proxilion/security/cost_limiter.py +690 -0
- proxilion/security/idor_protection.py +460 -0
- proxilion/security/intent_capsule.py +849 -0
- proxilion/security/intent_validator.py +495 -0
- proxilion/security/memory_integrity.py +767 -0
- proxilion/security/rate_limiter.py +509 -0
- proxilion/security/scope_enforcer.py +680 -0
- proxilion/security/sequence_validator.py +636 -0
- proxilion/security/trust_boundaries.py +784 -0
- proxilion/streaming/__init__.py +70 -0
- proxilion/streaming/detector.py +761 -0
- proxilion/streaming/transformer.py +674 -0
- proxilion/timeouts/__init__.py +55 -0
- proxilion/timeouts/decorators.py +477 -0
- proxilion/timeouts/manager.py +545 -0
- proxilion/tools/__init__.py +69 -0
- proxilion/tools/decorators.py +493 -0
- proxilion/tools/registry.py +732 -0
- proxilion/types.py +339 -0
- proxilion/validation/__init__.py +93 -0
- proxilion/validation/pydantic_schema.py +351 -0
- proxilion/validation/schema.py +651 -0
- proxilion-0.0.1.dist-info/METADATA +872 -0
- proxilion-0.0.1.dist-info/RECORD +94 -0
- proxilion-0.0.1.dist-info/WHEEL +4 -0
- proxilion-0.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base classes and protocols for cloud storage exporters.
|
|
3
|
+
|
|
4
|
+
Provides common interfaces and configuration for exporting audit logs
|
|
5
|
+
to cloud storage providers (AWS S3, GCP Cloud Storage, Azure Blob).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import gzip
|
|
11
|
+
import hashlib
|
|
12
|
+
import logging
|
|
13
|
+
import threading
|
|
14
|
+
import time
|
|
15
|
+
from abc import ABC, abstractmethod
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
from datetime import datetime, timezone
|
|
18
|
+
from enum import Enum
|
|
19
|
+
from typing import Any, Literal, Protocol, runtime_checkable
|
|
20
|
+
|
|
21
|
+
from proxilion.audit.events import AuditEventV2
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class CompressionType(Enum):
|
|
27
|
+
"""Compression types for exported files."""
|
|
28
|
+
NONE = "none"
|
|
29
|
+
GZIP = "gzip"
|
|
30
|
+
ZSTD = "zstd"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ExportFormat(Enum):
|
|
34
|
+
"""Export file formats."""
|
|
35
|
+
JSONL = "jsonl"
|
|
36
|
+
PARQUET = "parquet"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class CloudExporterConfig:
|
|
41
|
+
"""
|
|
42
|
+
Configuration for cloud storage exporters.
|
|
43
|
+
|
|
44
|
+
Attributes:
|
|
45
|
+
provider: Cloud provider ("aws", "gcp", "azure").
|
|
46
|
+
bucket_name: Name of the bucket/container.
|
|
47
|
+
prefix: Path prefix within the bucket (e.g., "audit-logs/proxilion/").
|
|
48
|
+
region: Cloud region (e.g., "us-west-2", "us-central1").
|
|
49
|
+
credentials_path: Path to service account/credentials file.
|
|
50
|
+
use_instance_credentials: Whether to use instance/managed identity.
|
|
51
|
+
batch_size: Number of events per export batch.
|
|
52
|
+
compression: Compression type for exported files.
|
|
53
|
+
format: Export file format.
|
|
54
|
+
endpoint_url: Custom endpoint URL (for S3-compatible storage).
|
|
55
|
+
connection_timeout: Connection timeout in seconds.
|
|
56
|
+
read_timeout: Read timeout in seconds.
|
|
57
|
+
max_retries: Maximum number of retries for failed operations.
|
|
58
|
+
retry_delay: Initial delay between retries in seconds.
|
|
59
|
+
"""
|
|
60
|
+
provider: Literal["aws", "gcp", "azure"]
|
|
61
|
+
bucket_name: str
|
|
62
|
+
prefix: str = ""
|
|
63
|
+
region: str | None = None
|
|
64
|
+
credentials_path: str | None = None
|
|
65
|
+
use_instance_credentials: bool = True
|
|
66
|
+
batch_size: int = 100
|
|
67
|
+
compression: CompressionType = CompressionType.GZIP
|
|
68
|
+
format: ExportFormat = ExportFormat.JSONL
|
|
69
|
+
endpoint_url: str | None = None
|
|
70
|
+
connection_timeout: float = 30.0
|
|
71
|
+
read_timeout: float = 60.0
|
|
72
|
+
max_retries: int = 3
|
|
73
|
+
retry_delay: float = 1.0
|
|
74
|
+
|
|
75
|
+
def __post_init__(self) -> None:
|
|
76
|
+
"""Validate and normalize configuration."""
|
|
77
|
+
# Ensure prefix ends with / if not empty
|
|
78
|
+
if self.prefix and not self.prefix.endswith("/"):
|
|
79
|
+
self.prefix = self.prefix + "/"
|
|
80
|
+
|
|
81
|
+
# Convert string enums if needed
|
|
82
|
+
if isinstance(self.compression, str):
|
|
83
|
+
self.compression = CompressionType(self.compression)
|
|
84
|
+
if isinstance(self.format, str):
|
|
85
|
+
self.format = ExportFormat(self.format)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass
|
|
89
|
+
class ExportResult:
|
|
90
|
+
"""
|
|
91
|
+
Result of an export operation.
|
|
92
|
+
|
|
93
|
+
Attributes:
|
|
94
|
+
success: Whether the export succeeded.
|
|
95
|
+
events_exported: Number of events successfully exported.
|
|
96
|
+
batch_id: Unique identifier for this export batch.
|
|
97
|
+
destination: Full path/key where data was exported.
|
|
98
|
+
error: Error message if export failed.
|
|
99
|
+
duration_ms: Export duration in milliseconds.
|
|
100
|
+
bytes_written: Number of bytes written.
|
|
101
|
+
checksum: MD5/SHA256 checksum of exported data.
|
|
102
|
+
"""
|
|
103
|
+
success: bool
|
|
104
|
+
events_exported: int = 0
|
|
105
|
+
batch_id: str = ""
|
|
106
|
+
destination: str = ""
|
|
107
|
+
error: str | None = None
|
|
108
|
+
duration_ms: float = 0.0
|
|
109
|
+
bytes_written: int = 0
|
|
110
|
+
checksum: str | None = None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@dataclass
|
|
114
|
+
class ExportBatch:
|
|
115
|
+
"""
|
|
116
|
+
A batch of events to export.
|
|
117
|
+
|
|
118
|
+
Attributes:
|
|
119
|
+
batch_id: Unique identifier for this batch.
|
|
120
|
+
events: List of audit events.
|
|
121
|
+
created_at: When the batch was created.
|
|
122
|
+
metadata: Additional metadata for the batch.
|
|
123
|
+
"""
|
|
124
|
+
batch_id: str
|
|
125
|
+
events: list[AuditEventV2]
|
|
126
|
+
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
127
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def event_count(self) -> int:
|
|
131
|
+
"""Get the number of events in the batch."""
|
|
132
|
+
return len(self.events)
|
|
133
|
+
|
|
134
|
+
def to_jsonl(self) -> str:
|
|
135
|
+
"""Convert batch to JSON Lines format."""
|
|
136
|
+
lines = []
|
|
137
|
+
for event in self.events:
|
|
138
|
+
lines.append(event.to_json(pretty=False))
|
|
139
|
+
return "\n".join(lines)
|
|
140
|
+
|
|
141
|
+
def to_bytes(self, compression: CompressionType = CompressionType.NONE) -> bytes:
|
|
142
|
+
"""
|
|
143
|
+
Convert batch to bytes with optional compression.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
compression: Compression type to apply.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Bytes representation of the batch.
|
|
150
|
+
"""
|
|
151
|
+
content = self.to_jsonl().encode("utf-8")
|
|
152
|
+
|
|
153
|
+
if compression == CompressionType.GZIP:
|
|
154
|
+
return gzip.compress(content)
|
|
155
|
+
elif compression == CompressionType.ZSTD:
|
|
156
|
+
try:
|
|
157
|
+
import zstandard as zstd
|
|
158
|
+
cctx = zstd.ZstdCompressor()
|
|
159
|
+
return cctx.compress(content)
|
|
160
|
+
except ImportError:
|
|
161
|
+
logger.warning("zstandard not installed, falling back to gzip")
|
|
162
|
+
return gzip.compress(content)
|
|
163
|
+
|
|
164
|
+
return content
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@runtime_checkable
|
|
168
|
+
class CloudExporter(Protocol):
|
|
169
|
+
"""
|
|
170
|
+
Protocol for cloud storage exporters.
|
|
171
|
+
|
|
172
|
+
All cloud exporters must implement these methods.
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
def export(self, events: list[AuditEventV2]) -> ExportResult:
|
|
176
|
+
"""
|
|
177
|
+
Export a list of events to cloud storage.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
events: List of audit events to export.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
ExportResult with success/failure information.
|
|
184
|
+
"""
|
|
185
|
+
...
|
|
186
|
+
|
|
187
|
+
def export_batch(self, batch: ExportBatch) -> ExportResult:
|
|
188
|
+
"""
|
|
189
|
+
Export a pre-formed batch to cloud storage.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
batch: The batch to export.
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
ExportResult with success/failure information.
|
|
196
|
+
"""
|
|
197
|
+
...
|
|
198
|
+
|
|
199
|
+
def configure(self, config: dict[str, Any]) -> None:
|
|
200
|
+
"""
|
|
201
|
+
Update exporter configuration.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
config: Configuration dictionary.
|
|
205
|
+
"""
|
|
206
|
+
...
|
|
207
|
+
|
|
208
|
+
def health_check(self) -> bool:
|
|
209
|
+
"""
|
|
210
|
+
Check if the exporter can connect to cloud storage.
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
True if healthy and connected.
|
|
214
|
+
"""
|
|
215
|
+
...
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class BaseCloudExporter(ABC):
|
|
219
|
+
"""
|
|
220
|
+
Abstract base class for cloud storage exporters.
|
|
221
|
+
|
|
222
|
+
Provides common functionality for all cloud exporters including:
|
|
223
|
+
- Key generation with time-based partitioning
|
|
224
|
+
- Compression handling
|
|
225
|
+
- Retry logic
|
|
226
|
+
- Thread-safe batch accumulation
|
|
227
|
+
"""
|
|
228
|
+
|
|
229
|
+
def __init__(self, config: CloudExporterConfig) -> None:
|
|
230
|
+
"""
|
|
231
|
+
Initialize the base exporter.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
config: Exporter configuration.
|
|
235
|
+
"""
|
|
236
|
+
self.config = config
|
|
237
|
+
self._lock = threading.RLock()
|
|
238
|
+
self._pending_events: list[AuditEventV2] = []
|
|
239
|
+
self._batch_counter = 0
|
|
240
|
+
|
|
241
|
+
def generate_key(
|
|
242
|
+
self,
|
|
243
|
+
timestamp: datetime | None = None,
|
|
244
|
+
batch_id: str | None = None,
|
|
245
|
+
) -> str:
|
|
246
|
+
"""
|
|
247
|
+
Generate an object key with time-based partitioning.
|
|
248
|
+
|
|
249
|
+
Format: {prefix}/{year}/{month}/{day}/{hour}/{batch_id}.{ext}
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
timestamp: Timestamp for partitioning (default: now).
|
|
253
|
+
batch_id: Unique batch identifier.
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
The generated object key.
|
|
257
|
+
"""
|
|
258
|
+
if timestamp is None:
|
|
259
|
+
timestamp = datetime.now(timezone.utc)
|
|
260
|
+
|
|
261
|
+
if batch_id is None:
|
|
262
|
+
with self._lock:
|
|
263
|
+
self._batch_counter += 1
|
|
264
|
+
batch_id = f"{timestamp.strftime('%Y%m%d%H%M%S')}_{self._batch_counter:06d}"
|
|
265
|
+
|
|
266
|
+
# Determine file extension
|
|
267
|
+
ext = self.config.format.value
|
|
268
|
+
if self.config.compression == CompressionType.GZIP:
|
|
269
|
+
ext += ".gz"
|
|
270
|
+
elif self.config.compression == CompressionType.ZSTD:
|
|
271
|
+
ext += ".zst"
|
|
272
|
+
|
|
273
|
+
# Build partitioned path
|
|
274
|
+
key = (
|
|
275
|
+
f"{self.config.prefix}"
|
|
276
|
+
f"{timestamp.year:04d}/"
|
|
277
|
+
f"{timestamp.month:02d}/"
|
|
278
|
+
f"{timestamp.day:02d}/"
|
|
279
|
+
f"{timestamp.hour:02d}/"
|
|
280
|
+
f"{batch_id}.{ext}"
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
return key
|
|
284
|
+
|
|
285
|
+
def prepare_batch(self, events: list[AuditEventV2]) -> ExportBatch:
|
|
286
|
+
"""
|
|
287
|
+
Prepare events as an export batch.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
events: Events to include in the batch.
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
Prepared ExportBatch.
|
|
294
|
+
"""
|
|
295
|
+
import uuid
|
|
296
|
+
|
|
297
|
+
batch_id = str(uuid.uuid4())
|
|
298
|
+
return ExportBatch(
|
|
299
|
+
batch_id=batch_id,
|
|
300
|
+
events=events,
|
|
301
|
+
metadata={
|
|
302
|
+
"exporter": self.__class__.__name__,
|
|
303
|
+
"provider": self.config.provider,
|
|
304
|
+
"bucket": self.config.bucket_name,
|
|
305
|
+
},
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
def compute_checksum(self, data: bytes) -> str:
|
|
309
|
+
"""
|
|
310
|
+
Compute MD5 checksum of data.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
data: Bytes to checksum.
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
Hex-encoded MD5 checksum.
|
|
317
|
+
"""
|
|
318
|
+
return hashlib.md5(data).hexdigest()
|
|
319
|
+
|
|
320
|
+
def with_retry(
|
|
321
|
+
self,
|
|
322
|
+
operation: callable,
|
|
323
|
+
*args: Any,
|
|
324
|
+
**kwargs: Any,
|
|
325
|
+
) -> Any:
|
|
326
|
+
"""
|
|
327
|
+
Execute an operation with retry logic.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
operation: Function to execute.
|
|
331
|
+
*args: Positional arguments for operation.
|
|
332
|
+
**kwargs: Keyword arguments for operation.
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
Result of the operation.
|
|
336
|
+
|
|
337
|
+
Raises:
|
|
338
|
+
Last exception if all retries fail.
|
|
339
|
+
"""
|
|
340
|
+
last_error: Exception | None = None
|
|
341
|
+
delay = self.config.retry_delay
|
|
342
|
+
|
|
343
|
+
for attempt in range(self.config.max_retries + 1):
|
|
344
|
+
try:
|
|
345
|
+
return operation(*args, **kwargs)
|
|
346
|
+
except Exception as e:
|
|
347
|
+
last_error = e
|
|
348
|
+
if attempt < self.config.max_retries:
|
|
349
|
+
logger.warning(
|
|
350
|
+
f"Export attempt {attempt + 1} failed: {e}. "
|
|
351
|
+
f"Retrying in {delay:.1f}s..."
|
|
352
|
+
)
|
|
353
|
+
time.sleep(delay)
|
|
354
|
+
delay *= 2 # Exponential backoff
|
|
355
|
+
|
|
356
|
+
raise last_error # type: ignore
|
|
357
|
+
|
|
358
|
+
def export(self, events: list[AuditEventV2]) -> ExportResult:
|
|
359
|
+
"""
|
|
360
|
+
Export a list of events to cloud storage.
|
|
361
|
+
|
|
362
|
+
Args:
|
|
363
|
+
events: List of audit events to export.
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
ExportResult with success/failure information.
|
|
367
|
+
"""
|
|
368
|
+
if not events:
|
|
369
|
+
return ExportResult(
|
|
370
|
+
success=True,
|
|
371
|
+
events_exported=0,
|
|
372
|
+
batch_id="",
|
|
373
|
+
destination="",
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
batch = self.prepare_batch(events)
|
|
377
|
+
return self.export_batch(batch)
|
|
378
|
+
|
|
379
|
+
@abstractmethod
|
|
380
|
+
def export_batch(self, batch: ExportBatch) -> ExportResult:
|
|
381
|
+
"""
|
|
382
|
+
Export a batch to cloud storage.
|
|
383
|
+
|
|
384
|
+
Must be implemented by subclasses.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
batch: The batch to export.
|
|
388
|
+
|
|
389
|
+
Returns:
|
|
390
|
+
ExportResult with success/failure information.
|
|
391
|
+
"""
|
|
392
|
+
pass
|
|
393
|
+
|
|
394
|
+
def configure(self, config: dict[str, Any]) -> None:
|
|
395
|
+
"""
|
|
396
|
+
Update exporter configuration.
|
|
397
|
+
|
|
398
|
+
Args:
|
|
399
|
+
config: Configuration dictionary.
|
|
400
|
+
"""
|
|
401
|
+
for key, value in config.items():
|
|
402
|
+
if hasattr(self.config, key):
|
|
403
|
+
setattr(self.config, key, value)
|
|
404
|
+
|
|
405
|
+
@abstractmethod
|
|
406
|
+
def health_check(self) -> bool:
|
|
407
|
+
"""
|
|
408
|
+
Check if the exporter can connect to cloud storage.
|
|
409
|
+
|
|
410
|
+
Must be implemented by subclasses.
|
|
411
|
+
|
|
412
|
+
Returns:
|
|
413
|
+
True if healthy and connected.
|
|
414
|
+
"""
|
|
415
|
+
pass
|
|
416
|
+
|
|
417
|
+
def add_pending(self, event: AuditEventV2) -> ExportResult | None:
|
|
418
|
+
"""
|
|
419
|
+
Add an event to the pending buffer.
|
|
420
|
+
|
|
421
|
+
If the buffer reaches batch_size, exports automatically.
|
|
422
|
+
|
|
423
|
+
Args:
|
|
424
|
+
event: Event to add.
|
|
425
|
+
|
|
426
|
+
Returns:
|
|
427
|
+
ExportResult if batch was exported, None otherwise.
|
|
428
|
+
"""
|
|
429
|
+
with self._lock:
|
|
430
|
+
self._pending_events.append(event)
|
|
431
|
+
|
|
432
|
+
if len(self._pending_events) >= self.config.batch_size:
|
|
433
|
+
events = self._pending_events
|
|
434
|
+
self._pending_events = []
|
|
435
|
+
return self.export(events)
|
|
436
|
+
|
|
437
|
+
return None
|
|
438
|
+
|
|
439
|
+
def flush_pending(self) -> ExportResult | None:
|
|
440
|
+
"""
|
|
441
|
+
Export any pending events.
|
|
442
|
+
|
|
443
|
+
Returns:
|
|
444
|
+
ExportResult if events were exported, None if buffer was empty.
|
|
445
|
+
"""
|
|
446
|
+
with self._lock:
|
|
447
|
+
if not self._pending_events:
|
|
448
|
+
return None
|
|
449
|
+
|
|
450
|
+
events = self._pending_events
|
|
451
|
+
self._pending_events = []
|
|
452
|
+
return self.export(events)
|
|
453
|
+
|
|
454
|
+
def get_content_type(self) -> str:
|
|
455
|
+
"""Get the content type for exported files."""
|
|
456
|
+
if self.config.format == ExportFormat.JSONL:
|
|
457
|
+
return "application/x-ndjson"
|
|
458
|
+
elif self.config.format == ExportFormat.PARQUET:
|
|
459
|
+
return "application/vnd.apache.parquet"
|
|
460
|
+
return "application/octet-stream"
|
|
461
|
+
|
|
462
|
+
def get_content_encoding(self) -> str | None:
|
|
463
|
+
"""Get the content encoding for compressed files."""
|
|
464
|
+
if self.config.compression == CompressionType.GZIP:
|
|
465
|
+
return "gzip"
|
|
466
|
+
elif self.config.compression == CompressionType.ZSTD:
|
|
467
|
+
return "zstd"
|
|
468
|
+
return None
|