blaxel 0.2.28__py3-none-any.whl → 0.2.30__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.
blaxel/__init__.py CHANGED
@@ -4,8 +4,8 @@ from .core.common.autoload import autoload
4
4
  from .core.common.env import env
5
5
  from .core.common.settings import settings
6
6
 
7
- __version__ = "0.2.28"
8
- __commit__ = "a53662270aa3205b3eb563a4e41efc378999d612"
7
+ __version__ = "0.2.30"
8
+ __commit__ = "65a88e27efd0b99c49c23a5cc339865d3bafca2e"
9
9
  __sentry_dsn__ = "https://9711de13cd02b285ca4378c01de8dc30@o4508714045276160.ingest.us.sentry.io/4510461121462272"
10
10
  __all__ = ["autoload", "settings", "env"]
11
11
 
@@ -2,6 +2,7 @@ import atexit
2
2
  import logging
3
3
  import sys
4
4
  import threading
5
+ from asyncio import CancelledError
5
6
 
6
7
  from sentry_sdk import Client, Hub
7
8
 
@@ -19,21 +20,72 @@ logger = logging.getLogger(__name__)
19
20
  _sentry_hub: Hub | None = None
20
21
  _captured_exceptions: set = set() # Track already captured exceptions to avoid duplicates
21
22
 
23
+ # Exceptions that are part of normal control flow and should not be captured
24
+ _IGNORED_EXCEPTIONS = (
25
+ StopIteration, # Iterator exhaustion
26
+ StopAsyncIteration, # Async iterator exhaustion
27
+ GeneratorExit, # Generator cleanup
28
+ KeyboardInterrupt, # User interrupt (Ctrl+C)
29
+ SystemExit, # Program exit
30
+ CancelledError, # Async task cancellation
31
+ )
32
+
33
+ # Optional dependencies that may not be installed - import errors for these are expected
34
+ _OPTIONAL_DEPENDENCIES = (
35
+ 'opentelemetry',
36
+ )
37
+
38
+
39
+ def _get_exception_key(exc_type, exc_value, frame) -> str:
40
+ """Generate a unique key for an exception based on type, message, and origin."""
41
+ # Use type name + message + original file/line where exception was raised
42
+ # This ensures the same logical exception is only captured once
43
+ exc_name = exc_type.__name__ if exc_type else "Unknown"
44
+ exc_msg = str(exc_value) if exc_value else ""
45
+ # Get the original traceback location (where exception was first raised)
46
+ tb = getattr(exc_value, '__traceback__', None)
47
+ if tb:
48
+ # Walk to the deepest frame (origin of exception)
49
+ while tb.tb_next:
50
+ tb = tb.tb_next
51
+ origin = f"{tb.tb_frame.f_code.co_filename}:{tb.tb_lineno}"
52
+ else:
53
+ origin = f"{frame.f_code.co_filename}:{frame.f_lineno}"
54
+ return f"{exc_name}:{exc_msg}:{origin}"
55
+
56
+
57
+ def _is_optional_dependency_error(exc_type, exc_value) -> bool:
58
+ """Check if the exception is an import error for an optional dependency."""
59
+ # ModuleNotFoundError is a subclass of ImportError, so checking ImportError covers both
60
+ if exc_type and issubclass(exc_type, ImportError):
61
+ msg = str(exc_value).lower()
62
+ return any(dep in msg for dep in _OPTIONAL_DEPENDENCIES)
63
+ return False
64
+
22
65
 
23
66
  def _trace_blaxel_exceptions(frame, event, arg):
24
67
  """Trace function that captures exceptions from blaxel SDK code."""
25
68
  if event == 'exception':
26
69
  exc_type, exc_value, exc_tb = arg
