blaxel 0.1.14rc54__py3-none-any.whl → 0.1.14rc55__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/common/logger.py CHANGED
@@ -2,8 +2,55 @@
2
2
  This module provides a custom colored formatter for logging and an initialization function
3
3
  to set up logging configurations for Blaxel applications.
4
4
  """
5
-
5
+ import json
6
6
  import logging
7
+ import os
8
+ from opentelemetry import trace
9
+
10
+ class JsonFormatter(logging.Formatter):
11
+ """
12
+ A logger compatible with standard json logging.
13
+ """
14
+ def __init__(self):
15
+ super().__init__()
16
+ self.trace_id_name = os.environ.get('BL_LOGGER_TRACE_ID', 'trace_id')
17
+ self.span_id_name = os.environ.get('BL_LOGGER_SPAN_ID', 'span_id')
18
+ self.labels_name = os.environ.get('BL_LOGGER_LABELS', 'labels')
19
+ self.trace_id_prefix = os.environ.get('BL_LOGGER_TRACE_ID_PREFIX', '')
20
+ self.span_id_prefix = os.environ.get('BL_LOGGER_SPAN_ID_PREFIX', '')
21
+ self.task_index = os.environ.get('BL_TASK_KEY', 'TASK_INDEX')
22
+ self.task_prefix = os.environ.get('BL_TASK_PREFIX', '')
23
+ self.execution_key = os.environ.get('BL_EXECUTION_KEY', 'BL_EXECUTION_ID')
24
+ self.execution_prefix = os.environ.get('BL_EXECUTION_PREFIX', '')
25
+
26
+ def format(self, record):
27
+ """
28
+ Formats the log record by converting it to a JSON object with trace context and environment variables.
29
+ """
30
+ log_entry = {
31
+ 'message': record.getMessage(),
32
+ 'severity': record.levelname,
33
+ self.labels_name: {}
34
+ }
35
+
36
+ # Add trace context if available
37
+ current_span = trace.get_current_span()
38
+ if current_span.is_recording():
39
+ span_context = current_span.get_span_context()
40
+ log_entry[self.trace_id_name] = f"{self.trace_id_prefix}{span_context.trace_id}"
41
+ log_entry[self.span_id_name] = f"{self.span_id_prefix}{span_context.span_id}"
42
+
43
+ # Add task ID if available
44
+ task_id = os.environ.get(self.task_index)
45
+ if task_id:
46
+ log_entry[self.labels_name]['blaxel-task'] = f"{self.task_prefix}{task_id}"
47
+
48
+ # Add execution ID if available
49
+ execution_id = os.environ.get(self.execution_key)
50
+ if execution_id:
51
+ log_entry[self.labels_name]['blaxel-execution'] = f"{self.execution_prefix}{execution_id.split('-')[-1]}"
52
+
53
+ return json.dumps(log_entry)
7
54
 
8
55
 
9
56
  class ColoredFormatter(logging.Formatter):
@@ -51,5 +98,10 @@ def init_logger(log_level: str):
51
98
  logging.getLogger('urllib3').setLevel(logging.CRITICAL)
52
99
  logging.getLogger("httpx").setLevel(logging.CRITICAL)
53
100
  handler = logging.StreamHandler()
54
- handler.setFormatter(ColoredFormatter("%(levelname)s %(name)s - %(message)s"))
101
+
102
+ logger_type = os.environ.get("BL_LOGGER", "http")
103
+ if logger_type == "json":
104
+ handler.setFormatter(JsonFormatter())
105
+ else:
106
+ handler.setFormatter(ColoredFormatter("%(levelname)s %(name)s - %(message)s"))
55
107
  logging.basicConfig(level=log_level, handlers=[handler])
@@ -7,6 +7,7 @@ import importlib
7
7
  import logging
8
8
  import signal
9
9
  import time
10
+ import os
10
11
  from typing import Any, Dict, List, Optional, Type
11
12
 
12
13
  from opentelemetry import metrics, trace
@@ -164,14 +165,15 @@ class TelemetryManager:
164
165
  metrics.set_meter_provider(meter_provider)
165
166
  self.meter = meter_provider.get_meter(__name__)
166
167
 
167
- # Set up the LoggerProvider
168
- self.logger_provider = LoggerProvider(resource=resource)
169
- set_logger_provider(self.logger_provider)
170
- self.logger_provider.add_log_record_processor(
171
- AsyncLogRecordProcessor(self.get_log_exporter())
172
- )
173
- handler = LoggingHandler(level=logging.NOTSET, logger_provider=self.logger_provider)
174
- logging.getLogger().addHandler(handler)
168
+ logger_type = os.environ.get("BL_LOGGER", "http")
169
+ if logger_type == "http":
170
+ self.logger_provider = LoggerProvider(resource=resource)
171
+ set_logger_provider(self.logger_provider)
172
+ self.logger_provider.add_log_record_processor(
173
+ AsyncLogRecordProcessor(self.get_log_exporter())
174
+ )
175
+ handler = LoggingHandler(level=logging.NOTSET, logger_provider=self.logger_provider)
176
+ logging.getLogger().addHandler(handler)
175
177
 
176
178
  # Load and enable instrumentations
177
179
  for name, mapping in MAPPINGS.items():
blaxel/jobs/__init__.py CHANGED
@@ -2,10 +2,21 @@ import argparse
2
2
  import os
3
3
  import sys
4
4
  import asyncio
5
- from typing import Any, Dict, Callable
5
+ import json
6
6
  import requests
7
7
 
8
- class BlJob:
8
+
9
+ from typing import Any, Dict, Callable, Awaitable
10
+ from logging import getLogger
11
+ from ..client import client
12
+ from ..common.env import env
13
+ from ..common.internal import get_global_unique_hash
14
+ from ..common.settings import settings
15
+ from ..instrumentation.span import SpanManager
16
+
17
+
18
+
19
+ class BlJobWrapper:
9
20
  def get_arguments(self) -> Dict[str, Any]:
10
21
  if not os.getenv('BL_EXECUTION_DATA_URL'):
11
22
  parser = argparse.ArgumentParser()
@@ -40,16 +51,144 @@ class BlJob:
40
51
  Handles both async and sync functions.
41
52
  Arguments are passed as keyword arguments to the function.
42
53
  """