70
+
71
+ # Skip control flow exceptions (not actual errors)
72
+ if exc_type and issubclass(exc_type, _IGNORED_EXCEPTIONS):
73
+ return _trace_blaxel_exceptions
74
+
75
+ # Skip import errors for optional dependencies (expected when not installed)
76
+ if _is_optional_dependency_error(exc_type, exc_value):
77
+ return _trace_blaxel_exceptions
78
+
27
79
  filename = frame.f_code.co_filename
28
80
 
29
81
  # Only capture if it's from blaxel in site-packages
30
82
  if 'site-packages/blaxel' in filename:
31
- # Avoid capturing the same exception multiple times
32
- exc_id = id(exc_value)
33
- if exc_id not in _captured_exceptions:
34
- _captured_exceptions.add(exc_id)
83
+ # Avoid capturing the same exception multiple times using a content-based key
84
+ exc_key = _get_exception_key(exc_type, exc_value, frame)
85
+ if exc_key not in _captured_exceptions:
86
+ _captured_exceptions.add(exc_key)
35
87
  capture_exception(exc_value)
36
- # Clean up old exception IDs to prevent memory leak
88
+ # Clean up old exception keys to prevent memory leak
37
89
  if len(_captured_exceptions) > 1000:
38
90
  _captured_exceptions.clear()
39
91
 
@@ -1,6 +1,10 @@
1
1
  """Blaxel telemetry module."""
2
2
 
3
- from .exporters import * # noqa: F403, F401
4
- from .instrumentation import * # noqa: F403, F401
5
- from .log import * # noqa: F403, F401
3
+ try:
4
+ from .exporters import * # noqa: F403, F401
5
+ from .instrumentation import * # noqa: F403, F401
6
+ from .log import * # noqa: F403, F401
7
+ except ImportError:
8
+ pass
9
+
6
10
  from .manager import * # noqa: F403, F401
@@ -1,13 +1,28 @@
1
- from typing import Callable, Dict, Sequence
2
-
3
- from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
4
- from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
5
- from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
6
- from opentelemetry.sdk._logs import LogData
7
- from opentelemetry.sdk.metrics.export import MetricExportResult, MetricsData
1
+ from __future__ import annotations
8
2
 
3
+ from typing import Callable, Dict, Sequence
9
4
 
10
- class DynamicHeadersSpanExporter(OTLPSpanExporter):
5
+ try:
6
+ from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
7
+ from opentelemetry.exporter.otlp.proto.http.metric_exporter import (
8
+ OTLPMetricExporter,
9
+ )
10
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
11
+ from opentelemetry.sdk._logs import LogData
12
+ from opentelemetry.sdk.metrics.export import MetricExportResult, MetricsData
13
+
14
+ _OPENTELEMETRY_AVAILABLE = True
15
+ except ImportError:
16
+ _OPENTELEMETRY_AVAILABLE = False
17
+ OTLPLogExporter = object
18
+ OTLPMetricExporter = object
19
+ OTLPSpanExporter = object
20
+ LogData = None
21
+ MetricExportResult = None
22
+ MetricsData = None
23
+
24
+
25
+ class DynamicHeadersSpanExporter(OTLPSpanExporter): # type: ignore[misc]
11
26
  """Span exporter with dynamic headers."""
12
27
 
13
28
  def __init__(self, get_headers: Callable[[], Dict[str, str]]):
@@ -18,7 +33,7 @@ class DynamicHeadersSpanExporter(OTLPSpanExporter):
18
33
  self._session.headers.update(self._get_headers())
19
34
  return super().export(spans)
20
35
 
21
- class DynamicHeadersMetricExporter(OTLPMetricExporter):
36
+ class DynamicHeadersMetricExporter(OTLPMetricExporter): # type: ignore[misc]
22
37
  """Metric exporter with dynamic headers."""
23
38
 
24
39
  def __init__(self, get_headers: Callable[[], Dict[str, str]]):
@@ -27,19 +42,19 @@ class DynamicHeadersMetricExporter(OTLPMetricExporter):
27
42
 
28
43
  def export(
29
44
  self,
30
- metrics_data: MetricsData,
45
+ metrics_data: MetricsData, # type: ignore[reportUnknownReturnType]
31
46
  timeout_millis: float = 10_000,
32
47
  **kwargs,
33
- ) -> MetricExportResult:
48
+ ) -> MetricExportResult: # type: ignore[reportUnknownReturnType]
34
49
  self._session.headers.update(self._get_headers())
35
50
  return super().export(metrics_data, timeout_millis, **kwargs)
36
51
 
37
- class DynamicHeadersLogExporter(OTLPLogExporter):
52
+ class DynamicHeadersLogExporter(OTLPLogExporter): # type: ignore[misc]
38
53
  """Log exporter with dynamic headers."""
39
54
  def __init__(self, get_headers: Callable[[], Dict[str, str]]):
40
55
  self._get_headers = get_headers
41
56
  super().__init__()
42
57
 
43
- def export(self, batch: Sequence[LogData]):
58
+ def export(self, batch: Sequence[LogData]): # type: ignore[reportUnknownReturnType]
44
59
  self._session.headers.update(self._get_headers())
45
60
  return super().export(batch)
@@ -3,6 +3,8 @@ This module provides utilities for setting up and managing OpenTelemetry instrum
3
3
  It includes classes and functions for configuring tracers, meters, loggers, and integrating with FastAPI applications.