43
- try:
44
- parsed_args = self.get_arguments()
45
- if asyncio.iscoroutinefunction(func):
46
- asyncio.run(func(**parsed_args))
47
- else:
48
- func(**parsed_args)
49
- sys.exit(0)
50
- except Exception as error:
51
- print('Job execution failed:', error, file=sys.stderr)
52
- sys.exit(1)
54
+ attributes = {
55
+ "job.name": self.name,
56
+ "span.type": "job.start",
57
+ }
58
+ with SpanManager("blaxel-tracer").create_span(self.name, attributes) as span:
59
+ try:
60
+ parsed_args = self.get_arguments()
61
+ if asyncio.iscoroutinefunction(func):
62
+ asyncio.run(func(**parsed_args))
63
+ else:
64
+ func(**parsed_args)
65
+ span.set_attribute("job.start.success", True)
66
+ sys.exit(0)
67
+ except Exception as error:
68
+ span.set_attribute("job.start.error", str(error))
69
+ print('Job execution failed:', error, file=sys.stderr)
70
+ sys.exit(1)
71
+
72
+
73
+
74
+
75
+ logger = getLogger(__name__)
76
+
77
+ class BlJob:
78
+ def __init__(self, name: str):
79
+ self.name = name
80
+
81
+ @property
82
+ def internal_url(self):
83
+ """Get the internal URL for the job using a hash of workspace and job name."""
84
+ hash = get_global_unique_hash(settings.workspace, "job", self.name)
85
+ return f"{settings.run_internal_protocol}://bl-{settings.env}-{hash}.{settings.run_internal_hostname}"
86
+
87
+ @property
88
+ def forced_url(self):
89
+ """Get the forced URL from environment variables if set."""
90
+ env_var = self.name.replace("-", "_").upper()
91
+ if env[f"BL_JOB_{env_var}_URL"]:
92
+ return env[f"BL_JOB_{env_var}_URL"]
93
+ return None
94
+
95
+ @property
96
+ def external_url(self):
97
+ return f"{settings.run_url}/{settings.workspace}/jobs/{self.name}"
98
+
99
+ @property
100
+ def fallback_url(self):
101
+ if self.external_url != self.url:
102
+ return self.external_url
103
+ return None
104
+
105
+ @property
106
+ def url(self):
107
+ if self.forced_url:
108
+ return self.forced_url
109
+ if settings.run_internal_hostname:
110
+ return self.internal_url
111
+ return self.external_url
112
+
113
+ def call(self, url, input_data, headers: dict = {}, params: dict = {}):
114
+ body = {
115
+ "tasks": input_data
116
+ }
117
+
118
+ return client.get_httpx_client().post(
119
+ url+"/executions",
120
+ headers={
121
+ 'Content-Type': 'application/json',
122
+ **headers
123
+ },
124
+ json=body,
125
+ params=params
126
+ )
127
+
128
+ async def acall(self, url, input_data, headers: dict = {}, params: dict = {}):
129
+ logger.debug(f"Job Calling: {self.name}")
130
+ body = {
131
+ "tasks": input_data
132
+ }
133
+
134
+ return await client.get_async_httpx_client().post(
135
+ url+"/executions",
136
+ headers={
137
+ 'Content-Type': 'application/json',
138
+ **headers
139
+ },
140
+ json=body,
141
+ params=params
142
+ )
143
+
144
+ def run(self, input: Any, headers: dict = {}, params: dict = {}) -> str:
145
+ attributes = {
146
+ "job.name": self.name,
147
+ "span.type": "job.run",
148
+ }
149
+ with SpanManager("blaxel-tracer").create_active_span(self.name, attributes) as span:
150
+ logger.debug(f"Job Calling: {self.name}")
151
+ response = self.call(self.url, input, headers, params)
152
+ if response.status_code >= 400:
153
+ if not self.fallback_url:
154
+ span.set_attribute("job.run.error", response.text)
155
+ raise Exception(f"Job {self.name} returned status code {response.status_code} with body {response.text}")
156
+ response = self.call(self.fallback_url, input, headers, params)
157
+ if response.status_code >= 400:
158
+ span.set_attribute("job.run.error", response.text)
159
+ raise Exception(f"Job {self.name} returned status code {response.status_code} with body {response.text}")
160
+ span.set_attribute("job.run.result", response.text)
161
+ return response.text
162
+
163
+ async def arun(self, input: Any, headers: dict = {}, params: dict = {}) -> Awaitable[str]:
164
+ attributes = {
165
+ "job.name": self.name,
166
+ "span.type": "job.run",
167
+ }
168
+ with SpanManager("blaxel-tracer").create_active_span(self.name, attributes) as span:
169
+ logger.debug(f"Job Calling: {self.name}")
170
+ response = await self.acall(self.url, input, headers, params)
171
+ if response.status_code >= 400:
172
+ if not self.fallback_url:
173
+ span.set_attribute("job.run.error", response.text)
174
+ raise Exception(f"Job {self.name} returned status code {response.status_code} with body {response.text}")
175
+ response = await self.acall(self.fallback_url, input, headers, params)
176
+ if response.status_code >= 400:
177
+ span.set_attribute("job.run.error", response.text)
178
+ raise Exception(f"Job {self.name} returned status code {response.status_code} with body {response.text}")
179
+ span.set_attribute("job.run.result", response.text)
180
+ return response.text
181
+
182
+ def __str__(self):
183
+ return f"Job {self.name}"
184
+
185
+ def __repr__(self):
186
+ return self.__str__()
187
+
188
+
189
+
190
+ def bl_job(name: str):
191
+ return BlJob(name)
53
192
 