4
4
  """
5
5
 
6
+ from __future__ import annotations
7
+
6
8
  import importlib
7
9
  import logging
8
10
  import os
@@ -10,27 +12,52 @@ import signal
10
12
  import time
11
13
  from typing import Any, Dict, List, Type
12
14
 
13
- from opentelemetry import metrics, trace
14
- from opentelemetry._logs import set_logger_provider
15
- from opentelemetry.metrics import NoOpMeterProvider
16
- from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
17
- from opentelemetry.sdk.metrics import MeterProvider
18
- from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
19
- from opentelemetry.sdk.resources import Resource
20
- from opentelemetry.sdk.trace import TracerProvider
21
- from opentelemetry.sdk.trace.export import BatchSpanProcessor
22
- from opentelemetry.trace import NoOpTracerProvider
15
+ try:
16
+ from opentelemetry import metrics, trace
17
+ from opentelemetry._logs import set_logger_provider
18
+ from opentelemetry.metrics import NoOpMeterProvider
19
+ from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
20
+ from opentelemetry.sdk.metrics import MeterProvider
21
+ from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
22
+ from opentelemetry.sdk.resources import Resource
23
+ from opentelemetry.sdk.trace import TracerProvider
24
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
25
+ from opentelemetry.trace import NoOpTracerProvider
26
+
27
+ _OPENTELEMETRY_AVAILABLE = True
28
+ except ImportError:
29
+ _OPENTELEMETRY_AVAILABLE = False
30
+ metrics = None
31
+ trace = None
32
+ set_logger_provider = None
33
+ NoOpMeterProvider = None
34
+ LoggerProvider = None
35
+ LoggingHandler = None
36
+ MeterProvider = None
37
+ PeriodicExportingMetricReader = None
38
+ Resource = None
39
+ TracerProvider = None
40
+ BatchSpanProcessor = None
41
+ NoOpTracerProvider = None
23
42
 
24
43
  from blaxel.core.common import Settings
25
44
 
26
- from .exporters import (
27
- DynamicHeadersLogExporter,
28
- DynamicHeadersMetricExporter,
29
- DynamicHeadersSpanExporter,
30
- )
31
- from .instrumentation.map import MAPPINGS
32
- from .log.log import AsyncLogRecordProcessor
33
- from .span import DefaultAttributesSpanProcessor
45
+ if _OPENTELEMETRY_AVAILABLE:
46
+ from .exporters import (
47
+ DynamicHeadersLogExporter,
48
+ DynamicHeadersMetricExporter,
49
+ DynamicHeadersSpanExporter,
50
+ )
51
+ from .instrumentation.map import MAPPINGS
52
+ from .log.log import AsyncLogRecordProcessor
53
+ from .span import DefaultAttributesSpanProcessor
54
+ else:
55
+ DynamicHeadersLogExporter = None
56
+ DynamicHeadersMetricExporter = None
57
+ DynamicHeadersSpanExporter = None
58
+ MAPPINGS = {}
59
+ AsyncLogRecordProcessor = None
60
+ DefaultAttributesSpanProcessor = None
34
61
 
35
62
  logger = logging.getLogger(__name__)
36
63
 
@@ -46,6 +73,8 @@ class TelemetryManager:
46
73
 
47
74
  @property
48
75
  def enabled(self) -> bool:
76
+ if not _OPENTELEMETRY_AVAILABLE:
77
+ return False
49
78
  return (self.settings and self.settings.enable_opentelemetry) or False
50
79
 
51
80
  @property
@@ -127,6 +156,9 @@ class TelemetryManager:
127
156
  def initialize(self, settings: Settings):
128
157
  """Initialize the telemetry system."""
129
158
  self.settings = settings
159
+ if not _OPENTELEMETRY_AVAILABLE:
160
+ logger.debug("OpenTelemetry not available, telemetry disabled")
161
+ return
130
162
  if not self.enabled or self.initialized:
131
163
  return
132
164
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: blaxel
3
- Version: 0.2.28
3
+ Version: 0.2.30
4
4
  Summary: Blaxel - AI development platform SDK
5
5
  Project-URL: Homepage, https://blaxel.ai
6
6
  Project-URL: Documentation, https://docs.blaxel.ai
@@ -1,4 +1,4 @@
1
- blaxel/__init__.py,sha256=DVz0_05tpBPPTNgeCRWyw95kg2le4aBfDfEeeAK3rOQ,412
1
+ blaxel/__init__.py,sha256=cYJGcG2cbn4ieiOOx5G6CgS82USYtqTTSk8I9dSIxrY,412
2
2
  blaxel/core/__init__.py,sha256=CKMC7TaCYdOdnwqcJCN9VjBbNg366coZUGTxI1mgFQQ,1710
3
3
  blaxel/core/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  blaxel/core/agents/__init__.py,sha256=nZfYRnBlpYLD2rnMpVgl3VabQm4w6NoDbGU3qHtpGhw,4376
@@ -333,7 +333,7 @@ blaxel/core/client/models/workspace_labels.py,sha256=WbnUY6eCTkUNdY7hhhSF-KQCl8f
333
333
  blaxel/core/client/models/workspace_runtime.py,sha256=dxEpmwCFPOCRKHRKhY-iW7j6TbtL5qUsbjzSn00uTXU,1665
334
334
  blaxel/core/client/models/workspace_user.py,sha256=70CcifQWYbeWG7TDui4pblTzUe5sVK0AS19vNCzKE8g,3423
335
335
  blaxel/core/common/__init__.py,sha256=K5uIZe98NIjYkjMPdKvP5RuwzIfv1Obm6g3aWh2qjZ0,599
336
- blaxel/core/common/autoload.py,sha256=QwXx4dSHONCmkgU-yT6aNmjsOULFyTGcMRB_Op7M5No,4159
336
+ blaxel/core/common/autoload.py,sha256=MBOyVG3neNwE2zC0G8u-Ijyk3hB0E2nclYDZqhUs5FM,6404
337
337
  blaxel/core/common/env.py,sha256=-nns-h3p0x6MGyGdUNAOEfn4Hi_J_pQfJjRKHz43A8E,1231
338
338
  blaxel/core/common/internal.py,sha256=NDTFh9Duj84os8GkMXjGzn-UVS9zBDyLAcfPxIpoQGA,3218
339
339
  blaxel/core/common/logger.py,sha256=Jt0MCJgYDPq36rl7UyKRDJH76a-AwYdfggNeNYJt6N0,4779
@@ -475,9 +475,9 @@ blaxel/pydantic/model.py,sha256=LQui3m7SjpOunlvm7EjxQOfDof8gfUUnig_EkztEsj0,6039
475
475
  blaxel/pydantic/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
476
476
  blaxel/pydantic/tools.py,sha256=94eYhgPsE7PwL8F2zYh5nWUh4GDgAg_GP0RgsMb2UZE,1969
477
477
  blaxel/pydantic/custom/gemini.py,sha256=2oZFb-7-qaaCY1ajoCkiHSavZqV4uzCLwqp1192u7VM,1051
478
- blaxel/telemetry/__init__.py,sha256=b3Xr-zFq9HEhr0Hp6f9fN8agurOPJnRnr8XAxu835ow,210
479
- blaxel/telemetry/exporters.py,sha256=EoX3uaBVku1Rg49pSNXKFyHhgY5OV3Ih6UlqgjF5epw,1670
480
- blaxel/telemetry/manager.py,sha256=i_3r8W-zpluXiqdIHsoW97Hz-HyZkXMeM76UO-3nS5s,9365
478
+ blaxel/telemetry/__init__.py,sha256=g5wNqaXAVdRmUe0cX7qKHN1cftF2MaHo_QCS4D-u8CQ,257
479
+ blaxel/telemetry/exporters.py,sha256=2eHcHlCXJ4yQcKfWnRcnRhkBim8_yUNqgFIVUDPHkv0,2194
480
+ blaxel/telemetry/manager.py,sha256=r7fbx1ElUO8J4aNJ8SFlT0VxmQI2-an1HrmvannkO4Q,10339
481
481
  blaxel/telemetry/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
482
482
  blaxel/telemetry/span.py,sha256=_mLYE_8vSEenUGmnC-OqPExJqBpOx6iT3BnkmkmQxN4,3644
483
483
  blaxel/telemetry/instrumentation/blaxel_core.py,sha256=j0NwvC92DgMIXd6bMwC_Wor5tOPNPizNDNQDU4wsYAM,4930
@@ -488,7 +488,7 @@ blaxel/telemetry/instrumentation/map.py,sha256=PCzZJj39yiYVYJrxLBNP-NW-tjjYyTijw
488
488
  blaxel/telemetry/instrumentation/utils.py,sha256=KInMYZH-mu9_wvetmf0EmgrfN3Sw8IWk2Y95v2u90_U,1901
489
489
  blaxel/telemetry/log/log.py,sha256=k9Gcs_wCDrZFFGCkcgMBROk2vTJIy7eIVNiP_pI2EPY,2430
490
490
  blaxel/telemetry/log/logger.py,sha256=NPAS3g82ryROjvc_DEZaTIfrcehoLEZoP-JkLxADxc0,4113
491
- blaxel-0.2.28.dist-info/METADATA,sha256=pW7OepRIJm3QJaDY9Njcua3SYxartLoyC16DlTiv9Nw,10102
492
- blaxel-0.2.28.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
493
- blaxel-0.2.28.dist-info/licenses/LICENSE,sha256=p5PNQvpvyDT_0aYBDgmV1fFI_vAD2aSV0wWG7VTgRis,1069
494
- blaxel-0.2.28.dist-info/RECORD,,
491
+ blaxel-0.2.30.dist-info/METADATA,sha256=PqakkZQjcfocQtRxicP0Pamw7AwerRtiId4IkhlxmXw,10102
492
+ blaxel-0.2.30.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
493
+ blaxel-0.2.30.dist-info/licenses/LICENSE,sha256=p5PNQvpvyDT_0aYBDgmV1fFI_vAD2aSV0wWG7VTgRis,1069
494
+ blaxel-0.2.30.dist-info/RECORD,,