54
193
  # Create a singleton instance
55
- bl_job = BlJob()
194
+ bl_start_job = BlJobWrapper()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: blaxel
3
- Version: 0.1.14rc54
3
+ Version: 0.1.14rc55
4
4
  Summary: Add your description here
5
5
  Author-email: cploujoux <cploujoux@blaxel.ai>
6
6
  License-File: LICENSE
@@ -263,14 +263,14 @@ blaxel/client/models/workspace_user.py,sha256=70CcifQWYbeWG7TDui4pblTzUe5sVK0AS1
263
263
  blaxel/common/autoload.py,sha256=NFuK71-IHOY2JQyEBSjDCVfUaQ8D8PJsEUEryIdG4AU,263
264
264
  blaxel/common/env.py,sha256=wTbzPDdNgz4HMJiS2NCZmQlN0qpxy1PQEYBaZgtvhoc,1247
265
265
  blaxel/common/internal.py,sha256=PExgeKfJEmjINKreNb3r2nB5GAfG7uJhbfqHxuxBED8,2395
266
- blaxel/common/logger.py,sha256=emqgonfZMBIaQPowpngWOOZxWQieKP-yj_OzGZT8Oe8,1918
266
+ blaxel/common/logger.py,sha256=7oWvrZ4fg7qUfrXe7oFAeH-1pTxadrvWQmPDn7bvbmQ,4111
267
267
  blaxel/common/settings.py,sha256=7KTryuBdud0IfHqykX7xEEtpgq5M5h1Z8YEzYKsHB-Q,2327
268
268
  blaxel/instrumentation/exporters.py,sha256=EoX3uaBVku1Rg49pSNXKFyHhgY5OV3Ih6UlqgjF5epw,1670
269
269
  blaxel/instrumentation/log.py,sha256=4tGyvLg6r4DbjqJfajYbbZ1toUzF4Q4H7kHVqYWFAEA,2537
270
- blaxel/instrumentation/manager.py,sha256=jVwVasyMI6tu0zv27DVYOaN57nXYuHFJ8QzJQKaNoK4,8880
270
+ blaxel/instrumentation/manager.py,sha256=vX8RT84upjzgCUeiULp9QpDSSNVnPNFxLq0sMVz4Pjs,8974
271
271
  blaxel/instrumentation/map.py,sha256=zZoiUiQHmik5WQZ4VCWNARSa6ppMi0r7D6hlb41N-Mg,1589
272
272
  blaxel/instrumentation/span.py,sha256=X2lwfu_dyxwQTMQJT2vbXOrbVSChEhjRLc413QOxQJM,3244
273
- blaxel/jobs/__init__.py,sha256=xJzVGQG7hB8w7V6OL9DoDH0I2Sd8a6lF5FMjRiQefZE,1827
273
+ blaxel/jobs/__init__.py,sha256=ImTr7XPndP8_MO3qGcriJNBl0Bre-7pZvYZ2nnaOS94,6870
274
274
  blaxel/mcp/__init__.py,sha256=KednMrtuc4Y0O3lv7u1Lla54FCk8UX9c1k0USjL3Ahk,69
275
275
  blaxel/mcp/client.py,sha256=cFFXfpKXoMu8qTUly2ejF0pX2iBQkSNAxqwvDV1V6xY,4979
276
276
  blaxel/mcp/server.py,sha256=GIldtA_NgIc2dzd7ZpPvpbhpIt_7AfKu5yS_YJ0bDGg,7310
@@ -341,7 +341,7 @@ blaxel/tools/llamaindex.py,sha256=-gQ-C9V_h9a11J4ItsbWjXrCJOg0lRKsb98v9rVsNak,71
341
341
  blaxel/tools/openai.py,sha256=GuFXkj6bXEwldyVr89jEsRAi5ihZUVEVe327QuWiGNs,653
342
342
  blaxel/tools/pydantic.py,sha256=CvnNbAG_J4yBtA-XFI4lQrq3FYKjNd39hu841vZT004,1801
343
343
  blaxel/tools/types.py,sha256=YPCGJ4vZDhqR0X2H_TWtc5chQScsC32nGTQdRKJlO8Y,707
344
- blaxel-0.1.14rc54.dist-info/METADATA,sha256=2jL6j0mOA14FnKcDB52Qmv8Fa2VC_RxqkF_9C8OJ8rQ,11776
345
- blaxel-0.1.14rc54.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
346
- blaxel-0.1.14rc54.dist-info/licenses/LICENSE,sha256=p5PNQvpvyDT_0aYBDgmV1fFI_vAD2aSV0wWG7VTgRis,1069
347
- blaxel-0.1.14rc54.dist-info/RECORD,,
344
+ blaxel-0.1.14rc55.dist-info/METADATA,sha256=8_tzp5TdE2MyLup-KwDgmWgjZQUG50UcuJu-XHhW5wg,11776
345
+ blaxel-0.1.14rc55.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
346
+ blaxel-0.1.14rc55.dist-info/licenses/LICENSE,sha256=p5PNQvpvyDT_0aYBDgmV1fFI_vAD2aSV0wWG7VTgRis,1069
347
+ blaxel-0.1.14rc55.dist-info/RECORD,,